diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..f2e7f7d0 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,10 @@ +# EditorConfig is awesome: http://EditorConfig.org + +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*] +end_of_line = lf +insert_final_newline = true +indent_style = tab diff --git a/README.md b/README.md index 49c18202..611c95eb 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ ![Rambox](https://raw.githubusercontent.com/saenzramiro/rambox/master/resources/screenshots/mac.png) -## Services available - 57 +## Services available - 72 WhatsApp Messenger @@ -68,7 +68,6 @@ Yahoo! Mail Ryver Yandex Mail -OFfice 365 Dasher DingTalk FlowDock @@ -94,8 +93,23 @@ iCloud Mail IRC Cloud Kiwi IRC - -


























+Smooch +Crisp +Flock +Openmailbox +Typetalk +Drift +mmmelon +Cisco Spark +Fleep +Socialcast +Actor +Riot +Pushbullet +Movim +Kaiwa + +































## Features @@ -122,6 +136,10 @@ Sessions will persist using the [partition:persist](http://electron.atom.io/docs Sync feature use Auth0 for Single Sign On & Token Based Authentication with the integration with Firebase to store the services that user is using (and the configuration for each service). You are always welcome to check the code! ;) +## Translations + +Help us translate Rambox on https://crowdin.com/project/rambox/invite. + ## [Install on Linux - Steps](https://github.com/saenzramiro/rambox/wiki/Install-on-Linux) ## [To Do](https://github.com/saenzramiro/rambox/blob/master/TODO.md) diff --git a/app.js b/app.js index c0489dd6..a77c0ea4 100644 --- a/app.js +++ b/app.js @@ -130,3 +130,8 @@ ipc.on('setBadge', function(event, messageCount) { ipc.send('setBadge', messageCount, canvas.toDataURL()); }); +// Reload Current Service +ipc.on('reloadCurrentService', function(e) { + var tab = Ext.cq1('app-main').getActiveTab(); + if ( tab.id !== 'ramboxTab' ) tab.reloadService(); +}); diff --git a/app.json b/app.json index 8fac74ac..abef42a1 100644 --- a/app.json +++ b/app.json @@ -113,7 +113,10 @@ { "path": "resources/js/GALocalStorage.js" }, - { + { + "path": "resources/js/loadscreen.js" + }, + { "path": "env.js" }, { diff --git a/app/Application.js b/app/Application.js index e3380485..43fdc2b3 100644 --- a/app/Application.js +++ b/app/Application.js @@ -29,9 +29,7 @@ Ext.define('Rambox.Application', { // Set Google Analytics events ga_storage._setAccount('UA-80680424-1'); ga_storage._trackPageview('/index.html', 'main'); - - // Initialize Auth0 - Rambox.ux.Auth0.init(); + ga_storage._trackEvent('Versions', require('electron').remote.app.getVersion()); // Add shortcuts to switch services using CTRL + Number var map = new Ext.util.KeyMap({ @@ -45,9 +43,10 @@ Ext.define('Rambox.Application', { ,handler: function(key) { var tabPanel = Ext.cq1('app-main'); var activeIndex = tabPanel.items.indexOf(tabPanel.getActiveTab()); - if ( tabPanel.items.items[activeIndex + 1] && tabPanel.items.items[activeIndex + 1].id === 'tbfill' ) activeIndex++; - if ( !tabPanel.items.items[activeIndex + 1] ) activeIndex = -1; - tabPanel.setActiveTab( activeIndex + 1 ); + var i = activeIndex + 1; + if ( i >= tabPanel.items.items.length - 1 ) i = 0; + while ( tabPanel.items.items[i].id === 'tbfill' ) i++; + tabPanel.setActiveTab( i ); } } ,{ @@ -58,10 +57,38 @@ Ext.define('Rambox.Application', { ,handler: function(key) { var tabPanel = Ext.cq1('app-main'); var activeIndex = tabPanel.items.indexOf(tabPanel.getActiveTab()); - if ( tabPanel.items.items[activeIndex - 1] && tabPanel.items.items[activeIndex - 1].id === 'tbfill' ) activeIndex--; - if ( !tabPanel.items.items[activeIndex - 1] && tabPanel.items.items.length !== 2 ) activeIndex = tabPanel.items.items.length; - if ( tabPanel.items.items.length === 2 ) activeIndex = 1; - tabPanel.setActiveTab( activeIndex - 1 ); + var i = activeIndex - 1; + if ( i < 0 ) i = tabPanel.items.items.length - 1; + while ( tabPanel.items.items[i].id === 'tbfill' || i < 0 ) i--; + tabPanel.setActiveTab( i ); + } + } + ,{ + key: Ext.event.Event.PAGE_DOWN + ,ctrl: true + ,alt: false + ,shift: false + ,handler: function(key) { + var tabPanel = Ext.cq1('app-main'); + var activeIndex = tabPanel.items.indexOf(tabPanel.getActiveTab()); + var i = activeIndex + 1; + if ( i >= tabPanel.items.items.length - 1 ) i = 0; + while ( tabPanel.items.items[i].id === 'tbfill' ) i++; + tabPanel.setActiveTab( i ); + } + } + ,{ + key: Ext.event.Event.PAGE_UP + ,ctrl: true + ,alt: false + ,shift: false + ,handler: function(key) { + var tabPanel = Ext.cq1('app-main'); + var activeIndex = tabPanel.items.indexOf(tabPanel.getActiveTab()); + var i = activeIndex - 1; + if ( i < 0 ) i = tabPanel.items.items.length - 1; + while ( tabPanel.items.items[i].id === 'tbfill' ) i--; + tabPanel.setActiveTab( i ); } } ,{ @@ -93,7 +120,9 @@ Ext.define('Rambox.Application', { ,ctrl: true ,alt: false ,handler: function(key) { - Ext.cq1('app-main').setActiveTab(key - 48); + key = key - 48; + if ( key >= Ext.cq1('app-main').items.indexOf(Ext.getCmp('tbfill')) ) key++; + Ext.cq1('app-main').setActiveTab(key); } } ,{ diff --git a/app/model/ServiceList.js b/app/model/ServiceList.js index 77e47c4e..91153c4f 100644 --- a/app/model/ServiceList.js +++ b/app/model/ServiceList.js @@ -31,6 +31,10 @@ Ext.define('Rambox.model.ServiceList', { name: 'allow_popups' ,type: 'boolean' ,defaultValue: false + },{ + name: 'manual_notifications' + ,type: 'boolean' + ,defaultValue: false },{ name: 'userAgent' ,type: 'string' @@ -39,5 +43,9 @@ Ext.define('Rambox.model.ServiceList', { name: 'note' ,type: 'string' ,defaultValue: '' + },{ + name: 'custom_domain' + ,type: 'boolean' + ,defaultValue: false }] }); diff --git a/app/package.json b/app/package.json index b6b54529..602caf51 100644 --- a/app/package.json +++ b/app/package.json @@ -1,7 +1,7 @@ { "name": "Rambox", "productName": "Rambox", - "version": "0.4.2", + "version": "0.4.4", "description": "Rambox", "main": "electron/main.js", "private": true, diff --git a/app/store/ServicesList.js b/app/store/ServicesList.js index e669e20c..4047e9b1 100644 --- a/app/store/ServicesList.js +++ b/app/store/ServicesList.js @@ -74,6 +74,7 @@ Ext.define('Rambox.store.ServicesList', { ,url: 'https://hangouts.google.com/' ,type: 'messaging' ,titleBlink: true + ,manual_notifications: true ,js_unread: 'function checkUnread(){updateBadge(document.getElementById("hangout-landing-chat").lastChild.contentWindow.document.body.getElementsByClassName("ee").length)}function updateBadge(e){e>=1?document.title="("+e+") "+originalTitle:document.title=originalTitle}var originalTitle=document.title;setInterval(checkUnread,3000);' }, { @@ -84,6 +85,7 @@ Ext.define('Rambox.store.ServicesList', { ,url: 'https://___.hipchat.com/chat' ,type: 'messaging' ,js_unread: 'function checkUnread(){var e=document.getElementsByClassName("hc-badge"),t=0;for(i=0;i=1?document.title="("+e+") "+originalTitle:document.title=originalTitle}var originalTitle=document.title;setInterval(checkUnread,3000);' + ,custom_domain: true }, { id: 'telegram' @@ -121,7 +123,8 @@ Ext.define('Rambox.store.ServicesList', { ,description: 'Inbox by Gmail is a new app from the Gmail team. Inbox is an organized place to get things done and get back to what matters. Bundles keep emails organized.' ,url: 'http://inbox.google.com/?cid=imp' ,type: 'email' - ,js_unread: 'function checkUnread(){updateBadge(document.getElementsByClassName("ss").length)}function updateBadge(a){a>=1?document.title="("+a+") "+originalTitle:document.title=originalTitle,checked&&a>oldUnread&&new Notification("Inbox",{body:"You have a new email",icon:"https://raw.githubusercontent.com/saenzramiro/rambox/master/resources/icons/inbox.png"}),checked=!0,oldUnread=a}var checked=!1,oldUnread,originalTitle=document.title;setInterval(checkUnread,3e3);' + ,manual_notifications: true + ,js_unread: 'function checkUnread(){updateBadge(document.getElementsByClassName("ss").length)}function updateBadge(a){a>=1?document.title="("+a+") "+originalTitle:document.title=originalTitle}var originalTitle=document.title;setInterval(checkUnread,3e3);' }, { id: 'chatwork' @@ -186,7 +189,8 @@ Ext.define('Rambox.store.ServicesList', { ,description: 'Take control. Do more. Outlook is the free email and calendar service that helps you stay on top of what matters and get things done.' ,url: 'https://mail.live.com/' ,type: 'email' - ,js_unread: 'function checkUnread(){var a=$(".subfolders [role=treeitem]:first .treeNodeRowElement").siblings().last().text();updateBadge(""===a?0:parseInt(a))}function updateBadge(a){a>=1?document.title="("+a+") "+originalTitle:document.title=originalTitle,checked&&a>oldUnread&&new Notification("Outlook",{body:"You have a new email",icon:"https://raw.githubusercontent.com/saenzramiro/rambox/master/resources/icons/outlook.png"}),checked=!0,oldUnread=a}var checked=!1,oldUnread,originalTitle=document.title;setInterval(checkUnread,3e3);' + ,manual_notifications: true + ,js_unread: 'function checkUnread(){var a=$(".subfolders [role=treeitem]:first .treeNodeRowElement").siblings().last().text();updateBadge(""===a?0:parseInt(a))}function updateBadge(a){a>=1?document.title="("+a+") "+originalTitle:document.title=originalTitle}var originalTitle=document.title;setInterval(checkUnread,3e3);' }, { id: 'outlook365' @@ -195,7 +199,8 @@ Ext.define('Rambox.store.ServicesList', { ,description: 'Outlook for Business' ,url: 'https://outlook.office.com/owa/' ,type: 'email' - ,js_unread: 'function checkUnread(){var a=$(".subfolders [role=treeitem]:first .treeNodeRowElement").siblings().last().text();updateBadge(""===a?0:parseInt(a))}function updateBadge(a){a>=1?document.title="("+a+") "+originalTitle:document.title=originalTitle,checked&&a>oldUnread&&new Notification("Outlook 365",{body:"You have a new email",icon:"https://raw.githubusercontent.com/saenzramiro/rambox/master/resources/icons/outlook365.png"}),checked=!0,oldUnread=a}var checked=!1,oldUnread,originalTitle=document.title;setInterval(checkUnread,3e3);' + ,manual_notifications: true + ,js_unread: 'function checkUnread(){var a=$(".subfolders [role=treeitem]:first .treeNodeRowElement").siblings().last().text();updateBadge(""===a?0:parseInt(a))}function updateBadge(a){a>=1?document.title="("+a+") "+originalTitle:document.title=originalTitle}var originalTitle=document.title;setInterval(checkUnread,3e3);' }, { id: 'yahoo' @@ -204,6 +209,7 @@ Ext.define('Rambox.store.ServicesList', { ,description: 'Web-based email service offered by the American company Yahoo!. The service is free for personal use, and paid-for business email plans are available.' ,url: 'https://mail.yahoo.com/' ,type: 'email' + ,note: 'To enable desktop notifications, you have to go to Options inside Yahoo! Mail.' }, { id: 'protonmail' @@ -311,6 +317,7 @@ Ext.define('Rambox.store.ServicesList', { ,description: 'Mattermost is an open source, self-hosted Slack-alternative. As an alternative to proprietary SaaS messaging, Mattermost brings all your team communication into one place, making it searchable and accessible anywhere.' ,url: '___' ,type: 'messaging' + ,js_unread: 'Object.defineProperty(document,"title",{configurable:!0,set:function(a){document.getElementsByTagName("title")[0].innerHTML=a[0]==="*"?"(•) Mattermost":a},get:function(){return document.getElementsByTagName("title")[0].innerHTML}});' }, { id: 'dingtalk' @@ -352,6 +359,7 @@ Ext.define('Rambox.store.ServicesList', { ,logo: 'custom.png' ,name: '_Custom Service' ,description: 'Add a custom service if is not listed above.' + ,url: '___' ,type: 'custom' ,allow_popups: true }, @@ -413,6 +421,8 @@ Ext.define('Rambox.store.ServicesList', { ,description: 'Ad-free business Email Hosting with a clean, minimalist interface. Integrated Calendar, Contacts, Notes, Tasks apps.' ,url: 'https://mail.zoho.com/' ,type: 'email' + ,js_unread: 'zmail.aInfo[zmail.accId].mailId = "a";' + ,note: 'To enable desktop notifications, you have to go to Settings inside Zoho Email.' }, { id: 'zohochat' @@ -449,13 +459,14 @@ Ext.define('Rambox.store.ServicesList', { ,type: 'email' }, { - id:' irccloud' + id: ' irccloud' ,logo: 'irccloud.png' ,name: 'IRCCloud' ,description: 'IRCCloud is a modern IRC client that keeps you connected, with none of the baggage.' ,url: 'https://www.irccloud.com/' ,type: 'messaging' ,js_unread: 'function checkUnread(){var t=0;[].map.call(document.querySelectorAll(".bufferBadges > .badge"),n=>n.textContent?parseInt(n.textContent,10):0).reduce((x,y)=>x+y,0);updateBadge(t)}function updateBadge(e){e>=1?document.title="("+e+") "+originalTitle:document.title=originalTitle}var originalTitle=document.title;setInterval(checkUnread,3000);' + ,custom_domain: true }, { id: 'ryver' @@ -481,6 +492,7 @@ Ext.define('Rambox.store.ServicesList', { ,url: 'https://kiwiirc.com/client' ,type: 'messaging' ,js_unread: 'function getUnreadCount(){var a=0;$(".activity").each(function(){a+=parseInt($(this).html())});var b=!1;return $(".panel[style*=display: block] .msg").each(function(){b?a++:$(this).hasClass("last_seen")&&(b=!0)}),a}function updateTitle(a){count=getUnreadCount(),cleanTitle=a.match(re),null!==cleanTitle&&cleanTitle.length>1?cleanTitle=cleanTitle[1]:cleanTitle=a,a=count>0?"("+getUnreadCount()+") "+cleanTitle:cleanTitle,$("title").text(a)}var re=/\(\d+\)[ ](.*)/;Object.defineProperty(document,"title",{configurable:!0,set:function(a){updateTitle(a)},get:function(){return $("title").text()}}),setInterval(function(){updateTitle(document.title)},3e3);' + ,custom_domain: true }, { id: 'icloud' @@ -524,6 +536,130 @@ Ext.define('Rambox.store.ServicesList', { ,url: '___' ,type: 'email' ,js_unread: 'function check_unread(){update_badge(appCtxt.getById(ZmFolder.ID_INBOX).numUnread)}function update_badge(a){document.title=a>0?"("+a+") "+original_title:original_title}const original_title=document.title;setInterval(check_unread,3e3);' + }, + { + id: 'kaiwa' + ,logo: 'kaiwa.png' + ,name: 'Kaiwa' + ,description: 'A modern and Open Source Web client for XMPP.' + ,url: '___' + ,type: 'messaging' + ,js_unread: 'function check_unread() { let count=0; for (let node of document.getElementsByClassName("unread")){ if (node.innerHTML){ count += parseInt(node.innerHTML); } } update_badge(count);}function update_badge(a) { document.title = a > 0 ? "(" + a + ") " + original_title : original_title}const original_title = document.title;setInterval(check_unread, 3e3);' + }, + { + id: 'movim' + ,logo: 'movim.png' + ,name: 'Movim' + ,description: 'Movim is a decentralized social network, written in PHP and HTML5 and based on the XMPP standard protocol.' + ,url: 'https://___.movim.eu/' + ,type: 'messaging' + ,js_unread: 'function checkUnread(){var a=document.getElementsByClassName("color dark"),b=0;for(i=0;i=1?document.title="("+a+") "+originalTitle:document.title=originalTitle}var originalTitle=document.title;setInterval(checkUnread,3e3);' + ,custom_domain: true + }, + { + id: 'pushbullet' + ,logo: 'pushbullet.png' + ,name: 'Pushbullet' + ,description: 'Pushbullet connects your devices, making them feel like one.' + ,url: 'https://www.pushbullet.com/' + ,type: 'messaging' + }, + { + id: 'riot' + ,logo: 'riot.png' + ,name: 'Riot' + ,description: 'Riot is a simple and elegant collaboration environment that gathers all of your different conversations and app integrations into one single app.' + ,url: 'https://riot.im/app/' + ,type: 'messaging' + }, + { + id: 'actor' + ,logo: 'actor.png' + ,name: 'Actor' + ,description: 'Free and Secure text, photo and voice messages over 2G/3G or Wi-Fi.' + ,url: 'https://app.actor.im/' + ,type: 'messaging' + }, + { + id: 'socialcast' + ,logo: 'socialcast.png' + ,name: 'Socialcast' + ,description: 'Socialcast is the premier enterprise social networking platform that connects people to the knowledge, ideas and resources they need to work more effectively.' + ,url: 'https://___.socialcast.com/' + ,type: 'messaging' + }, + { + id: 'fleep' + ,logo: 'fleep.png' + ,name: 'Fleep' + ,description: 'Fleep enables communication within and across organizations - be it your team chats, project communication or 1:1 conversations.' + ,url: 'https://fleep.io/chat' + ,type: 'messaging' + ,js_unread: 'document.getElementsByClassName("google-login-area")[0].remove();document.getElementsByClassName("microsoft-login-area")[0].remove();' + }, + { + id: 'spark' + ,logo: 'spark.png' + ,name: 'Cisco Spark' + ,description: 'Cisco Spark is for group chat, video calling, and sharing documents with your team. It’s all backed by Cisco security and reliability.' + ,url: 'https://web.ciscospark.com/' + ,type: 'messaging' + }, + { + id: 'mmmelon' + ,logo: 'mmmelon.png' + ,name: 'mmmelon' + ,description: 'The ultimate tool for daily management of projects and teams. Cloud-based, web and mobile.' + ,url: '___' + ,type: 'messaging' + }, + { + id: 'drift' + ,logo: 'drift.png' + ,name: 'Drift' + ,description: 'Drift is a messaging app that makes it easy for businesses to talk to their website visitors and customers in real-time, from anywhere.' + ,url: 'https://app.drift.com/' + ,type: 'messaging' + }, + { + id: 'typetalk' + ,logo: 'typetalk.png' + ,name: 'Typetalk' + ,description: 'Typetalk brings fun and ease to team discussions through instant messaging on desktop and mobile devices.' + ,url: 'https://typetalk.in/signin' + ,type: 'messaging' + }, + { + id: 'openmailbox' + ,logo: 'openmailbox.png' + ,name: 'Openmailbox' + ,description: 'Free mail hosting. Respect your rights and your privacy.' + ,url: 'https://www.openmailbox.org/webmail/' + ,type: 'email' + }, + { + id: 'flock' + ,logo: 'flock.png' + ,name: 'Flock' + ,description: 'Flock is a free enterprise tool for business communication. Packed with tons of productivity features, Flock drives efficiency and boosts speed of execution.' + ,url: 'https://web.flock.co/' + ,type: 'messaging' + }, + { + id: 'crisp' + ,logo: 'crisp.png' + ,name: 'Crisp' + ,description: 'Connect your customers to your team.' + ,url: 'https://app.crisp.im/inbox' + ,type: 'messaging' + }, + { + id: 'smooch' + ,logo: 'smooch.png' + ,name: 'Smooch' + ,description: 'Unified multi-channel messaging for businesses, bots and software makers.' + ,url: 'https://app.smooch.io/' + ,type: 'messaging' } - ] + ] }); diff --git a/app/ux/Auth0.js b/app/ux/Auth0.js index e7f40ce5..92c44b83 100644 --- a/app/ux/Auth0.js +++ b/app/ux/Auth0.js @@ -113,7 +113,7 @@ Ext.define('Rambox.ux.Auth0', { } else { 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); + Ext.cq1('app-main').getController().removeAllServices(false); } else { me.logout(); } @@ -124,7 +124,7 @@ Ext.define('Rambox.ux.Auth0', { } 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() { + Ext.cq1('app-main').getController().removeAllServices(false, function() { importServices(snapshot2); }); } else { @@ -140,10 +140,10 @@ Ext.define('Rambox.ux.Auth0', { localStorage.setItem('id_token', authResult.idToken); } }); - }).catch(function(error) { + })['catch'](function(error) { Ext.Msg.hide(); Ext.Msg.show({ - title: 'Firebug Error' + title: 'Firebase Error' ,message: error.message+'

