From 0704cf544a254d8468c61445c1d04c2c9352679d Mon Sep 17 00:00:00 2001 From: Yuya Ochiai Date: Sun, 31 Jan 2016 00:50:43 +0900 Subject: [PATCH 1/5] Use webpack to build --- .gitignore | 2 +- gulpfile.js | 84 +++++++++++++++++++++++++++++++----- package.json | 10 ++++- src/browser/index.html | 7 +-- src/browser/index.jsx | 4 ++ src/browser/settings.html | 7 +-- src/browser/settings.jsx | 12 +++++- src/main.js | 12 +++--- src/{ => main}/menus/app.js | 2 +- src/{ => main}/menus/tray.js | 0 test/browser_test.js | 6 +-- 11 files changed, 111 insertions(+), 35 deletions(-) rename src/{ => main}/menus/app.js (97%) rename src/{ => main}/menus/tray.js (100%) diff --git a/.gitignore b/.gitignore index 7210ba6c..ae75970c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ node_modules/ -build/ +dist/ release/ npm-debug.log diff --git a/gulpfile.js b/gulpfile.js index ad8bff8a..9bd57272 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -3,16 +3,18 @@ var gulp = require('gulp'); var prettify = require('gulp-jsbeautifier'); var babel = require('gulp-babel'); +var webpack = require('webpack-stream'); +var named = require('vinyl-named'); var changed = require('gulp-changed'); var esformatter = require('gulp-esformatter'); var del = require('del'); var electron = require('electron-connect').server.create({ - path: './src' + path: './dist' }); var packager = require('electron-packager'); var sources = ['**/*.js', '**/*.css', '**/*.html', '!**/node_modules/**', '!**/build/**', '!release/**']; -var app_root = 'src'; +var app_root = 'dist'; gulp.task('prettify', ['prettify:sources', 'prettify:jsx']); @@ -34,7 +36,7 @@ gulp.task('prettify:sources', ['sync-meta'], function() { }); gulp.task('prettify:jsx', function() { - return gulp.src(app_root + '/**/*.jsx') + return gulp.src('src/browser/**/*.jsx') .pipe(esformatter({ indent: { value: ' ' @@ -44,17 +46,77 @@ gulp.task('prettify:jsx', function() { .pipe(gulp.dest(app_root)); }); -gulp.task('build', ['sync-meta', 'build:jsx']); +gulp.task('build', ['sync-meta', 'webpack', 'copy'], function() { + return gulp.src('src/package.json') + .pipe(gulp.dest('dist')); +}); -gulp.task('build:jsx', function() { - return gulp.src(['src/browser/**/*.jsx', '!src/node_modules/**']) - .pipe(changed(app_root, { - extension: '.js' +gulp.task('webpack', ['webpack:main', 'webpack:browser']); + +gulp.task('webpack:browser', function() { + return gulp.src('src/browser/**/*.jsx') + .pipe(named()) + .pipe(webpack({ + module: { + loaders: [{ + test: /\.json$/, + loader: 'json' + }, { + test: /\.jsx$/, + loader: 'babel', + query: { + presets: ['react'] + } + }] + }, + output: { + filename: '[name].js' + }, + node: { + __filename: false, + __dirname: false + }, + target: 'electron' })) - .pipe(babel({ - presets: ['react'] + .pipe(gulp.dest('dist/browser/')); +}); + +gulp.task('webpack:main', function() { + return gulp.src('src/main.js') + .pipe(webpack({ + module: { + loaders: [{ + test: /\.json$/, + loader: 'json' + }] + }, + output: { + filename: '[name].js' + }, + node: { + __filename: false, + __dirname: false + }, + target: 'electron' })) - .pipe(gulp.dest('src/browser/build')); + .pipe(gulp.dest('dist/')); +}); + +gulp.task('copy', ['copy:resources', 'copy:html/css', 'copy:modules']); + +gulp.task('copy:resources', function() { + return gulp.src('src/resources/**') + .pipe(gulp.dest('dist/resources')); +}); + +gulp.task('copy:html/css', function() { + return gulp.src(['src/browser/**/*.html', 'src/browser/**/*.css']) + .pipe(gulp.dest('dist/browser')); +}); + +gulp.task('copy:modules', function() { + return gulp.src(['src/node_modules/bootstrap/dist/**']) + .pipe(gulp.dest('dist/browser/modules/bootstrap')) }); gulp.task('serve', ['build'], function() { diff --git a/package.json b/package.json index 405d36bf..50743590 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "install": "cd src && npm install", "postinstall": "npm run build", "build": "gulp build", - "start": "electron src", + "start": "electron dist", "serve": "gulp serve", "test": "gulp build && mocha", "package": "gulp package", @@ -20,6 +20,8 @@ "prettify": "gulp prettify" }, "devDependencies": { + "babel-core": "^6.5.1", + "babel-loader": "^6.2.2", "babel-preset-react": "^6.3.13", "chromedriver": "^2.20.0", "del": "^2.2.0", @@ -33,9 +35,13 @@ "gulp-changed": "^1.3.0", "gulp-esformatter": "^5.0.0", "gulp-jsbeautifier": "^1.0.1", + "json-loader": "^0.5.4", "mocha": "^2.3.4", "mocha-circleci-reporter": "0.0.1", "should": "^8.0.1", - "webdriverio": "^3.3.0" + "style-loader": "^0.13.0", + "vinyl-named": "^1.1.0", + "webdriverio": "^3.3.0", + "webpack-stream": "^3.1.0" } } diff --git a/src/browser/index.html b/src/browser/index.html index c9c0a495..6df0c0b7 100644 --- a/src/browser/index.html +++ b/src/browser/index.html @@ -4,15 +4,12 @@ electron-mattermost - - - - +
- + \ No newline at end of file diff --git a/src/browser/index.jsx b/src/browser/index.jsx index 726690fb..09d09ad7 100644 --- a/src/browser/index.jsx +++ b/src/browser/index.jsx @@ -1,5 +1,9 @@ 'use strict'; +const React = require('react'); +const ReactDOM = require('react-dom'); +const ReactBootstrap = require('react-bootstrap'); + const Grid = ReactBootstrap.Grid; const Row = ReactBootstrap.Row; const Col = ReactBootstrap.Col; diff --git a/src/browser/settings.html b/src/browser/settings.html index f3b11678..f4ee24ef 100644 --- a/src/browser/settings.html +++ b/src/browser/settings.html @@ -4,15 +4,12 @@ Settings - - - - +
- + \ No newline at end of file diff --git a/src/browser/settings.jsx b/src/browser/settings.jsx index 53d0319d..e57ff722 100644 --- a/src/browser/settings.jsx +++ b/src/browser/settings.jsx @@ -3,6 +3,10 @@ const remote = require('electron').remote; const settings = require('../common/settings'); +const React = require('react'); +const ReactDOM = require('react-dom'); +const ReactBootstrap = require('react-bootstrap'); + const Grid = ReactBootstrap.Grid; const Row = ReactBootstrap.Row; const Col = ReactBootstrap.Col; @@ -12,6 +16,10 @@ const ListGroup = ReactBootstrap.ListGroup; const ListGroupItem = ReactBootstrap.ListGroupItem; const Glyphicon = ReactBootstrap.Glyphicon; +function backToIndex(){ + window.location = 'index.html'; +} + var SettingsPage = React.createClass({ getInitialState: function() { var config; @@ -42,10 +50,10 @@ var SettingsPage = React.createClass({ currentWindow.setAutoHideMenuBar(config.hideMenuBar); currentWindow.setMenuBarVisibility(!config.hideMenuBar); } - window.location = './index.html'; + backToIndex(); }, handleCancel: function() { - window.location = './index.html'; + backToIndex(); }, handleChangeHideMenuBar: function() { this.setState({ diff --git a/src/main.js b/src/main.js index af10de33..c1681ca0 100644 --- a/src/main.js +++ b/src/main.js @@ -7,9 +7,10 @@ const Menu = electron.Menu; const Tray = electron.Tray; const ipc = electron.ipcMain; const fs = require('fs'); +const path = require('path'); var settings = require('./common/settings'); -var appMenu = require('./menus/app'); +var appMenu = require('./main/menus/app'); var argv = require('yargs').argv; @@ -38,6 +39,7 @@ try { } } catch (e) { + config = settings.loadDefault(); console.log('Failed to read or upgrade config.json'); } @@ -83,9 +85,9 @@ app.on('before-quit', function() { app.on('ready', function() { // set up tray icon to show balloon if (process.platform === 'win32') { - trayIcon = new Tray(__dirname + '/resources/tray.png'); + trayIcon = new Tray(path.resolve(__dirname, 'resources/tray.png')); trayIcon.setToolTip(app.getName()); - var tray_menu = require('./menus/tray').createDefault(); + var tray_menu = require('./main/menus/tray').createDefault(); trayIcon.setContextMenu(tray_menu); trayIcon.on('click', function() { mainWindow.focus(); @@ -95,7 +97,7 @@ app.on('ready', function() { }); ipc.on('notified', function(event, arg) { trayIcon.displayBalloon({ - icon: __dirname + '/resources/appicon.png', + icon: path.resolve(__dirname, 'resources/appicon.png'), title: arg.title, content: arg.options.body }); @@ -112,7 +114,7 @@ app.on('ready', function() { // follow Electron's defaults window_options = {}; } - window_options.icon = __dirname + '/resources/appicon.png'; + window_options.icon = path.resolve(__dirname, 'resources/appicon.png'); mainWindow = new BrowserWindow(window_options); if (window_options.maximized) { mainWindow.maximize(); diff --git a/src/menus/app.js b/src/main/menus/app.js similarity index 97% rename from src/menus/app.js rename to src/main/menus/app.js index 2759d6ba..68fdb8a4 100644 --- a/src/menus/app.js +++ b/src/main/menus/app.js @@ -15,7 +15,7 @@ var createTemplate = function(mainWindow) { }, { label: 'Settings', click: function(item, focusedWindow) { - mainWindow.loadURL('file://' + __dirname + '/../browser/settings.html'); + mainWindow.loadURL('file://' + __dirname + '/browser/settings.html'); } }, { label: 'Quit', diff --git a/src/menus/tray.js b/src/main/menus/tray.js similarity index 100% rename from src/menus/tray.js rename to src/main/menus/tray.js diff --git a/test/browser_test.js b/test/browser_test.js index 746ce496..a30ae701 100644 --- a/test/browser_test.js +++ b/test/browser_test.js @@ -25,7 +25,7 @@ var options = { browserName: 'chrome', chromeOptions: { binary: electron_binary_path, // Path to your Electron binary. - args: ['app=' + path.join(source_root_dir, 'src'), '--config-file=' + config_file_path] // Optional, perhaps 'app=' + /path/to/your/app/ + args: ['app=' + path.join(source_root_dir, 'dist'), '--config-file=' + config_file_path] // Optional, perhaps 'app=' + /path/to/your/app/ } } }; @@ -187,7 +187,7 @@ describe('electron-mattermost', function() { it('should show index.thml when Cancel button is clicked', function() { return client .init() - .url('file://' + path.join(source_root_dir, 'src/browser/settings.html')) + .url('file://' + path.join(source_root_dir, 'dist/browser/settings.html')) .waitForExist('#btnCancel') .click('#btnCancel') .pause(1000) @@ -201,7 +201,7 @@ describe('electron-mattermost', function() { it('should show index.thml when Save button is clicked', function() { return client .init() - .url('file://' + path.join(source_root_dir, 'src/browser/settings.html')) + .url('file://' + path.join(source_root_dir, 'dist/browser/settings.html')) .waitForExist('#btnSave') .click('#btnSave') .pause(1000) From ee273a9441b5e5f9ef5e0954579837ad4c094b41 Mon Sep 17 00:00:00 2001 From: Yuya Ochiai Date: Tue, 9 Feb 2016 20:56:59 +0900 Subject: [PATCH 2/5] Copy src/browser/webview/mattermost.js into dist/ --- gulpfile.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/gulpfile.js b/gulpfile.js index 9bd57272..567b2b34 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -102,7 +102,7 @@ gulp.task('webpack:main', function() { .pipe(gulp.dest('dist/')); }); -gulp.task('copy', ['copy:resources', 'copy:html/css', 'copy:modules']); +gulp.task('copy', ['copy:resources', 'copy:html/css', 'copy:webview:js', 'copy:modules']); gulp.task('copy:resources', function() { return gulp.src('src/resources/**') @@ -114,6 +114,11 @@ gulp.task('copy:html/css', function() { .pipe(gulp.dest('dist/browser')); }); +gulp.task('copy:webview:js', function() { + return gulp.src(['src/browser/webview/**/*.js']) + .pipe(gulp.dest('dist/browser/webview')) +}); + gulp.task('copy:modules', function() { return gulp.src(['src/node_modules/bootstrap/dist/**']) .pipe(gulp.dest('dist/browser/modules/bootstrap')) From f3bca4421845610377f5622864aa477702348262 Mon Sep 17 00:00:00 2001 From: Yuya Ochiai Date: Tue, 9 Feb 2016 20:59:52 +0900 Subject: [PATCH 3/5] Fix watch-task for webpack --- README.md | 4 ++-- gulpfile.js | 13 ++++++++----- package.json | 3 ++- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 5e505a07..e40dbc20 100644 --- a/README.md +++ b/README.md @@ -50,10 +50,10 @@ Node.js is required to test this app. 2. Run `npm install`. 3. Run `npm start`. -When you edit **.jsx** files, please execute `npm run build` before `npm start`. +When you edit `src/**` files, please execute `npm run build` before `npm start`. ### Development -#### `npm run serve` +#### `npm run watch` Reload the app automatically when you have saved source codes. #### `npm test` diff --git a/gulpfile.js b/gulpfile.js index 567b2b34..f9ff198d 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -124,15 +124,18 @@ gulp.task('copy:modules', function() { .pipe(gulp.dest('dist/browser/modules/bootstrap')) }); -gulp.task('serve', ['build'], function() { +gulp.task('watch', ['build'], function() { var options = ['--livereload']; electron.start(options); - gulp.watch(['src/**', '!src/browser/**', '!src/node_modules/**'], function() { + + gulp.watch(['src/main.js', 'src/main/**/*.js', 'src/common/**/*.js'], ['webpack:main']); + gulp.watch(['src/browser/**/*.js', 'src/browser/**/*.jsx'], ['webpack:browser', 'copy:webview:js']); + gulp.watch(['src/browser/**/*.css', 'src/browser/**/*.html', 'src/resources/**/*.png'], ['copy']); + + gulp.watch(['dist/main.js', 'dist/resources/**'], function() { electron.restart(options); }); - gulp.watch('src/browser/**/*.jsx', ['build:jsx']); - gulp.watch(['src/browser/**', '!src/browser/**/*.jsx'], electron.reload); - gulp.watch('gulpfile.js', process.exit); + gulp.watch(['dist/browser/*.js'], electron.reload); }); function makePackage(platform, arch, callback) { diff --git a/package.json b/package.json index 50743590..a5e317e2 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,8 @@ "postinstall": "npm run build", "build": "gulp build", "start": "electron dist", - "serve": "gulp serve", + "watch": "gulp watch", + "serve": "gulp watch", "test": "gulp build && mocha", "package": "gulp package", "package:windows": "gulp package:windows", From 528bd0241415682cdcfcabf2a97ac99d4ad1a5d7 Mon Sep 17 00:00:00 2001 From: Yuya Ochiai Date: Fri, 12 Feb 2016 00:44:16 +0900 Subject: [PATCH 4/5] Use numerical badge on Windows taskbar icon --- src/browser/index.jsx | 47 +++++++++++++++++++++++++++++----------- src/browser/js/badge.js | 29 +++++++++++++++++++++++++ src/main.js | 8 ++++++- src/resources/badge.png | Bin 293 -> 0 bytes 4 files changed, 70 insertions(+), 14 deletions(-) create mode 100644 src/browser/js/badge.js delete mode 100644 src/resources/badge.png diff --git a/src/browser/index.jsx b/src/browser/index.jsx index 09d09ad7..3e4aa131 100644 --- a/src/browser/index.jsx +++ b/src/browser/index.jsx @@ -300,24 +300,45 @@ window.addEventListener('contextmenu', function(e) { menu.popup(remote.getCurrentWindow()); }, false); +var showUnreadBadgeWindows = function(unreadCount, mentionCount) { + const badge = require('./js/badge'); + const sendBadge = function(dataURL, description) { + // window.setOverlayIcon() does't work with NativeImage across remote boundaries. + // https://github.com/atom/electron/issues/4011 + electron.ipcRenderer.send('win32-overlay', { + overlayDataURL: dataURL, + description: description + }); + }; + + if (mentionCount > 0) { + const dataURL = badge.createDataURL(mentionCount.toString()); + sendBadge(dataURL, 'You have unread mention (' + mentionCount + ')'); + } else if (unreadCount > 0) { + const dataURL = badge.createDataURL('•'); + sendBadge(dataURL, 'You have unread channels'); + } else { + remote.getCurrentWindow().setOverlayIcon(null, ''); + } +} + +var showUnreadBadgeOSX = function(unreadCount, mentionCount) { + if (mentionCount > 0) { + remote.app.dock.setBadge(mentionCount.toString()); + } else if (unreadCount > 0) { + remote.app.dock.setBadge('•'); + } else { + remote.app.dock.setBadge(''); + } +} + var showUnreadBadge = function(unreadCount, mentionCount) { switch (process.platform) { case 'win32': - var window = remote.getCurrentWindow(); - if (unreadCount > 0 || mentionCount > 0) { - window.setOverlayIcon(path.join(__dirname, '../resources/badge.png'), 'You have unread channels.'); - } else { - window.setOverlayIcon(null, ''); - } + showUnreadBadgeWindows(unreadCount, mentionCount); break; case 'darwin': - if (mentionCount > 0) { - remote.app.dock.setBadge(mentionCount.toString()); - } else if (mentionCount < unreadCount) { - remote.app.dock.setBadge('•'); - } else { - remote.app.dock.setBadge(''); - } + showUnreadBadgeOSX(unreadCount, mentionCount); break; default: } diff --git a/src/browser/js/badge.js b/src/browser/js/badge.js new file mode 100644 index 00000000..b77e1f22 --- /dev/null +++ b/src/browser/js/badge.js @@ -0,0 +1,29 @@ +'use strict'; + +var createDataURL = function(text) { + const scale = 2; // should rely display dpi + const size = 16 * scale; + const canvas = document.createElement('canvas'); + canvas.setAttribute('width', size); + canvas.setAttribute('height', size); + const ctx = canvas.getContext('2d'); + + // circle + ctx.fillStyle = "#FF1744"; // Material Red A400 + ctx.beginPath(); + ctx.arc(size / 2, size / 2, size / 2, 0, Math.PI * 2); + ctx.fill(); + + // text + ctx.fillStyle = "#ffffff" + ctx.textAlign = 'center'; + ctx.textBaseline = 'middle'; + ctx.font = (11 * scale) + "px sans-serif"; + ctx.fillText(text, size / 2, size / 2, size); + + return canvas.toDataURL(); +}; + +module.exports = { + createDataURL: createDataURL +}; diff --git a/src/main.js b/src/main.js index c1681ca0..8df87f72 100644 --- a/src/main.js +++ b/src/main.js @@ -83,8 +83,8 @@ app.on('before-quit', function() { // This method will be called when Electron has finished // initialization and is ready to create browser windows. app.on('ready', function() { - // set up tray icon to show balloon if (process.platform === 'win32') { + // set up tray icon to show balloon trayIcon = new Tray(path.resolve(__dirname, 'resources/tray.png')); trayIcon.setToolTip(app.getName()); var tray_menu = require('./main/menus/tray').createDefault(); @@ -102,6 +102,12 @@ app.on('ready', function() { content: arg.options.body }); }); + + // Set overlay icon from dataURL + ipc.on('win32-overlay', function(event, arg) { + var overlay = electron.nativeImage.createFromDataURL(arg.overlayDataURL); + mainWindow.setOverlayIcon(overlay, arg.description); + }); } // Create the browser window. diff --git a/src/resources/badge.png b/src/resources/badge.png deleted file mode 100644 index dfc562243db9955b7b8a0a050f2fd00098a74cf6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 293 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`EX7WqAsj$Z!;#Vf4nJ zFeZU8W4!u>V4$F6iEBhjaDG}zd16s2LwR|*US?i)adKios$PCk`s{Z$Qb0wEJY5_^ zEP9tt_U1ceAmALogJ(YPsvR6_ckr#MI=rsolVx|L$<#$EDYueNcK4+CADet&e!H--GyOrFX5o|VnN^(Utv_Yq<3hTiWt zX1!c?qm}JjRgi{>M3{t)llJrPHs${uKProuaPyqo-;2+)w>+O;O1ENa i|9IlAWBj=i)qNLUos?M+uu}-=S_V&7KbLh*2~7at$!$si From cec2b0f5dc70df838ee1dd2f04532bd9e23e057b Mon Sep 17 00:00:00 2001 From: Yuya Ochiai Date: Fri, 12 Feb 2016 01:12:28 +0900 Subject: [PATCH 5/5] Fix prettify-task --- gulpfile.js | 5 ++--- src/browser/index.jsx | 2 +- src/browser/settings.jsx | 16 ++++++++-------- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/gulpfile.js b/gulpfile.js index f9ff198d..7cf0908e 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -14,7 +14,6 @@ var electron = require('electron-connect').server.create({ var packager = require('electron-packager'); var sources = ['**/*.js', '**/*.css', '**/*.html', '!**/node_modules/**', '!**/build/**', '!release/**']; -var app_root = 'dist'; gulp.task('prettify', ['prettify:sources', 'prettify:jsx']); @@ -43,7 +42,7 @@ gulp.task('prettify:jsx', function() { }, plugins: ['esformatter-jsx'] })) - .pipe(gulp.dest(app_root)); + .pipe(gulp.dest('src/browser')); }); gulp.task('build', ['sync-meta', 'webpack', 'copy'], function() { @@ -141,7 +140,7 @@ gulp.task('watch', ['build'], function() { function makePackage(platform, arch, callback) { var packageJson = require('./src/package.json'); packager({ - dir: './' + app_root, + dir: './dist', name: packageJson.name, platform: platform, arch: arch, diff --git a/src/browser/index.jsx b/src/browser/index.jsx index 3e4aa131..dc3324b3 100644 --- a/src/browser/index.jsx +++ b/src/browser/index.jsx @@ -130,7 +130,7 @@ var MainPage = React.createClass({ tabs_row = ( + activeKey={ this.state.key } onSelect={ this.handleSelect }> ); } diff --git a/src/browser/settings.jsx b/src/browser/settings.jsx index e57ff722..c25e53a8 100644 --- a/src/browser/settings.jsx +++ b/src/browser/settings.jsx @@ -16,7 +16,7 @@ const ListGroup = ReactBootstrap.ListGroup; const ListGroupItem = ReactBootstrap.ListGroupItem; const Glyphicon = ReactBootstrap.Glyphicon; -function backToIndex(){ +function backToIndex() { window.location = 'index.html'; } @@ -64,8 +64,8 @@ var SettingsPage = React.createClass({ var teams_row = ( -

Teams

- +

Teams

+
); @@ -77,8 +77,8 @@ var SettingsPage = React.createClass({ var options_row = (options.length > 0) ? ( -

Options

- { options } +

Options

+ { options }
) : null; @@ -89,9 +89,9 @@ var SettingsPage = React.createClass({ { options_row } - - { ' ' } - + + { ' ' } +