diff --git a/app.json b/app.json index 18b33371..a2ff4665 100644 --- a/app.json +++ b/app.json @@ -277,7 +277,8 @@ * Extra resources to be copied along when build */ "resources": [ - "main.js", + "electron", + "electron_main.js", "package.json" ], diff --git a/app/Application.js b/app/Application.js index ab41cd40..21fc3efc 100644 --- a/app/Application.js +++ b/app/Application.js @@ -9,7 +9,8 @@ Ext.define('Rambox.Application', { ] ,config: { - totalServicesLoaded: 0 + totalServicesLoaded: 0 + ,totalNotifications: 0 } ,launch: function () { @@ -23,4 +24,13 @@ Ext.define('Rambox.Application', { } }); } + + ,updateTotalNotifications: function( newValue, oldValue ) { + newValue = parseInt(newValue); + if ( newValue > 0 ) { + document.title = 'Rambox (' + newValue + ')'; + } else { + document.title = 'Rambox'; + } + } }); diff --git a/app/ux/WebView.js b/app/ux/WebView.js index 2f67a527..8287550c 100644 --- a/app/ux/WebView.js +++ b/app/ux/WebView.js @@ -40,14 +40,25 @@ Ext.define('Rambox.ux.WebView',{ ,autosize: 'on' } }] + ,tabConfig: { + listeners: { + badgetextchange: me.onBadgeTextChange + } + } ,listeners: { - afterrender: me.onAfterRender + afterrender: me.onAfterRender } }); me.callParent(config); } + ,onBadgeTextChange: function( tab, badgeText, oldBadgeText ) { + if ( oldBadgeText === null ) oldBadgeText = 0; + var actualNotifications = Rambox.app.getTotalNotifications(); + Rambox.app.setTotalNotifications(actualNotifications - parseInt(oldBadgeText) + parseInt(badgeText)); + } + ,onAfterRender: function() { var me = this; var webview = me.down('component').el.dom; @@ -119,7 +130,7 @@ Ext.define('Rambox.ux.WebView',{ me.tab.setBadgeText(1); me.notifications = 1; } else { - me.tab.setBadgeText(''); + me.tab.setBadgeText(0); me.notifications = 0; } break; diff --git a/app/view/main/Main.js b/app/view/main/Main.js index 5d5dd976..c70249b9 100644 --- a/app/view/main/Main.js +++ b/app/view/main/Main.js @@ -35,13 +35,13 @@ Ext.define('Rambox.view.main.Main', { } } ] - + ,autoRender: true ,autoShow: true ,deferredRender: false ,items: [ { - icon: 'resources/logo_32.png' + icon: 'resources/IconTray.png' ,closable: false ,reorderable: false ,autoScroll: true diff --git a/electron/menu.js b/electron/menu.js new file mode 100644 index 00000000..1f8276bb --- /dev/null +++ b/electron/menu.js @@ -0,0 +1,203 @@ +'use strict'; +const os = require('os'); +const electron = require('electron'); +const app = electron.app; +const BrowserWindow = electron.BrowserWindow; +const shell = electron.shell; +const appName = app.getName(); + +function sendAction(action) { + const win = BrowserWindow.getAllWindows()[0]; + + if (process.platform === 'darwin') { + win.restore(); + } + + win.webContents.send(action); +} + +const helpSubmenu = [ + { + label: `Visit ${appName} Website`, + click() { + shell.openExternal('http://saenzramiro.github.io/rambox/'); + } + }, + { + label: 'Report an Issue...', + click() { + const body = ` + + + + + +- +> ${app.getName()} ${app.getVersion()} +> Electron ${process.versions.electron} +> ${process.platform} ${process.arch} ${os.release()}`; + + shell.openExternal(`https://github.com/saenzramiro/rambox/issues/new?body=${encodeURIComponent(body)}`); + } + } +]; + +let tpl = [ + { + label: 'Edit', + submenu: [ + { + label: 'Undo', + accelerator: 'CmdOrCtrl+Z', + role: 'undo' + }, + { + label: 'Redo', + accelerator: 'Shift+CmdOrCtrl+Z', + role: 'redo' + }, + { + type: 'separator' + }, + { + label: 'Cut', + accelerator: 'CmdOrCtrl+X', + role: 'cut' + }, + { + label: 'Copy', + accelerator: 'CmdOrCtrl+C', + role: 'copy' + }, + { + label: 'Paste', + accelerator: 'CmdOrCtrl+V', + role: 'paste' + }, + { + label: 'Select All', + accelerator: 'CmdOrCtrl+A', + role: 'selectall' + }, + ] + }, + { + label: 'View', + submenu: [ + { + label: 'Reload', + accelerator: 'CmdOrCtrl+R', + click(item, focusedWindow) { + if (focusedWindow) focusedWindow.reload(); + } + }, + { + label: 'Toggle Full Screen', + accelerator: process.platform === 'darwin' ? 'Ctrl+Command+F' : 'F11', + click(item, focusedWindow) { + if (focusedWindow) + focusedWindow.setFullScreen(!focusedWindow.isFullScreen()); + } + }, + { + label: 'Toggle Developer Tools', + accelerator: process.platform === 'darwin' ? 'Alt+Command+I' : 'Ctrl+Shift+I', + click(item, focusedWindow) { + if (focusedWindow) + focusedWindow.webContents.toggleDevTools(); + } + }, + ] + }, + { + label: 'Window', + role: 'window', + submenu: [ + { + label: 'Minimize', + accelerator: 'CmdOrCtrl+M', + role: 'minimize' + }, + { + label: 'Close', + accelerator: 'CmdOrCtrl+W', + role: 'close' + }, + ] + }, + { + label: 'Help', + role: 'help' + } +]; + +if (process.platform === 'darwin') { + tpl.unshift({ + label: appName, + submenu: [ + { + label: `About ${appName}`, + role: 'about' + }, + { + type: 'separator' + }, + { + label: 'Services', + role: 'services', + submenu: [] + }, + { + type: 'separator' + }, + { + label: `Hide ${appName}`, + accelerator: 'Command+H', + role: 'hide' + }, + { + label: 'Hide Others', + accelerator: 'Command+Alt+H', + role: 'hideothers' + }, + { + label: 'Show All', + role: 'unhide' + }, + { + type: 'separator' + }, + { + label: `Quit ${appName}`, + accelerator: 'Cmd+Q', + click() { + app.quit(); + } + } + ] + }); +} else { + tpl.unshift({ + label: 'File', + submenu: [ + { + label: `Quit ${appName}`, + accelerator: 'Cmd+Q', + click() { + app.quit(); + } + } + ] + }); + helpSubmenu.push({ + type: 'separator' + }); + helpSubmenu.push({ + label: `About ${appName}`, + role: 'about' + }); +} + +tpl[tpl.length - 1].submenu = helpSubmenu; + +module.exports = electron.Menu.buildFromTemplate(tpl); diff --git a/electron/tray.js b/electron/tray.js new file mode 100644 index 00000000..8001aa2d --- /dev/null +++ b/electron/tray.js @@ -0,0 +1,88 @@ +const path = require('path'); +const electron = require('electron'); +const app = electron.app; +const MenuItem = electron.MenuItem; +let tray = null; + +exports.create = win => { + if (process.platform === 'darwin' || tray) { + return; + } + + const icon = process.platform === 'linux' ? 'IconTray.png' : 'Icon.ico'; + const iconPath = path.join(__dirname, `../resources/${icon}`); + + let showMB = new MenuItem({ + label: 'Show Rambox' + ,position: '1' + ,visible: false + ,click(btn) { + win.show(); + contextMenu.items[0].visible = false; + contextMenu.items[1].visible = true; + } + }); + + let hideMB = new MenuItem({ + label: 'Minimize Rambox' + ,position: '2' + ,click(btn) { + win.hide(); + contextMenu.items[1].visible = false; + contextMenu.items[0].visible = true; + } + }); + + const contextMenu = electron.Menu.buildFromTemplate([ + showMB, + hideMB, + { + label: 'Preferences' + }, + { + type: 'separator' + }, + { + label: 'Quit' + ,click() { + app.quit(); + } + } + ]); + + tray = new electron.Tray(iconPath); + tray.setToolTip('Rambox'); + tray.setContextMenu(contextMenu); + tray.on('click', function() { + if ( win.isVisible() ) { + win.hide(); + contextMenu.items[1].visible = false; + contextMenu.items[0].visible = true; + } else { + win.show(); + contextMenu.items[0].visible = false; + contextMenu.items[1].visible = true; + } + }); + + win.on('hide', function() { + contextMenu.items[1].visible = false; + contextMenu.items[0].visible = true; + }); +}; + +exports.setBadge = shouldDisplayUnread => { + if (process.platform === 'darwin' || !tray) { + return; + } + + let icon; + if (process.platform === 'linux') { + icon = shouldDisplayUnread ? 'IconTrayUnread.png' : 'IconTray.png'; + } else { + icon = shouldDisplayUnread ? 'IconTrayUnread.ico' : 'Icon.ico'; + } + + const iconPath = path.join(__dirname, `../resources/${icon}`); + tray.setImage(iconPath); +}; diff --git a/main.js b/electron_main.js similarity index 61% rename from main.js rename to electron_main.js index 1fbd92d2..e9b8ddb3 100644 --- a/main.js +++ b/electron_main.js @@ -9,6 +9,10 @@ const BrowserWindow = electron.BrowserWindow; const Tray = electron.Tray; // Module for shell const shell = require('electron').shell; +// Require for menu file +const appMenu = require('./electron/menu'); +// Require for tray file +const tray = require('./electron/tray'); const MenuItem = electron.MenuItem; @@ -16,62 +20,15 @@ const MenuItem = electron.MenuItem; // be closed automatically when the JavaScript object is garbage collected. let mainWindow; let isQuitting = false; -let tray = null; - -let showMB = new MenuItem({ - label: 'Show Rambox' - ,position: '1' - ,visible: false - ,click(btn) { - mainWindow.show(); - contextMenu.items[0].visible = false; - contextMenu.items[1].visible = true; - } -}); - -let hideMB = new MenuItem({ - label: 'Minimize Rambox' - ,position: '2' - ,click(btn) { - mainWindow.hide(); - contextMenu.items[1].visible = false; - contextMenu.items[0].visible = true; - } -}); - -const contextMenu = electron.Menu.buildFromTemplate([ - showMB, - hideMB, - { - label: 'Preferences' - }, - { - label: 'Open Developer Console' - ,click() { - mainWindow.webContents.openDevTools(); - } - }, - { - type: 'separator' - }, - { - label: 'Quit' - ,click() { - app.quit(); - } - } -]); function createWindow () { // Create the browser window. mainWindow = new BrowserWindow({ title: 'Rambox' ,skipTaskbar: false - ,icon: __dirname + '/resources/logo_256.png' - ,autoHideMenuBar: true + ,icon: __dirname + '/resources/Icon.png' ,webPreferences: { webSecurity: false - ,partition: 'trusted*' ,nodeIntegration: true ,plugins: true ,partition: 'persist:rambox' @@ -84,20 +41,11 @@ function createWindow () { // and load the index.html of the app. mainWindow.loadURL('file://' + __dirname + '/index.html'); - tray = new Tray(__dirname + '/resources/logo_256.png'); - tray.setToolTip('Rambox'); - tray.setContextMenu(contextMenu); - tray.on('click', function() { - if ( mainWindow.isVisible() ) { - mainWindow.hide(); - contextMenu.items[1].visible = false; - contextMenu.items[0].visible = true; - } else { - mainWindow.show(); - contextMenu.items[0].visible = false; - contextMenu.items[1].visible = true; - } - }); + electron.Menu.setApplicationMenu(appMenu); + + tray.create(mainWindow); + + mainWindow.on('page-title-updated', (e, title) => updateBadge(title)); // Emitted when the window is closed. mainWindow.on('close', function(e) { @@ -117,6 +65,16 @@ function createWindow () { }); } +function updateBadge(title) { + const messageCount = (/\(([0-9]+)\)/).exec(title); + + if (process.platform === 'darwin') { + app.dock.setBadge(messageCount ? messageCount[1] : ''); + } else { + tray.setBadge(messageCount); + } +} + // This method will be called when Electron has finished // initialization and is ready to create browser windows. app.on('ready', createWindow); diff --git a/package.json b/package.json index f6265167..87b4aac5 100644 --- a/package.json +++ b/package.json @@ -2,9 +2,9 @@ "name": "Rambox", "version": "0.1.0", "description": "Free messaging app that combines chat and messaging common services into one application.", - "main": "main.js", + "main": "electron_main.js", "scripts": { - "start": "electron main.js" + "start": "electron electron_main.js" }, "repository": { "type": "git", @@ -24,7 +24,7 @@ "skype" ], "author": "Ramiro Saenz", - "license": "CC0-1.0", + "license": "MIT", "bugs": { "url": "https://github.com/saenzramiro/rambox/issues" }, diff --git a/resources/Icon.icns b/resources/Icon.icns new file mode 100644 index 00000000..95cb6e29 Binary files /dev/null and b/resources/Icon.icns differ diff --git a/resources/Icon.ico b/resources/Icon.ico new file mode 100644 index 00000000..bc97f674 Binary files /dev/null and b/resources/Icon.ico differ diff --git a/resources/logo_256.png b/resources/Icon.png similarity index 100% rename from resources/logo_256.png rename to resources/Icon.png diff --git a/resources/Icon.svg b/resources/Icon.svg new file mode 100644 index 00000000..efa806e8 --- /dev/null +++ b/resources/Icon.svg @@ -0,0 +1,28 @@ + + + + +Created by potrace 1.13, written by Peter Selinger 2001-2015 + + + + + + + diff --git a/resources/IconTray.png b/resources/IconTray.png new file mode 100644 index 00000000..88ed05ae Binary files /dev/null and b/resources/IconTray.png differ diff --git a/resources/IconTray@2x.png b/resources/IconTray@2x.png new file mode 100644 index 00000000..8adbe22f Binary files /dev/null and b/resources/IconTray@2x.png differ diff --git a/resources/IconTray@4x.png b/resources/IconTray@4x.png new file mode 100644 index 00000000..12872124 Binary files /dev/null and b/resources/IconTray@4x.png differ diff --git a/resources/IconTrayUnread.ico b/resources/IconTrayUnread.ico new file mode 100644 index 00000000..05fea58c Binary files /dev/null and b/resources/IconTrayUnread.ico differ diff --git a/resources/IconTrayUnread.png b/resources/IconTrayUnread.png new file mode 100644 index 00000000..53e1e706 Binary files /dev/null and b/resources/IconTrayUnread.png differ diff --git a/resources/IconTrayUnread@2x.png b/resources/IconTrayUnread@2x.png new file mode 100644 index 00000000..a00ff9ea Binary files /dev/null and b/resources/IconTrayUnread@2x.png differ diff --git a/resources/IconTrayUnread@4x.png b/resources/IconTrayUnread@4x.png new file mode 100644 index 00000000..5d7e2d6e Binary files /dev/null and b/resources/IconTrayUnread@4x.png differ diff --git a/resources/logo_128.png b/resources/logo_128.png deleted file mode 100644 index e06b9e8a..00000000 Binary files a/resources/logo_128.png and /dev/null differ diff --git a/resources/logo_16.png b/resources/logo_16.png deleted file mode 100644 index 66079b5c..00000000 Binary files a/resources/logo_16.png and /dev/null differ diff --git a/resources/logo_32.png b/resources/logo_32.png deleted file mode 100644 index aa950c9c..00000000 Binary files a/resources/logo_32.png and /dev/null differ diff --git a/resources/logo_64.png b/resources/logo_64.png deleted file mode 100644 index f827d6c4..00000000 Binary files a/resources/logo_64.png and /dev/null differ