Code: '+error.code+'

Sorry, try again later.' ,icon: Ext.Msg.ERROR ,buttons: Ext.Msg.OK @@ -164,6 +164,8 @@ Ext.define('Rambox.ux.Auth0', { ,login: function() { var me = this; + if ( !me.auth0 ) Rambox.ux.Auth0.init(); + me.lock.show(); } diff --git a/app/ux/Firebase.js b/app/ux/Firebase.js index cebf25f1..6fc3e008 100644 --- a/app/ux/Firebase.js +++ b/app/ux/Firebase.js @@ -14,14 +14,43 @@ Ext.define('Rambox.ux.Firebase', { // Attach an asynchronous callback to read the data at our posts reference ref.on("child_changed", function(snapshot, prevChildKey) { + // Disable duplicate actions when user edit a service + var rec = Ext.getStore('Services').findRecord('firebase_key', snapshot.key); + var recData = Ext.clone(rec.data); + delete recData.id; + delete recData.firebase_key; + if ( Ext.Object.equals(recData, snapshot.val()) ) return; + console.info('Firebase - Child Changed', snapshot.val(), snapshot.key, prevChildKey); + // Suspend events Ext.getStore('Services').suspendEvent('update'); - var rec = Ext.getStore('Services').findRecord('firebase_key', snapshot.key); - + // Change the title of the Tab Ext.getCmp('tab_'+rec.get('id')).setTitle(snapshot.val().name); - + // Change sound of the Tab + Ext.getCmp('tab_'+rec.get('id')).setAudioMuted(snapshot.val().muted); + // Change notifications of the Tab + Ext.getCmp('tab_'+rec.get('id')).setNotifications(snapshot.val().notifications); + // Change the icon of the Tab + if ( rec.get('type') === 'custom' && rec.get('logo') !== snapshot.val().logo ) Ext.getCmp('tab_'+rec.get('id')).setConfig('icon', snapshot.val().logo === '' ? 'resources/icons/custom.png' : snapshot.val().logo); + // Change the URL of the Tab + if ( rec.get('url') !== snapshot.val().url ) Ext.getCmp('tab_'+rec.get('id')).setURL(snapshot.val().url); + + // Change the align of the Tab + if ( rec.get('align') !== snapshot.val().align ) { + if ( rec.get('align') === 'left' ) { + Ext.cq1('app-main').moveBefore(Ext.getCmp('tab_'+rec.get('id')), Ext.getCmp('tbfill')); + } else { + Ext.cq1('app-main').moveAfter(Ext.getCmp('tab_'+rec.get('id')), Ext.getCmp('tbfill')); + } + } + // Apply the JS Code of the Tab + if ( rec.get('js_unread') !== snapshot.val().js_unread ) { + Ext.Msg.confirm('CUSTOM CODE', 'Rambox needs to reload the service to execute the new JavaScript code. Do you want to do it now?', function( btnId ) { + if ( btnId === 'yes' ) Ext.getCmp('tab_'+rec.get('id')).reloadService(); + }); + } // Position if ( rec.get('position') !== snapshot.val().position ) { var pos = parseInt(snapshot.val().position); @@ -29,11 +58,14 @@ Ext.define('Rambox.ux.Firebase', { Ext.cq1('app-main').move(Ext.getCmp('tab_'+rec.get('id')), pos); } - // Enable/Disable - if ( rec.get('enable') !== snapshot.val().enable ) Ext.getCmp('tab_'+rec.get('id')).setEnabled(snapshot.val().enabled); - rec.set(snapshot.val()); rec.save(); + Ext.getCmp('tab_'+rec.get('id')).record = rec; + Ext.getCmp('tab_'+rec.get('id')).tabConfig.service = rec; + + // Enable/Disable + if ( recData.enabled !== snapshot.val().enabled ) Ext.getCmp('tab_'+rec.get('id')).setEnabled(snapshot.val().enabled); + Ext.getStore('Services').resumeEvent('update'); Ext.getStore('Services').load(); }, function (errorObject) { diff --git a/app/ux/WebView.js b/app/ux/WebView.js index b222c17d..e681b331 100644 --- a/app/ux/WebView.js +++ b/app/ux/WebView.js @@ -34,7 +34,14 @@ Ext.define('Rambox.ux.WebView',{ if ( me.record.get('trust') ) ipc.send('allowCertificate', me.src); Ext.apply(me, { - items: me.webViewConstructor(me.record.get('enabled')) + items: me.webViewConstructor() + ,title: me.record.get('name') + ,icon: me.record.get('type') === 'custom' ? (me.record.get('logo') === '' ? 'resources/icons/custom.png' : me.record.get('logo')) : 'resources/icons/'+me.record.get('logo') + ,src: me.record.get('url') + ,type: me.record.get('type') + ,align: me.record.get('align') + ,notifications: me.record.get('notifications') + ,muted: me.record.get('muted') ,tabConfig: { listeners: { badgetextchange: me.onBadgeTextChange @@ -44,6 +51,7 @@ Ext.define('Rambox.ux.WebView',{ e.stopEvent(); }); } + ,scope: me } ,clickEvent: '' ,style: !me.record.get('enabled') ? '-webkit-filter: grayscale(1)' : '' @@ -121,10 +129,12 @@ Ext.define('Rambox.ux.WebView',{ me.callParent(config); } - ,webViewConstructor: function(enabled) { + ,webViewConstructor: function( enabled ) { var me = this; var cfg; + enabled = enabled || me.record.get('enabled'); + if ( !enabled ) { cfg = { xtype: 'container' @@ -140,25 +150,26 @@ Ext.define('Rambox.ux.WebView',{ ,autoShow: true ,autoEl: { tag: 'webview' - ,src: me.src + ,src: me.record.get('url') ,style: 'width:100%;height:100%;' - ,partition: 'persist:' + me.type + '_' + me.id.replace('tab_', '') + (localStorage.getItem('id_token') ? '_' + Ext.decode(localStorage.getItem('profile')).user_id : '') + ,partition: 'persist:' + me.record.get('type') + '_' + me.id.replace('tab_', '') + (localStorage.getItem('id_token') ? '_' + Ext.decode(localStorage.getItem('profile')).user_id : '') ,plugins: 'true' ,allowtransparency: 'on' ,autosize: 'on' ,disablewebsecurity: 'on' ,blinkfeatures: 'ApplicationCache,GlobalCacheStorage' - ,useragent: Ext.getStore('ServicesList').getById(me.type).get('userAgent') + ,useragent: Ext.getStore('ServicesList').getById(me.record.get('type')).get('userAgent') } }; - if ( Ext.getStore('ServicesList').getById(me.type).get('allow_popups') ) cfg.autoEl.allowpopups = 'on'; + if ( Ext.getStore('ServicesList').getById(me.record.get('type')).get('allow_popups') ) cfg.autoEl.allowpopups = 'on'; } return cfg; } ,onBadgeTextChange: function( tab, badgeText, oldBadgeText ) { + var me = this; if ( oldBadgeText === null ) oldBadgeText = 0; var actualNotifications = Rambox.app.getTotalNotifications(); @@ -166,6 +177,31 @@ Ext.define('Rambox.ux.WebView',{ badgeText = Rambox.util.Format.stripNumber(badgeText); Rambox.app.setTotalNotifications(actualNotifications - oldBadgeText + badgeText); + + // Some services dont have Desktop Notifications, so we add that functionality =) + if ( Ext.getStore('ServicesList').getById(me.type).get('manual_notifications') && oldBadgeText < badgeText && me.record.get('notifications') && !JSON.parse(localStorage.getItem('dontDisturb')) ) { + var text; + switch ( Ext.getStore('ServicesList').getById(me.type).get('type') ) { + case 'messaging': + text = 'You have ' + Ext.util.Format.plural(badgeText, 'new message', 'new messages') + '.'; + break; + case 'email': + text = 'You have ' + Ext.util.Format.plural(badgeText, 'new email', 'new emails') + '.'; + break; + default: + text = 'You have ' + Ext.util.Format.plural(badgeText, 'new activity', 'new activities') + '.'; + break; + } + var not = new Notification(me.record.get('name'), { + body: text + ,icon: tab.icon + ,silent: me.record.get('muted') + }); + not.onclick = function() { + require('electron').remote.getCurrentWindow().show(); + Ext.cq1('app-main').setActiveTab(me); + }; + } } ,onAfterRender: function() { @@ -198,7 +234,6 @@ Ext.define('Rambox.ux.WebView',{ // Open links in default browser webview.addEventListener('new-window', function(e) { - console.log('new-window', e); switch ( me.type ) { case 'skype': // hack to fix multiple browser tabs on Skype link click, re #11 @@ -271,7 +306,7 @@ Ext.define('Rambox.ux.WebView',{ }); webview.addEventListener('did-get-redirect-request', function( e ) { - if ( e.isMainFrame ) Ext.defer(function() { webview.loadURL(e.newURL); }, 1000); + if ( e.isMainFrame ) webview.loadURL(e.newURL); }); } @@ -279,7 +314,10 @@ Ext.define('Rambox.ux.WebView',{ var me = this; var webview = me.down('component').el.dom; - if ( me.record.get('enabled') ) webview.loadURL(me.src); + if ( me.record.get('enabled') ) { + me.tab.setBadgeText(''); + webview.loadURL(me.src); + } } ,toggleDevTools: function(btn) { @@ -289,10 +327,21 @@ Ext.define('Rambox.ux.WebView',{ if ( me.record.get('enabled') ) webview.isDevToolsOpened() ? webview.closeDevTools() : webview.openDevTools(); } + ,setURL: function(url) { + var me = this; + var webview = me.down('component').el.dom; + + me.src = url; + + if ( me.record.get('enabled') ) webview.loadURL(url); + } + ,setAudioMuted: function(muted, calledFromDisturb) { var me = this; var webview = me.down('component').el.dom; + me.muted = muted; + if ( !muted && !calledFromDisturb && JSON.parse(localStorage.getItem('dontDisturb')) ) return; if ( me.record.get('enabled') ) webview.setAudioMuted(muted); @@ -302,6 +351,8 @@ Ext.define('Rambox.ux.WebView',{ var me = this; var webview = me.down('component').el.dom; + me.notifications = notification; + if ( notification && !calledFromDisturb && JSON.parse(localStorage.getItem('dontDisturb')) ) return; if ( me.record.get('enabled') ) ipc.send('setServiceNotifications', webview.partition, notification); @@ -310,12 +361,14 @@ Ext.define('Rambox.ux.WebView',{ ,setEnabled: function(enabled) { var me = this; + me.tab.setBadgeText(''); me.removeAll(); me.add(me.webViewConstructor(enabled)); if ( enabled ) { me.resumeEvent('afterrender'); me.show(); me.tab.setStyle('-webkit-filter', 'grayscale(0)'); + me.onAfterRender(); } else { me.suspendEvent('afterrender'); me.tab.setStyle('-webkit-filter', 'grayscale(1)'); diff --git a/app/view/add/Add.js b/app/view/add/Add.js new file mode 100644 index 00000000..8e32e81f --- /dev/null +++ b/app/view/add/Add.js @@ -0,0 +1,219 @@ +Ext.define('Rambox.view.add.Add',{ + extend: 'Ext.window.Window' + + ,requires: [ + 'Rambox.view.add.AddController' + ,'Rambox.view.add.AddModel' + ] + + ,controller: 'add-add' + ,viewModel: { + type: 'add-add' + } + + // private + ,record: null + ,service: null + ,edit: false + + // defaults + ,modal: true + ,width: 500 + ,autoShow: true + ,resizable: false + ,draggable: false + ,bodyPadding: 20 + + ,initComponent: function() { + var me = this; + + me.title = (!me.edit ? 'Add ' : 'Edit ') + me.record.get('name'); + me.icon = me.record.get('type') === 'custom' ? (!me.edit ? 'resources/icons/custom.png' : (me.record.get('logo') === '' ? 'resources/icons/custom.png' : me.record.get('logo'))) : 'resources/icons/'+me.record.get('logo'); + me.items = [ + { + xtype: 'form' + ,items: [ + { + xtype: 'textfield' + ,fieldLabel: 'Name' + ,value: me.record.get('type') === 'custom' ? (me.edit ? me.record.get('name') : '') : me.record.get('name') + ,name: 'serviceName' + ,allowBlank: true + ,listeners: { specialkey: 'onEnter' } + } + ,{ + xtype: 'container' + ,layout: 'column' + ,hidden: me.edit ? me.service.get('url').indexOf('___') === -1 && !me.service.get('custom_domain') : me.record.get('url').indexOf('___') === -1 && !me.record.get('custom_domain') + ,items: [ + { + xtype: 'textfield' + ,fieldLabel: 'URL' + ,name: 'url' + ,value: me.edit && me.service.get('url').indexOf('___') >= 0 ? me.record.get('url').replace(me.service.get('url').split('___')[0], '').replace(me.service.get('url').split('___')[1], '') : (me.record.get('url').indexOf('___') === -1 ? me.record.get('url') : '') + ,readOnly: me.edit ? (me.service.get('custom_domain') && me.service.get('url') === me.record.get('url') ? true : me.service.get('url').indexOf('___') === -1 && !me.service.get('custom_domain')) : me.record.get('url').indexOf('___') === -1 && me.record.get('custom_domain') + ,allowBlank: false + ,submitEmptyText: false + ,emptyText: me.record.get('url') === '___' ? 'http://' : '' + ,vtype: me.record.get('url') === '___' ? 'url' : '' + ,width: 275 + ,listeners: { specialkey: 'onEnter' } + } + ,{ + xtype: 'cycle' + ,showText: true + ,style: 'border-top-left-radius:0;border-bottom-left-radius:0;' + ,hidden: me.edit ? me.service.get('type') === 'custom' || me.service.get('url') === '___' : me.record.get('type') === 'custom' || me.record.get('url') === '___' + ,arrowVisible: me.edit ? (me.service.get('url').indexOf('___') >= 0 && !me.service.get('custom_domain') ? false : me.service.get('custom_domain')) : (me.record.get('url').indexOf('___') >= 0 && !me.record.get('custom_domain') ? false : me.record.get('custom_domain')) + ,menu: { + items: [ + { + text: me.edit ? (me.service.get('url').indexOf('___') === -1 ? 'Official Server' : Ext.String.endsWith(me.service.get('url'), '/') ? me.service.get('url').split('___')[1].slice(0, -1) : me.service.get('url').split('___')[1]) : (me.record.get('url').indexOf('___') === -1 ? 'Official Server' : Ext.String.endsWith(me.record.get('url'), '/') ? me.record.get('url').split('___')[1].slice(0, -1) : me.record.get('url').split('___')[1]) + ,checked: me.edit ? (me.service.get('custom_domain') && me.service.get('url') === me.record.get('url') ? true : Ext.String.endsWith(me.record.get('url'), me.service.get('url').split('___')[1])) : true + ,disabled: me.edit ? me.service.get('url') === '___' : me.record.get('url') === '___' + } + ,{ + text: 'Custom Server' + ,checked: me.edit ? (me.service.get('custom_domain') && me.service.get('url') === me.record.get('url') ? false : !Ext.String.endsWith(me.record.get('url'), me.service.get('url').split('___')[1])) : false + ,custom: true + ,disabled: me.edit ? !me.service.get('custom_domain') : !me.record.get('custom_domain') + } + ] + } + // Fixes bug EXTJS-20094 for version Ext JS 5 + ,arrowHandler: function(cycleBtn, e) { + if ( !cycleBtn.arrowVisible ) cycleBtn.hideMenu(); + } + ,changeHandler: function(cycleBtn, activeItem) { + Ext.apply(cycleBtn.previousSibling(), { + emptyText: activeItem.custom ? 'http://' : ' ' + ,vtype: activeItem.custom ? 'url' : '' + }); + cycleBtn.previousSibling().applyEmptyText(); + cycleBtn.previousSibling().reset(); + + if ( me.edit && cycleBtn.nextSibling().originalValue !== '2' ) { + me.service.get('custom_domain') && !activeItem.custom ? cycleBtn.previousSibling().reset() : cycleBtn.previousSibling().setValue(''); + } else if ( me.edit && cycleBtn.nextSibling().originalValue === '2' ) { + me.service.get('custom_domain') && !activeItem.custom ? cycleBtn.previousSibling().setValue( me.service.get('url').indexOf('___') === -1 && me.service.get('custom_domain') ? me.service.get('url') : '') : cycleBtn.previousSibling().reset(); + } else if ( !me.edit && cycleBtn.nextSibling().originalValue === '1' ) { + activeItem.custom ? cycleBtn.previousSibling().setValue('') : cycleBtn.previousSibling().reset(); + } + + cycleBtn.previousSibling().setReadOnly( activeItem.custom ? false : (me.edit ? me.service.get('url').indexOf('___') === -1 : me.record.get('url').indexOf('___') === -1) ); + cycleBtn.nextSibling().setValue( activeItem.custom ? 2 : 1 ); + } + } + ,{ + xtype: 'hiddenfield' + ,name: 'cycleValue' + ,value: me.edit ? (me.service.get('custom_domain') && me.service.get('url') === me.record.get('url') ? 1 : (!Ext.String.endsWith(me.record.get('url'), me.service.get('url').split('___')[1]) ? 2 : 1)) : 1 + } + ] + } + ,{ + xtype: 'textfield' + ,fieldLabel: 'Logo' + ,emptyText: 'http://url.com/image.png' + ,name: 'logo' + ,vtype: me.record.get('type') === 'custom' ? 'url' : '' + ,value: me.record.get('type') === 'custom' ? (me.edit ? me.record.get('logo') : '') : me.record.get('logo') + ,allowBlank: true + ,hidden: me.record.get('type') !== 'custom' + ,margin: '5 0 0 0' + ,listeners: { specialkey: 'onEnter' } + } + ,{ + xtype: 'fieldset' + ,title: 'Options' + ,margin: '10 0 0 0' + ,items: [ + { + xtype: 'checkbox' + ,boxLabel: 'Align to Right' + ,checked: me.edit ? (me.record.get('align') === 'right' ? true : false) : false + ,name: 'align' + ,uncheckedValue: 'left' + ,inputValue: 'right' + } + ,{ + xtype: 'checkbox' + ,boxLabel: 'Show notifications' + ,name: 'notifications' + ,checked: me.edit ? me.record.get('notifications') : true + ,uncheckedValue: false + ,inputValue: true + } + ,{ + xtype: 'checkbox' + ,boxLabel: 'Mute all sounds' + ,name: 'muted' + ,checked: me.edit ? me.record.get('muted') : false + ,uncheckedValue: false + ,inputValue: true + } + ,{ + xtype: 'checkbox' + ,boxLabel: 'Trust invalid authority certificates' + ,name: 'trust' + ,hidden: me.record.get('type') !== 'custom' + ,checked: me.edit ? me.record.get('trust') : false + ,uncheckedValue: false + ,inputValue: true + } + ] + } + ,{ + xtype: 'fieldset' + ,title: 'Advanced' + ,margin: '10 0 0 0' + ,collapsible: true + ,collapsed: true + ,items: [ + { + xtype: 'textarea' + ,fieldLabel: 'Custom Code (read more)' + ,allowBlank: true + ,name: 'js_unread' + ,value: me.edit ? me.record.get('js_unread') : '' + ,anchor: '100%' + ,height: 120 + } + ] + } + ,{ + xtype: 'container' + ,hidden: (me.edit ? Ext.getStore('ServicesList').getById(me.record.get('type')).get('note') === '' : me.record.get('note') === '') + ,data: { note: (me.edit ? Ext.getStore('ServicesList').getById(me.record.get('type')).get('note') : me.record.get('note')) } + ,margin: '10 0 0 0' + ,style: 'background-color:#93CFE0;color:#053767;border-radius:6px;' + ,tpl: [ + '' + ,'{note}' + ] + } + ] + } + ]; + + me.buttons = [ + { + text: 'Cancel' + ,ui: 'decline' + ,handler: 'doCancel' + } + ,'->' + ,{ + text: me.title + ,itemId: 'submit' + ,handler: 'doSave' + } + ]; + + this.callParent(this); + } + + ,listeners: { + show: 'onShow' + } +}); diff --git a/app/view/add/AddController.js b/app/view/add/AddController.js new file mode 100644 index 00000000..97ce0a73 --- /dev/null +++ b/app/view/add/AddController.js @@ -0,0 +1,125 @@ +Ext.define('Rambox.view.add.AddController', { + extend: 'Ext.app.ViewController' + ,alias: 'controller.add-add' + + ,doCancel: function( btn ) { + var me = this; + + me.getView().close(); + } + + ,doSave: function( btn ) { + var me = this; + + var win = me.getView(); + if ( !win.down('form').isValid() ) return false; + + var formValues = win.down('form').getValues(); + + if ( win.edit ) { + // Format data + if ( win.service.get('url').indexOf('___') >= 0 ) { + formValues.url = formValues.cycleValue === '1' ? win.service.get('url').replace('___', formValues.url) : formValues.url; + } + + var oldData = win.record.getData(); + win.record.set({ + logo: formValues.logo + ,name: formValues.serviceName + ,url: formValues.url + ,align: formValues.align + ,notifications: formValues.notifications + ,muted: formValues.muted + ,trust: formValues.trust + ,js_unread: formValues.js_unread + }); + // Change the title of the Tab + Ext.getCmp('tab_'+win.record.get('id')).setTitle(formValues.serviceName); + // Change sound of the Tab + Ext.getCmp('tab_'+win.record.get('id')).setAudioMuted(formValues.muted); + // Change notifications of the Tab + Ext.getCmp('tab_'+win.record.get('id')).setNotifications(formValues.notifications); + // Change the icon of the Tab + if ( win.record.get('type') === 'custom' && oldData.logo !== formValues.logo ) Ext.getCmp('tab_'+win.record.get('id')).setConfig('icon', formValues.logo === '' ? 'resources/icons/custom.png' : formValues.logo); + // Change the URL of the Tab + if ( oldData.url !== formValues.url ) Ext.getCmp('tab_'+win.record.get('id')).setURL(formValues.url); + // Change the align of the Tab + if ( oldData.align !== formValues.align ) { + if ( formValues.align === 'left' ) { + Ext.cq1('app-main').moveBefore(Ext.getCmp('tab_'+win.record.get('id')), Ext.getCmp('tbfill')); + } else { + Ext.cq1('app-main').moveAfter(Ext.getCmp('tab_'+win.record.get('id')), Ext.getCmp('tbfill')); + } + } + // Apply the JS Code of the Tab + if ( win.down('textarea').isDirty() ) { + Ext.Msg.confirm('CUSTOM CODE', 'Rambox needs to reload the service to execute the new JavaScript code. Do you want to do it now?', function( btnId ) { + if ( btnId === 'yes' ) Ext.getCmp('tab_'+win.record.get('id')).reloadService(); + }); + } + + Ext.getCmp('tab_'+win.record.get('id')).record = win.record; + Ext.getCmp('tab_'+win.record.get('id')).tabConfig.service = win.record; + } else { + // Format data + if ( win.record.get('url').indexOf('___') >= 0 ) { + formValues.url = formValues.cycleValue === '1' ? win.record.get('url').replace('___', formValues.url) : formValues.url; + } + + var service = Ext.create('Rambox.model.Service', { + type: win.record.get('id') + ,logo: formValues.logo + ,name: formValues.serviceName + ,url: formValues.url + ,align: formValues.align + ,notifications: formValues.notifications + ,muted: formValues.muted + ,trust: formValues.trust + ,js_unread: formValues.js_unread + }); + service.save(); + Ext.getStore('Services').add(service); + + var tabData = { + xtype: 'webview' + ,id: 'tab_'+service.get('id') + /* + ,title: service.get('name') + ,icon: service.get('logo') + ,src: service.get('url') + ,type: service.get('type') + ,align: formValues.align + ,notifications: formValues.notifications + ,muted: formValues.muted + */ + ,record: service + ,tabConfig: { + service: service + } + }; + + if ( formValues.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).show(); + } else { + Ext.cq1('app-main').add(tabData).show(); + } + } + + win.close(); + } + + ,onEnter: function(field, e) { + var me = this; + + if ( e.getKey() == e.ENTER && field.up('form').isValid() ) me.doSave(); + } + + ,onShow: function(win) { + var me = this; + + // Make focus to the name field + win.down('textfield[name="serviceName"]').focus(true, 100); + } + +}); diff --git a/app/view/add/AddModel.js b/app/view/add/AddModel.js new file mode 100644 index 00000000..7dd56ad9 --- /dev/null +++ b/app/view/add/AddModel.js @@ -0,0 +1,4 @@ +Ext.define('Rambox.view.add.AddModel', { + extend: 'Ext.app.ViewModel' + ,alias: 'viewmodel.add-add' +}); diff --git a/app/view/main/Main.js b/app/view/main/Main.js index 82dcbc9b..7fc1f123 100644 --- a/app/view/main/Main.js +++ b/app/view/main/Main.js @@ -5,6 +5,7 @@ Ext.define('Rambox.view.main.Main', { ,'Rambox.view.main.MainModel' ,'Rambox.ux.WebView' ,'Rambox.ux.mixin.Badge' + ,'Rambox.view.add.Add' ,'Ext.ux.TabReorderer' ] @@ -290,16 +291,18 @@ Ext.define('Rambox.view.main.Main', { ,allowToggle: false ,items: [ { - text: 'Donate with' - ,overCls: '' + text: 'Help us with' + ,pressed: true } ,{ - glyph: 'xf1ed@FontAwesome' - ,href: 'https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=WU75QWS7LH2CA' + text: 'Donation' + ,glyph: 'xf21e@FontAwesome' + ,handler: 'showDonate' } ,{ - glyph: 'xf15a@FontAwesome' - ,href: 'https://www.coinbase.com/saenzramiro' + text: 'Translation' + ,glyph: 'xf0ac@FontAwesome' + ,href: 'https://crowdin.com/project/rambox/invite' } ] } @@ -310,19 +313,26 @@ Ext.define('Rambox.view.main.Main', { } ,'->' ,{ - glyph: 'xf082@FontAwesome' - ,tooltip: 'Facebook' - ,href: 'https://www.facebook.com/ramboxapp' - } - ,{ - glyph: 'xf099@FontAwesome' - ,tooltip: 'Twitter' - ,href: 'https://www.twitter.com/ramboxapp' - } - ,{ - glyph: 'xf09b@FontAwesome' - ,tooltip: 'GitHub' - ,href: 'https://www.github.com/saenzramiro/rambox' + xtype: 'segmentedbutton' + ,allowToggle: false + ,items: [ + { + text: 'Follow us' + ,pressed: true + } + ,{ + glyph: 'xf082@FontAwesome' + ,href: 'https://www.facebook.com/ramboxapp' + } + ,{ + glyph: 'xf099@FontAwesome' + ,href: 'https://www.twitter.com/ramboxapp' + } + ,{ + glyph: 'xf09b@FontAwesome' + ,href: 'https://www.github.com/saenzramiro/rambox' + } + ] } ] } diff --git a/app/view/main/MainController.js b/app/view/main/MainController.js index 27adee6c..b4b6519d 100644 --- a/app/view/main/MainController.js +++ b/app/view/main/MainController.js @@ -45,395 +45,6 @@ Ext.define('Rambox.view.main.MainController', { Ext.getCmp('tab_'+e.record.get('id')).setTitle(e.record.get('name')); } - ,showSimpleModal: function(record, edit) { - var me = this; - - var win = Ext.create('Ext.window.Window', { - title: (edit ? 'Edit ' : 'Add ') + record.get('name') - ,modal: true - ,width: 400 - ,resizable: false - ,draggable: false - ,bodyPadding: 20 - ,icon: 'resources/icons/' + record.get('logo') - ,items: [ - { - xtype: 'form' - ,items: [ - { - xtype: 'textfield' - ,fieldLabel: 'Name' - ,value: record.get('name') - ,name: 'serviceName' - ,allowBlank: true - ,listeners: { - specialkey: function(field, e) { - if(e.getKey() == e.ENTER && field.up('form').isValid()) { - field.up('window').down('#submit').handler(); - } - } - } - } - ,{ - xtype: 'fieldset' - ,title: 'Options' - ,margin: '10 0 0 0' - ,items: [ - { - xtype: 'checkbox' - ,boxLabel: 'Align to Right' - ,checked: edit ? (record.get('align') === 'right' ? true : false) : false - ,name: 'align' - ,uncheckedValue: 'left' - ,inputValue: 'right' - } - ,{ - xtype: 'checkbox' - ,boxLabel: 'Show notifications' - ,name: 'notifications' - ,checked: edit ? record.get('notifications') : true - ,uncheckedValue: false - ,inputValue: true - } - ,{ - xtype: 'checkbox' - ,boxLabel: 'Mute all sounds' - ,name: 'muted' - ,checked: edit ? record.get('muted') : false - ,uncheckedValue: false - ,inputValue: true - } - ] - } - ,{ - xtype: 'fieldset' - ,title: 'Advanced' - ,margin: '10 0 0 0' - ,collapsible: true - ,collapsed: true - ,items: [ - { - xtype: 'textarea' - ,fieldLabel: 'Custom Code (read more)' - ,allowBlank: true - ,name: 'js_unread' - ,value: edit ? record.get('js_unread') : '' - ,anchor: '100%' - ,height: 120 - } - ] - } - ,{ - xtype: 'container' - ,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;border-radius:6px;' - ,tpl: [ - '' - ,'{note}' - ] - } - ] - } - ] - ,buttons: [ - { - text: 'Cancel' - ,ui: 'decline' - ,handler: function() { - win.close(); - } - } - ,'->' - ,{ - text: edit ? 'Save' : 'Add service' - ,itemId: 'submit' - ,handler: function() { - if ( !win.down('form').isValid() ) return false; - - var formValues = win.down('form').getValues(); - - if ( edit ) { - var oldData = record.getData(); - record.set({ - name: formValues.serviceName - ,align: formValues.align - ,notifications: formValues.notifications - ,muted: formValues.muted - ,js_unread: formValues.js_unread - }); - // Change the title of the Tab - Ext.getCmp('tab_'+record.get('id')).setTitle(formValues.serviceName); - // Change sound of the Tab - Ext.getCmp('tab_'+record.get('id')).setAudioMuted(formValues.muted); - // Change notifications of the Tab - Ext.getCmp('tab_'+record.get('id')).setNotifications(formValues.notifications); - // Change the align of the Tab - if ( oldData.align !== formValues.align ) { - if ( formValues.align === 'left' ) { - me.getView().moveBefore(Ext.getCmp('tab_'+record.get('id')), Ext.getCmp('tbfill')); - } else { - me.getView().moveAfter(Ext.getCmp('tab_'+record.get('id')), Ext.getCmp('tbfill')); - } - } - - Ext.getCmp('tab_'+record.get('id')).record = record; - } else { - var service = Ext.create('Rambox.model.Service', { - type: record.get('id') - ,logo: record.get('logo') - ,name: formValues.serviceName - ,url: record.get('url') - ,align: formValues.align - ,notifications: formValues.notifications - ,muted: formValues.muted - ,js_unread: formValues.js_unread - }); - service.save(); - Ext.getStore('Services').add(service); - - var tabData = { - xtype: 'webview' - ,id: 'tab_'+service.get('id') - ,title: service.get('name') - ,icon: 'resources/icons/'+service.get('logo') - ,src: service.get('url') - ,type: service.get('type') - ,align: formValues.align - ,notifications: formValues.notifications - ,muted: formValues.muted - ,record: service - ,tabConfig: { - service: service - } - }; - - if ( formValues.align === 'left' ) { - var tbfill = me.getView().getTabBar().down('tbfill'); - me.getView().insert(me.getView().getTabBar().items.indexOf(tbfill), tabData).show(); - } else { - me.getView().add(tabData).show(); - } - } - - win.close(); - } - } - ] - }).show(); - - // Make focus to the name field - win.down('textfield[name="serviceName"]').focus(true, 100); - } - - ,showCustomModal: function(record, edit) { - var me = this; - - var win = Ext.create('Ext.window.Window', { - title: (edit ? 'Edit ' : 'Add ') + record.get('name') - ,modal: true - ,width: 400 - ,resizable: false - ,draggable: false - ,bodyPadding: 20 - ,icon: 'resources/icons/' + record.get('logo') - ,items: [ - { - xtype: 'form' - ,items: [ - { - xtype: 'textfield' - ,fieldLabel: 'Name' - ,value: record.get('name') - ,name: 'serviceName' - ,allowBlank: true - ,listeners: { - specialkey: function(field, e) { - if(e.getKey() == e.ENTER && field.up('form').isValid()) { - field.up('window').down('#submit').handler(); - } - } - } - } - ,{ - xtype: 'container' - ,layout: 'column' - ,items: [{ - xtype: 'textfield' - ,fieldLabel: record.get('name') + ' team' - ,name: 'url' - ,allowBlank: false - ,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()) { - field.up('window').down('#submit').handler(); - } - } - } - },{ - xtype: 'displayfield' - ,value: record.get('url').split('___')[1].slice(0, -1) // Get the URL and remove the final slash (/) - ,submitValue: false // Prevent being submitted - }] - } - ,{ - xtype: 'fieldset' - ,title: 'Options' - ,margin: '10 0 0 0' - ,items: [ - { - xtype: 'checkbox' - ,boxLabel: 'Align to Right' - ,checked: edit ? (record.get('align') === 'right' ? true : false) : false - ,name: 'align' - ,uncheckedValue: 'left' - ,inputValue: 'right' - } - ,{ - xtype: 'checkbox' - ,boxLabel: 'Show notifications' - ,name: 'notifications' - ,checked: edit ? record.get('notifications') : true - ,uncheckedValue: false - ,inputValue: true - } - ,{ - xtype: 'checkbox' - ,boxLabel: 'Mute all sounds' - ,name: 'muted' - ,checked: edit ? record.get('muted') : false - ,uncheckedValue: false - ,inputValue: true - } - ] - } - ,{ - xtype: 'fieldset' - ,title: 'Advanced' - ,margin: '10 0 0 0' - ,collapsible: true - ,collapsed: true - ,items: [ - { - xtype: 'textarea' - ,fieldLabel: 'Custom Code (read more)' - ,allowBlank: true - ,name: 'js_unread' - ,value: edit ? record.get('js_unread') : '' - ,anchor: '100%' - ,height: 120 - } - ] - } - ,{ - xtype: 'container' - ,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;border-radius:6px;' - ,tpl: [ - '' - ,'{note}' - ] - } - ] - } - ] - ,buttons: [ - { - text: 'Cancel' - ,ui: 'decline' - ,handler: function() { - win.close(); - } - } - ,'->' - ,{ - text: edit ? 'Save' : 'Add service' - ,itemId: 'submit' - ,handler: function() { - if ( !win.down('form').isValid() ) return false; - - var formValues = win.down('form').getValues(); - - if ( edit ) { - var oldData = record.getData(); - record.set({ - name: formValues.serviceName - ,align: formValues.align - ,notifications: formValues.notifications - ,muted: formValues.muted - ,js_unread: formValues.js_unread - }); - // Change the title of the Tab - Ext.getCmp('tab_'+record.get('id')).setTitle(formValues.serviceName); - // Change sound of the Tab - Ext.getCmp('tab_'+record.get('id')).setAudioMuted(formValues.muted); - // Change notifications of the Tab - Ext.getCmp('tab_'+record.get('id')).setNotifications(formValues.notifications); - // Change the align of the Tab - if ( oldData.align !== formValues.align ) { - if ( formValues.align === 'left' ) { - me.getView().moveBefore(Ext.getCmp('tab_'+record.get('id')), Ext.getCmp('tbfill')); - } else { - me.getView().moveAfter(Ext.getCmp('tab_'+record.get('id')), Ext.getCmp('tbfill')); - } - } - - Ext.getCmp('tab_'+record.get('id')).record = record; - } else { - var service = Ext.create('Rambox.model.Service', { - type: record.get('id') - ,logo: record.get('logo') - ,name: formValues.serviceName - ,url: record.get('url').replace('___', formValues.url) - ,align: formValues.align - ,notifications: formValues.notifications - ,muted: formValues.muted - ,js_unread: formValues.js_unread - }); - service.save(); - Ext.getStore('Services').add(service); - - var tabData = { - xtype: 'webview' - ,id: 'tab_'+service.get('id') - ,title: service.get('name') - ,icon: 'resources/icons/'+service.get('logo') - ,src: service.get('url') - ,type: service.get('type') - ,align: formValues.align - ,notifications: formValues.notifications - ,muted: formValues.muted - ,record: service - ,tabConfig: { - service: service - } - }; - - if ( formValues.align === 'left' ) { - var tbfill = me.getView().getTabBar().down('tbfill'); - me.getView().insert(me.getView().getTabBar().items.indexOf(tbfill), tabData).show(); - } else { - me.getView().add(tabData).show(); - } - } - - win.close(); - } - } - ] - }).show(); - - // Make focus to the name field - win.down('textfield[name="serviceName"]').focus(true, 100); - } - ,onEnableDisableService: function(cc, rowIndex, checked) { var rec = Ext.getStore('Services').getAt(rowIndex); @@ -441,13 +52,9 @@ Ext.define('Rambox.view.main.MainController', { } ,onNewServiceSelect: function( view, record, item, index, e ) { - if ( record.get('url').indexOf('___') >= 0 ) { - this.showCustomModal(record); - } else if ( record.get('type') === 'custom' ) { - this.addCustomService(record, false); - } else { - this.showSimpleModal(record, false); - } + Ext.create('Rambox.view.add.Add', { + record: record + }); } ,removeServiceFn: function(serviceId) { @@ -459,7 +66,7 @@ Ext.define('Rambox.view.main.MainController', { var rec = Ext.getStore('Services').getById(serviceId); // Clear all trash data - if ( rec.get('enabled') ) { + if ( rec.get('enabled') && tab.down('component').el ) { tab.down('component').el.dom.getWebContents().session.clearCache(Ext.emptyFn); tab.down('component').el.dom.getWebContents().session.clearStorageData({}, Ext.emptyFn); } @@ -495,6 +102,7 @@ Ext.define('Rambox.view.main.MainController', { Ext.getStore('Services').load(); if ( Ext.isFunction(callback) ) callback(); Ext.cq1('app-main').resumeEvent('remove'); + document.title = 'Rambox'; } }); } else { @@ -505,236 +113,16 @@ Ext.define('Rambox.view.main.MainController', { Ext.getStore('Services').load(); if ( Ext.isFunction(callback) ) callback(); Ext.cq1('app-main').resumeEvent('remove'); + document.title = 'Rambox'; } } ,configureService: function( gridView, rowIndex, colIndex, col, e, rec, rowEl ) { - if ( rec.get('type') === 'custom' ) { - this.addCustomService(rec, true); - } else { - this.showSimpleModal(rec, true); - } - } - - ,addCustomService: function( record, edit ) { - var me = this; - - var win = Ext.create('Ext.window.Window', { - title: (edit ? 'Edit ' : 'Add ') + 'Custom Service' - ,modal: true - ,width: 400 - ,resizable: false - ,draggable: false - ,bodyPadding: 20 - ,items: [ - { - xtype: 'form' - ,items: [ - { - xtype: 'textfield' - ,fieldLabel: 'Name' - ,name: 'serviceName' - ,value: (edit ? record.get('name') : '') - ,allowBlank: true - ,listeners: { - specialkey: function(field, e) { - if(e.getKey() == e.ENTER && field.up('form').isValid()) { - field.up('window').down('#submit').handler(); - } - } - } - } - ,{ - xtype: 'textfield' - ,fieldLabel: 'URL' - ,emptyText: 'http://service.url.com' - ,name: 'url' - ,vtype: 'url' - ,value: (edit ? record.get('url') : '') - ,allowBlank: false - ,listeners: { - specialkey: function(field, e) { - if(e.getKey() == e.ENTER && field.up('form').isValid()) { - field.up('window').down('#submit').handler(); - } - } - } - } - ,{ - xtype: 'textfield' - ,fieldLabel: 'Logo' - ,emptyText: 'http://image.url.com/image.png' - ,name: 'logo' - ,vtype: 'url' - ,value: (edit ? record.get('logo') : '') - ,allowBlank: true - ,listeners: { - specialkey: function(field, e) { - if(e.getKey() == e.ENTER && field.up('form').isValid()) { - field.up('window').down('#submit').handler(); - } - } - } - } - ,{ - xtype: 'fieldset' - ,title: 'Options' - ,margin: '10 0 0 0' - ,items: [ - { - xtype: 'checkbox' - ,boxLabel: 'Align to Right' - ,checked: edit ? (record.get('align') === 'right' ? true : false) : false - ,name: 'align' - ,uncheckedValue: 'left' - ,inputValue: 'right' - } - ,{ - xtype: 'checkbox' - ,boxLabel: 'Show notifications' - ,name: 'notifications' - ,checked: edit ? record.get('notifications') : true - ,uncheckedValue: false - ,inputValue: true - } - ,{ - xtype: 'checkbox' - ,boxLabel: 'Mute all sounds' - ,name: 'muted' - ,checked: edit ? record.get('muted') : false - ,uncheckedValue: false - ,inputValue: true - } - ,{ - xtype: 'checkbox' - ,boxLabel: 'Trust invalid authority certificates' - ,name: 'trust' - ,checked: edit ? record.get('trust') : false - ,uncheckedValue: false - ,inputValue: true - } - ] - } - ,{ - xtype: 'fieldset' - ,title: 'Advanced' - ,margin: '10 0 0 0' - ,collapsible: true - ,collapsed: true - ,items: [ - { - xtype: 'textarea' - ,fieldLabel: 'Custom Code (read more)' - ,allowBlank: true - ,name: 'js_unread' - ,value: (edit ? record.get('js_unread') : '') - ,anchor: '100%' - ,height: 120 - } - ] - } - ] - } - ] - ,buttons: [ - { - text: 'Cancel' - ,ui: 'decline' - ,handler: function() { - win.close(); - } - } - ,'->' - ,{ - text: (edit ? 'Edit ' : 'Add ') + ' Service' - ,itemId: 'submit' - ,handler: function() { - if ( !win.down('form').isValid() ) return false; - - var formValues = win.down('form').getValues(); - - if ( edit ) { - var oldData = record.getData(); - // If users change the URL, we change the URL of the Webview - if ( record.get('url') !== formValues.url ) Ext.getCmp('tab_'+record.get('id')).down('component').el.dom.loadURL(formValues.url); - - // Save the service - record.set({ - name: formValues.serviceName - ,url: formValues.url - ,logo: formValues.logo - ,align: formValues.align - ,notifications: formValues.notifications - ,muted: formValues.muted - ,trust: formValues.trust - ,js_unread: formValues.js_unread - }); - - // Change the title of the Tab - Ext.getCmp('tab_'+record.get('id')).setTitle(formValues.serviceName); - // Change sound of the Tab - Ext.getCmp('tab_'+record.get('id')).setAudioMuted(formValues.muted); - // Change notifications of the Tab - Ext.getCmp('tab_'+record.get('id')).setNotifications(formValues.notifications); - // Change the icon of the Tab - Ext.getCmp('tab_'+record.get('id')).setIcon(record.get('logo') === '' ? 'resources/icons/custom.png' : record.get('logo')); - // Change the align of the Tab - if ( oldData.align !== formValues.align ) { - if ( formValues.align === 'left' ) { - me.getView().moveBefore(Ext.getCmp('tab_'+record.get('id')), Ext.getCmp('tbfill')); - } else { - me.getView().moveAfter(Ext.getCmp('tab_'+record.get('id')), Ext.getCmp('tbfill')); - } - } - - Ext.getCmp('tab_'+record.get('id')).record = record; - } else { - var service = Ext.create('Rambox.model.Service', { - type: 'custom' - ,logo: formValues.logo - ,name: formValues.serviceName - ,url: formValues.url - ,align: formValues.align - ,notifications: formValues.notifications - ,muted: formValues.muted - ,trust: formValues.trust - ,js_unread: formValues.js_unread - }); - service.save(); - Ext.getStore('Services').add(service); - - var tabData = { - xtype: 'webview' - ,id: 'tab_'+service.get('id') - ,title: service.get('name') - ,icon: service.get('logo') === '' ? 'resources/icons/custom.png' : service.get('logo') - ,src: service.get('url') - ,type: service.get('type') - ,align: formValues.align - ,notifications: formValues.notifications - ,muted: formValues.muted - ,record: service - ,tabConfig: { - service: service - } - }; - - if ( formValues.align === 'left' ) { - var tbfill = me.getView().getTabBar().down('tbfill'); - me.getView().insert(me.getView().getTabBar().items.indexOf(tbfill), tabData).show(); - } else { - me.getView().add(tabData).show(); - } - } - - win.close(); - } - } - ] - }).show(); - - // Make focus to the name field - win.down('textfield[name="serviceName"]').focus(true, 100); + Ext.create('Rambox.view.add.Add', { + record: rec + ,service: Ext.getStore('ServicesList').getById(rec.get('type')) + ,edit: true + }); } ,onSearchRender: function( field ) { @@ -988,4 +376,8 @@ Ext.define('Rambox.view.main.MainController', { logoutFn(); } } + + ,showDonate: function( btn ) { + Tooltip.API.show('zxzKWZfcmgRtHXgth'); + } }); diff --git a/app/view/preferences/Preferences.js b/app/view/preferences/Preferences.js index bdb63b95..749340a1 100644 --- a/app/view/preferences/Preferences.js +++ b/app/view/preferences/Preferences.js @@ -59,7 +59,7 @@ Ext.define('Rambox.view.preferences.Preferences',{ ,name: 'hide_menu_bar' ,boxLabel: 'Auto-hide Menu bar (Alt key to display)' ,value: config.hide_menu_bar - ,hidden: Ext.os.is.MacOS + ,hidden: process.platform !== 'win32' } ,{ xtype: 'checkbox' @@ -67,7 +67,7 @@ Ext.define('Rambox.view.preferences.Preferences',{ ,boxLabel: 'Show in Taskbar' ,value: config.skip_taskbar ,reference: 'skipTaskbar' - ,hidden: Ext.os.is.MacOS + ,hidden: process.platform !== 'win32' } ,{ xtype: 'checkbox' @@ -75,7 +75,7 @@ Ext.define('Rambox.view.preferences.Preferences',{ ,boxLabel: 'Keep Rambox in the Taskbar when close it' ,value: config.keep_in_taskbar_on_close ,bind: { disabled: '{!skipTaskbar.checked}' } - ,hidden: Ext.os.is.MacOS + ,hidden: process.platform !== 'win32' } ,{ xtype: 'checkbox' @@ -88,7 +88,13 @@ Ext.define('Rambox.view.preferences.Preferences',{ ,name: 'systemtray_indicator' ,boxLabel: 'Show System Tray indicator on unread messages' ,value: config.systemtray_indicator - ,hidden: Ext.os.is.MacOS + ,hidden: process.platform === 'darwin' + } + ,{ + xtype: 'checkbox' + ,name: 'disable_gpu' + ,boxLabel: 'Disable Hardware Acceleration (needs to relaunch)' + ,value: config.disable_gpu } ,{ xtype: 'fieldset' diff --git a/appveyor.yml b/appveyor.yml index 71d23b32..d30d971d 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,4 +1,4 @@ -version: 0.4.2 +version: 0.4.4 pull_requests: do_not_increment_build_number: true branches: @@ -33,12 +33,13 @@ build_script: npm --version - node_modules/.bin/build --win --x64 + node_modules/.bin/build --win --ia32 --x64 test: off artifacts: - path: dist\win\*.exe - path: dist\win\*.nupkg - path: dist\win\RELEASES +- path: dist\win-ia32\*.exe - path: dist\*.zip deploy: - provider: GitHub diff --git a/electron/main.js b/electron/main.js index 0a97b502..b066129a 100644 --- a/electron/main.js +++ b/electron/main.js @@ -24,10 +24,12 @@ const config = new Config({ ,hide_menu_bar: false ,skip_taskbar: true ,auto_launch: !isDev - ,keep_in_taskbar_on_close: getDefaultValueForkeep_in_taskbar_on_close() + // On Linux false because it's uncommon for apps on linux to stay in the taskbar on close + ,keep_in_taskbar_on_close: process.platform !== 'linux' ,start_minimized: false ,systemtray_indicator: true ,master_password: false + ,disable_gpu: process.platform === 'linux' ,proxy: false ,proxyHost: '' ,proxyPort: '' @@ -40,15 +42,6 @@ const config = new Config({ } }); -/** - * Returns the default value for "keep_in_taskbar_on_close". - * On all platforms except linux: true - * On linux: false (because it's uncommon for apps on linux to stay in the taskbar on close) - */ -function getDefaultValueForkeep_in_taskbar_on_close() { - return process.platform !== 'linux'; -} - // Configure AutoLaunch const appLauncher = new AutoLaunch({ name: 'Rambox' @@ -232,6 +225,9 @@ function createMasterPasswordWindow() { backgroundColor: '#0675A0' ,frame: false }); + // Open the DevTools. + if ( isDev ) mainMasterPasswordWindow.webContents.openDevTools(); + mainMasterPasswordWindow.loadURL('file://' + __dirname + '/../masterpassword.html'); mainMasterPasswordWindow.on('close', function() { mainMasterPasswordWindow = null }); } @@ -319,10 +315,10 @@ app.on('certificate-error', function(event, webContents, url, error, certificate } else { callback(false); dialog.showMessageBox(mainWindow, { - title: 'Certification Error' + title: 'Certification Warning' ,message: 'The service with the following URL has an invalid authority certification.\n\n'+url+'\n\nIf is a Custom Service, you have to remove it and add it again, enabling the "Trust invalid authority certificates" in the Options.' ,buttons: ['OK'] - ,type: 'error' + ,type: 'warning' }, function() { }); @@ -394,6 +390,12 @@ ipcMain.on('image:popup', function(event, url, partition) { // Proxy if ( config.get('proxy') ) app.commandLine.appendSwitch('proxy-server', config.get('proxyHost')+':'+config.get('proxyPort')); +// Disable GPU Acceleration for Linux +// to prevent White Page bug +// https://github.com/electron/electron/issues/6139 +// https://github.com/saenzramiro/rambox/issues/181 +if ( config.get('disable_gpu') ) app.disableHardwareAcceleration(); + // This method will be called when Electron has finished // initialization and is ready to create browser windows. app.on('ready', function() { @@ -409,12 +411,14 @@ app.on('window-all-closed', function () { } }); +// Only macOS: On OS X it's common to re-create a window in the app when the +// dock icon is clicked and there are no other windows open. app.on('activate', function () { - // On OS X it's common to re-create a window in the app when the - // dock icon is clicked and there are no other windows open. if (mainWindow === null && mainMasterPasswordWindow === null ) { config.get('master_password') ? createMasterPasswordWindow() : createWindow(); } + + if ( mainWindow !== null ) mainWindow.show(); }); app.on('before-quit', function () { diff --git a/electron/menu.js b/electron/menu.js index 8d6847b3..06f693b7 100644 --- a/electron/menu.js +++ b/electron/menu.js @@ -67,6 +67,29 @@ const helpSubmenu = [ shell.openExternal('https://gitter.im/saenzramiro/rambox'); } }, + { + label: `Tools`, + submenu: [ + { + label: `Clear Cache`, + click(item, win) { + win.webContents.session.clearCache(function() { + win.reload(); + }); + } + }, + { + label: `Clear Local Storage`, + click(item, win) { + win.webContents.session.clearStorageData({ + storages: ['localstorage'] + }, function() { + win.reload(); + }); + } + } + ] + }, { type: 'separator' }, @@ -121,6 +144,13 @@ let tpl = [ if (focusedWindow) focusedWindow.reload(); } }, + { + label: 'Reload current Service', + accelerator: 'CmdOrCtrl+Shift+R', + click() { + sendAction('reloadCurrentService'); + } + }, { type: 'separator' }, diff --git a/electron/tray.js b/electron/tray.js index 78547dc7..dbe0c69e 100644 --- a/electron/tray.js +++ b/electron/tray.js @@ -51,7 +51,7 @@ exports.create = function(win, config) { appIcon.setToolTip('Rambox'); appIcon.setContextMenu(contextMenu); appIcon.on('double-click', () => { - win.isVisible() && config.get('maximized') ? win.maximize() : win.show(); + if ( !win.isVisible() || win.isMinimized() ) config.get('maximized') ? win.maximize() : win.show(); }); }; diff --git a/index.html b/index.html index 22c60550..f315b2d1 100644 --- a/index.html +++ b/index.html @@ -21,6 +21,14 @@ -
+
+
+
diff --git a/masterpassword.html b/masterpassword.html index 264f30ee..c09c3eb0 100644 --- a/masterpassword.html +++ b/masterpassword.html @@ -9,19 +9,22 @@
Master Password
-
+
+
Exit Rambox
diff --git a/package.json b/package.json index 838ddba2..7a999965 100644 --- a/package.json +++ b/package.json @@ -95,7 +95,7 @@ "asar": "^0.12.1", "bestzip": "^1.1.3", "electron-builder": "6.5.2", - "electron-prebuilt": "1.3.5", + "electron-prebuilt": "1.4.1", "electron-squirrel-startup": "^1.0.0" }, "dependencies": { diff --git a/packages/local/rambox-default-theme/resources/fonts/icomoon/icomoon.eot b/packages/local/rambox-default-theme/resources/fonts/icomoon/icomoon.eot new file mode 100644 index 00000000..0d9f71a5 Binary files /dev/null and b/packages/local/rambox-default-theme/resources/fonts/icomoon/icomoon.eot differ diff --git a/packages/local/rambox-default-theme/resources/fonts/icomoon/icomoon.svg b/packages/local/rambox-default-theme/resources/fonts/icomoon/icomoon.svg new file mode 100644 index 00000000..360b0b0a --- /dev/null +++ b/packages/local/rambox-default-theme/resources/fonts/icomoon/icomoon.svg @@ -0,0 +1,21 @@ + + + +Generated by IcoMoon + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/local/rambox-default-theme/resources/fonts/icomoon/icomoon.ttf b/packages/local/rambox-default-theme/resources/fonts/icomoon/icomoon.ttf new file mode 100644 index 00000000..fc7e5642 Binary files /dev/null and b/packages/local/rambox-default-theme/resources/fonts/icomoon/icomoon.ttf differ diff --git a/packages/local/rambox-default-theme/resources/fonts/icomoon/icomoon.woff b/packages/local/rambox-default-theme/resources/fonts/icomoon/icomoon.woff new file mode 100644 index 00000000..5b3c470a Binary files /dev/null and b/packages/local/rambox-default-theme/resources/fonts/icomoon/icomoon.woff differ diff --git a/packages/local/rambox-default-theme/sass/etc/_loadscreen.scss b/packages/local/rambox-default-theme/sass/etc/_loadscreen.scss new file mode 100644 index 00000000..dddb9092 --- /dev/null +++ b/packages/local/rambox-default-theme/sass/etc/_loadscreen.scss @@ -0,0 +1,229 @@ +/* Main component wrapper */ +body { + overflow: hidden; +} +.component { + position: absolute;; + z-index: 1; + width: 200px; + height: 200px; + margin: -100px 0 0 -100px; + top: 50%; + left: 50%; +} + + +/* Actual buttons (laid over shapes) */ + +.button { + font-weight: bold; + position: absolute; + bottom: 4px; + top: 50%; + left: 50%; + width: 200px; + height: 200px; + margin: -100px 0 0 -100px; + padding: 0; + text-align: center; + color: #00a7e7; + border: none; + background: none; + -webkit-transition: opacity 0.3s; + transition: opacity 0.3s; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); +} + +.button:hover, +.button:focus { + outline: none; + color: #048abd; +} + +.button--listen { + pointer-events: none; +} + +.button--close { + z-index: 10; + top: 0px; + right: 0px; + left: auto; + width: 40px; + height: 40px; + padding: 10px; + color: #fff; +} + +.button--close:hover, +.button--close:focus { + color: #ddd; +} + +.button--hidden { + pointer-events: none; + opacity: 0; +} + + +/* Inner content of the start/*/ + +.button__content { + position: absolute; + opacity: 0; + -webkit-transition: -webkit-transform 0.4s, opacity 0.4s; + transition: transform 0.4s, opacity 0.4s; +} + +.button__content--listen { + font-size: 1.75em; + line-height: 64px; + bottom: 0; + left: 50%; + width: 60px; + height: 60px; + margin: 0 0 0 -30px; + border-radius: 50%; + -webkit-transform: translate3d(0, 25px, 0); + transform: translate3d(0, 25px, 0); + -webkit-transition-timing-function: cubic-bezier(0.8, 0, 0.2, 1); + transition-timing-function: cubic-bezier(0.8, 0, 0.2, 1); +} + +.button__content--listen::before, +.button__content--listen::after { + content: ''; + position: absolute; + left: 0; + width: 100%; + height: 100%; + pointer-events: none; + opacity: 0; + border: 1px solid rgba(255, 255, 255, 0.2); + border-radius: 50%; +} + +.button--animate .button__content--listen::before, +.button--animate .button__content--listen::after { + -webkit-animation: anim-ripple 1.2s ease-out infinite forwards; + animation: anim-ripple 1.2s ease-out infinite forwards; +} + +.button--animate .button__content--listen::after { + -webkit-animation-delay: 0.6s; + animation-delay: 0.6s; +} + +@-webkit-keyframes anim-ripple { + 0% { + opacity: 0; + -webkit-transform: scale3d(3, 3, 1); + transform: scale3d(3, 3, 1); + } + 50% { + opacity: 1; + } + 100% { + opacity: 0; + -webkit-transform: scale3d(1, 1, 1); + transform: scale3d(1, 1, 1); + } +} + +@keyframes anim-ripple { + 0% { + opacity: 0; + -webkit-transform: scale3d(3, 3, 1); + transform: scale3d(3, 3, 1); + } + 50% { + opacity: 1; + } + 100% { + opacity: 0; + -webkit-transform: scale3d(1, 1, 1); + transform: scale3d(1, 1, 1); + } +} + +.notes { + position: absolute; + z-index: -1; + bottom: 0; + left: 50%; + width: 200px; + height: 100px; + margin: 0 0 0 -100px; +} + +.note { + font-size: 2.8em; + position: absolute; + left: 50%; + width: 1em; + margin: 0 0 0 -0.5em; + opacity: 0; + color: rgba(255, 255, 255, 0.75); +} + +.note:nth-child(odd) { + color: rgba(0, 0, 0, 0.1); +} + +.note:nth-child(4n) { + font-size: 2em; +} + +.note:nth-child(6n) { + color: rgba(255, 255, 255, 0.3); +} + +/* ICONS */ +@font-face { + font-family: 'icomoon'; + src:url('../resources/fonts/icomoon/icomoon.eot?4djz1y'); + src:url('../resources/fonts/icomoon/icomoon.eot?4djz1y#iefix') format('embedded-opentype'), + url('../resources/fonts/icomoon/icomoon.ttf?4djz1y') format('truetype'), + url('../resources/fonts/icomoon/icomoon.woff?4djz1y') format('woff'), + url('../resources/fonts/icomoon/icomoon.svg?4djz1y#icomoon') format('svg'); + font-weight: normal; + font-style: normal; +} + +.icon { + font-family: 'icomoon'; + speak: none; + font-style: normal; + font-weight: normal; + font-variant: normal; + text-transform: none; + line-height: 1; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + + +.icon--microphone:before { + content: "\ea95"; +} +.icon--cross:before { + content: "\e90c"; +} +.icon--note1:before { + content: "\ea83"; +} +.icon--note2:before { + content: "\eaad"; +} +.icon--note3:before { + content: "\eac5"; +} +.icon--note4:before { + content: "\ea93"; +} +.icon--note5:before { + content: "\ea95"; +} +.icon--note6:before { + content: "\ea96"; +} diff --git a/packages/local/rambox-default-theme/sass/etc/all.scss b/packages/local/rambox-default-theme/sass/etc/all.scss index 851e7101..1eda5991 100644 --- a/packages/local/rambox-default-theme/sass/etc/all.scss +++ b/packages/local/rambox-default-theme/sass/etc/all.scss @@ -1,6 +1,7 @@ @import url(../resources/fonts/font-awesome/css/font-awesome.min.css); @import url(https://fonts.googleapis.com/css?family=Josefin+Sans:400,700,600); @import url(https://fonts.googleapis.com/css?family=Roboto:400,100,100italic,300,300italic,700italic,700,500italic,500,400italic); +@import 'loadscreen'; $base-color: #2E658E; $font-family: 'Roboto', sans-serif; diff --git a/packages/local/rambox-default-theme/sass/src/grid/column/Action.scss b/packages/local/rambox-default-theme/sass/src/grid/column/Action.scss new file mode 100644 index 00000000..2bf1fade --- /dev/null +++ b/packages/local/rambox-default-theme/sass/src/grid/column/Action.scss @@ -0,0 +1,3 @@ +.x-action-col-glyph { + color: lighten($base-color, 20%); +} diff --git a/resources/icons/actor.png b/resources/icons/actor.png new file mode 100644 index 00000000..4fbc4091 Binary files /dev/null and b/resources/icons/actor.png differ diff --git a/resources/icons/crisp.png b/resources/icons/crisp.png new file mode 100644 index 00000000..1e6ad78a Binary files /dev/null and b/resources/icons/crisp.png differ diff --git a/resources/icons/drift.png b/resources/icons/drift.png new file mode 100644 index 00000000..c995a413 Binary files /dev/null and b/resources/icons/drift.png differ diff --git a/resources/icons/fleep.png b/resources/icons/fleep.png new file mode 100644 index 00000000..5935aa0e Binary files /dev/null and b/resources/icons/fleep.png differ diff --git a/resources/icons/flock.png b/resources/icons/flock.png new file mode 100644 index 00000000..d1d15e93 Binary files /dev/null and b/resources/icons/flock.png differ diff --git a/resources/icons/kaiwa.png b/resources/icons/kaiwa.png new file mode 100644 index 00000000..fc16270b Binary files /dev/null and b/resources/icons/kaiwa.png differ diff --git a/resources/icons/mmmelon.png b/resources/icons/mmmelon.png new file mode 100644 index 00000000..aadf806c Binary files /dev/null and b/resources/icons/mmmelon.png differ diff --git a/resources/icons/movim.png b/resources/icons/movim.png new file mode 100644 index 00000000..8840297b Binary files /dev/null and b/resources/icons/movim.png differ diff --git a/resources/icons/openmailbox.png b/resources/icons/openmailbox.png new file mode 100644 index 00000000..c4d59c78 Binary files /dev/null and b/resources/icons/openmailbox.png differ diff --git a/resources/icons/pushbullet.png b/resources/icons/pushbullet.png new file mode 100644 index 00000000..c0243f1a Binary files /dev/null and b/resources/icons/pushbullet.png differ diff --git a/resources/icons/riot.png b/resources/icons/riot.png new file mode 100644 index 00000000..1a80ae1d Binary files /dev/null and b/resources/icons/riot.png differ diff --git a/resources/icons/smooch.png b/resources/icons/smooch.png new file mode 100644 index 00000000..360cbbde Binary files /dev/null and b/resources/icons/smooch.png differ diff --git a/resources/icons/socialcast.png b/resources/icons/socialcast.png new file mode 100644 index 00000000..ddb3f99b Binary files /dev/null and b/resources/icons/socialcast.png differ diff --git a/resources/icons/spark.png b/resources/icons/spark.png new file mode 100644 index 00000000..f6efbe49 Binary files /dev/null and b/resources/icons/spark.png differ diff --git a/resources/icons/typetalk.png b/resources/icons/typetalk.png new file mode 100644 index 00000000..bf57dce4 Binary files /dev/null and b/resources/icons/typetalk.png differ diff --git a/resources/installer/Icon.icns b/resources/installer/Icon.icns index 57a57efa..70207a97 100644 Binary files a/resources/installer/Icon.icns and b/resources/installer/Icon.icns differ diff --git a/resources/js/loadscreen.js b/resources/js/loadscreen.js new file mode 100644 index 00000000..049d2675 --- /dev/null +++ b/resources/js/loadscreen.js @@ -0,0 +1,263 @@ +/*! modernizr 3.2.0 (Custom Build) | MIT * + * http://modernizr.com/download/?-csstransitions-prefixedcss !*/ +!function(e,n,t){function r(e,n){return typeof e===n}function o(){var e,n,t,o,i,s,a;for(var f in C)if(C.hasOwnProperty(f)){if(e=[],n=C[f],n.name&&(e.push(n.name.toLowerCase()),n.options&&n.options.aliases&&n.options.aliases.length))for(t=0;td;d++)if(v=e[d],h=N.style[v],f(v,"-")&&(v=a(v)),N.style[v]!==t){if(i||r(o,"undefined"))return s(),"pfx"==n?v:!0;try{N.style[v]=o}catch(g){}if(N.style[v]!=h)return s(),"pfx"==n?v:!0}return s(),!1}function h(e,n,t,o,i){var s=e.charAt(0).toUpperCase()+e.slice(1),a=(e+" "+b.join(s+" ")+s).split(" ");return r(n,"string")||r(n,"undefined")?v(a,n,o,i):(a=(e+" "+P.join(s+" ")+s).split(" "),p(a,n,t))}function y(e,n,r){return h(e,t,t,n,r)}var g=[],C=[],x={_version:"3.2.0",_config:{classPrefix:"",enableClasses:!0,enableJSClass:!0,usePrefixes:!0},_q:[],on:function(e,n){var t=this;setTimeout(function(){n(t[e])},0)},addTest:function(e,n,t){C.push({name:e,fn:n,options:t})},addAsyncTest:function(e){C.push({name:null,fn:e})}},Modernizr=function(){};Modernizr.prototype=x,Modernizr=new Modernizr;var _=n.documentElement,w="svg"===_.nodeName.toLowerCase(),S="Moz O ms Webkit",b=x._config.usePrefixes?S.split(" "):[];x._cssomPrefixes=b;var E=function(n){var r,o=prefixes.length,i=e.CSSRule;if("undefined"==typeof i)return t;if(!n)return!1;if(n=n.replace(/^@/,""),r=n.replace(/-/g,"_").toUpperCase()+"_RULE",r in i)return"@"+n;for(var s=0;o>s;s++){var a=prefixes[s],f=a.toUpperCase()+"_"+r;if(f in i)return"@-"+a.toLowerCase()+"-"+n}return!1};x.atRule=E;var P=x._config.usePrefixes?S.toLowerCase().split(" "):[];x._domPrefixes=P;var z={elem:l("modernizr")};Modernizr._q.push(function(){delete z.elem});var N={style:z.elem.style};Modernizr._q.unshift(function(){delete N.style}),x.testAllProps=h;var T=x.prefixed=function(e,n,t){return 0===e.indexOf("@")?E(e):(-1!=e.indexOf("-")&&(e=a(e)),n?h(e,n,t):h(e,"pfx"))};x.prefixedCSS=function(e){var n=T(e);return n&&s(n)};x.testAllProps=y,Modernizr.addTest("csstransitions",y("transition","all",!0)),o(),i(g),delete x.addTest,delete x.addAsyncTest;for(var j=0;j t1) return curveY(t1); + + // Fallback to the bisection method for reliability. + while (t0 < t1){ + x2 = curveX(t2); + if (Math.abs(x2 - x) < epsilon) return curveY(t2); + if (x > x2) t0 = t2; + else t1 = t2; + t2 = (t1 - t0) * .5 + t0; + } + // Failure + return curveY(t2); + }; + }, + getRandomNumber = function(min, max) { + return Math.floor(Math.random() * (max - min + 1)) + min; + }, + throttle = function(fn, delay) { + var allowSample = true; + + return function(e) { + if (allowSample) { + allowSample = false; + setTimeout(function() { allowSample = true; }, delay); + fn(e); + } + }; + }, + // from https://davidwalsh.name/vendor-prefix + prefix = (function () { + var styles = window.getComputedStyle(document.documentElement, ''), + pre = (Array.prototype.slice.call(styles).join('').match(/-(moz|webkit|ms)-/) || (styles.OLink === '' && ['', 'o']))[1], + dom = ('WebKit|Moz|MS|O').match(new RegExp('(' + pre + ')', 'i'))[1]; + + return { + dom: dom, + lowercase: pre, + css: '-' + pre + '-', + js: pre[0].toUpperCase() + pre.substr(1) + }; + })(); + + var support = {transitions : Modernizr.csstransitions}, + transEndEventNames = { 'WebkitTransition': 'webkitTransitionEnd', 'MozTransition': 'transitionend', 'OTransition': 'oTransitionEnd', 'msTransition': 'MSTransitionEnd', 'transition': 'transitionend' }, + transEndEventName = transEndEventNames[ Modernizr.prefixed( 'transition' ) ], + onEndTransition = function( el, callback, propTest ) { + var onEndCallbackFn = function( ev ) { + if( support.transitions ) { + if( ev.target != this || propTest && ev.propertyName !== propTest && ev.propertyName !== prefix.css + propTest ) return; + this.removeEventListener( transEndEventName, onEndCallbackFn ); + } + if( callback && typeof callback === 'function' ) { callback.call(this); } + }; + if( support.transitions ) { + el.addEventListener( transEndEventName, onEndCallbackFn ); + } + else { + onEndCallbackFn(); + } + }, + // the main component element/wrapper + shzEl = document.querySelector('.component'), + // the initial button + shzCtrl = shzEl.querySelector('div.button--start'), + // total number of notes/symbols moving towards the listen button + totalNotes = 50, + // the notes elements + notes, + // the note´s speed factor relative to the distance from the note element to the button. + // if notesSpeedFactor = 1, then the speed equals the distance (in ms) + notesSpeedFactor = 4.5, + // window sizes + winsize = {width: window.innerWidth, height: window.innerHeight}, + // button offset + shzCtrlOffset = shzCtrl.getBoundingClientRect(), + // button sizes + shzCtrlSize = {width: shzCtrl.offsetWidth, height: shzCtrl.offsetHeight}, + // tells us if the listening animation is taking place + isListening = false, + // audio player element + playerEl = shzEl.querySelector('.player'); + // close player control + //playerCloseCtrl = playerEl.querySelector('.button--close'); + + function init() { + // create the music notes elements - the musical symbols that will animate/move towards the listen button + createNotes(); + // star animation + listen(); + } + + /** + * creates [totalNotes] note elements (the musical symbols that will animate/move towards the listen button) + */ + function createNotes() { + var notesEl = document.createElement('div'), notesElContent = ''; + notesEl.className = 'notes'; + for(var i = 0; i < totalNotes; ++i) { + // we have 6 different types of symbols (icon--note1, icon--note2 ... icon--note6) + var j = (i + 1) - 6 * Math.floor(i/6); + notesElContent += '
'; + } + notesEl.innerHTML = notesElContent; + shzEl.insertBefore(notesEl, shzEl.firstChild) + + // reference to the notes elements + notes = [].slice.call(notesEl.querySelectorAll('.note')); + } + + /** + * transform the initial button into a circle shaped one that "listens" to the current song.. + */ + function listen() { + isListening = true; + + showNotes(); + } + + /** + * stop the ripples and notes animations + */ + function stopListening() { + isListening = false; + // music notes animation stops... + hideNotes(); + } + + /** + * show the notes elements: first set a random position and then animate them towards the button + */ + function showNotes() { + notes.forEach(function(note) { + // first position the notes randomly on the page + positionNote(note); + // now, animate the notes torwards the button + animateNote(note); + }); + } + + /** + * fade out the notes elements + */ + function hideNotes() { + notes.forEach(function(note) { + note.style.opacity = 0; + }); + } + + /** + * positions a note/symbol randomly on the page. The area is restricted to be somewhere outside of the viewport. + * @param {Element Node} note - the note element + */ + function positionNote(note) { + // we want to position the notes randomly (translation and rotation) outside of the viewport + var x = getRandomNumber(-2*(shzCtrlOffset.left + shzCtrlSize.width/2), 2*(winsize.width - (shzCtrlOffset.left + shzCtrlSize.width/2))), y, + rotation = getRandomNumber(-30, 30); + + if( x > -1*(shzCtrlOffset.top + shzCtrlSize.height/2) && x < shzCtrlOffset.top + shzCtrlSize.height/2 ) { + y = getRandomNumber(0,1) > 0 ? getRandomNumber(-2*(shzCtrlOffset.top + shzCtrlSize.height/2), -1*(shzCtrlOffset.top + shzCtrlSize.height/2)) : getRandomNumber(winsize.height - (shzCtrlOffset.top + shzCtrlSize.height/2), winsize.height + winsize.height - (shzCtrlOffset.top + shzCtrlSize.height/2)); + } + else { + y = getRandomNumber(-2*(shzCtrlOffset.top + shzCtrlSize.height/2), winsize.height + winsize.height - (shzCtrlOffset.top + shzCtrlSize.height/2)); + } + + // first reset transition if any + note.style.WebkitTransition = note.style.transition = 'none'; + + // apply the random transforms + note.style.WebkitTransform = note.style.transform = 'translate3d(' + x + 'px,' + y + 'px,0) rotate3d(0,0,1,' + rotation + 'deg)'; + + // save the translation values for later + note.setAttribute('data-tx', Math.abs(x)); + note.setAttribute('data-ty', Math.abs(y)); + } + + /** + * animates a note torwards the button. Once that's done, it repositions the note and animates it again until the component is no longer listening. + * @param {Element Node} note - the note element + */ + function animateNote(note) { + setTimeout(function() { + if(!isListening) return; + // the transition speed of each note will be proportional to the its distance to the button + // speed = notesSpeedFactor * distance + var noteSpeed = notesSpeedFactor * Math.sqrt(Math.pow(note.getAttribute('data-tx'),2) + Math.pow(note.getAttribute('data-ty'),2)); + + // apply the transition + note.style.WebkitTransition = '-webkit-transform ' + noteSpeed + 'ms ease, opacity 0.8s'; + note.style.transition = 'transform ' + noteSpeed + 'ms ease-in, opacity 0.8s'; + + // now apply the transform (reset the transform so the note moves to its original position) and fade in the note + note.style.WebkitTransform = note.style.transform = 'translate3d(0,0,0)'; + note.style.opacity = 1; + + // after the animation is finished, + var onEndTransitionCallback = function() { + // reset transitions and styles + note.style.WebkitTransition = note.style.transition = 'none'; + note.style.opacity = 0; + + if(!isListening) return; + + positionNote(note); + animateNote(note); + }; + + onEndTransition(note, onEndTransitionCallback, 'transform'); + }, 60); + } + + init(); + +})(window);