Merge branch 'master' into dev

This commit is contained in:
Yuya Ochiai
2016-06-06 21:30:23 +09:00
16 changed files with 570 additions and 88 deletions

View File

@@ -326,6 +326,7 @@ var MattermostView = React.createClass({
var currentURL = url.parse(webview.getURL());
var destURL = url.parse(e.url);
if (destURL.protocol !== 'http:' && destURL.protocol !== 'https:') {
ipcRenderer.send('confirm-protocol', destURL.protocol, e.url);
return;
}
if (currentURL.host === destURL.host) {
@@ -473,11 +474,11 @@ var showUnreadBadgeWindows = function(unreadCount, mentionCount) {
};
if (mentionCount > 0) {
const dataURL = badge.createDataURL(mentionCount.toString());
sendBadge(dataURL, 'You have unread mention (' + mentionCount + ')');
const dataURL = badge.createDataURL(mentionCount.toString(), "#FF1744", "#580817"); // Material Red A400
sendBadge(dataURL, 'You have unread mentions (' + mentionCount + ')');
} else if (unreadCount > 0) {
const dataURL = badge.createDataURL('•');
sendBadge(dataURL, 'You have unread channels');
const dataURL = badge.createDataURL('•', "#00e5ff", "#06545D"); // Material Cyan A400
sendBadge(dataURL, 'You have unread channels (' + unreadCount + ')');
} else {
sendBadge(null, 'You have no unread messages');
}

View File

@@ -1,6 +1,6 @@
'use strict';
var createDataURL = function(text) {
var createDataURL = function(text, color, circleColor) {
const scale = 2; // should rely display dpi
const size = 16 * scale;
const canvas = document.createElement('canvas');
@@ -9,10 +9,13 @@ var createDataURL = function(text) {
const ctx = canvas.getContext('2d');
// circle
ctx.fillStyle = "#FF1744"; // Material Red A400
ctx.fillStyle = color;
ctx.beginPath();
ctx.arc(size / 2, size / 2, size / 2, 0, Math.PI * 2);
ctx.fill();
ctx.lineWidth = 2;
ctx.strokeStyle = circleColor;
ctx.stroke();
// text
ctx.fillStyle = "#ffffff"

View File

@@ -28,14 +28,21 @@ var SettingsPage = React.createClass({
} catch (e) {
config = settings.loadDefault();
}
this.setState({
showAddTeamForm: false
});
return config;
},
handleTeamsChange: function(teams) {
this.setState({
teams: teams
});
this.handleSave(false);
},
handleSave: function() {
handleSave: function(toIndex) {
var config = {
teams: this.state.teams,
hideMenuBar: this.state.hideMenuBar,
@@ -50,8 +57,12 @@ var SettingsPage = React.createClass({
currentWindow.setAutoHideMenuBar(config.hideMenuBar);
currentWindow.setMenuBarVisibility(!config.hideMenuBar);
}
ipcRenderer.send('update-menu', config);
backToIndex();
if (typeof toIndex == 'undefined' || toIndex) {
backToIndex();
}
},
handleCancel: function() {
backToIndex();
@@ -76,12 +87,27 @@ var SettingsPage = React.createClass({
trayIconTheme: this.refs.trayIconTheme.getValue()
});
},
handleShowTeamForm: function() {
if (!this.state.showAddTeamForm) {
this.setState({
showAddTeamForm: true
});
} else {
this.setState({
showAddTeamForm: false
});
}
},
render: function() {
var buttonStyle = {
marginTop: 20
};
var teams_row = (
<Row>
<Col md={ 12 }>
<h2>Teams</h2>
<TeamList teams={ this.state.teams } onTeamsChange={ this.handleTeamsChange } />
<TeamList teams={ this.state.teams } showAddTeamForm={ this.state.showAddTeamForm } onTeamsChange={ this.handleTeamsChange } />
</Col>
</Row>
);
@@ -114,6 +140,16 @@ var SettingsPage = React.createClass({
return (
<Grid className="settingsPage">
<Row>
<Col xs={ 4 } sm={ 1 } md={ 2 } lg={ 2 }>
<h2>Teams</h2>
</Col>
<Col xs={ 4 } sm={ 2 } md={ 1 } lg={ 1 } mdPull={ 1 }>
<Button className="pull-right" style={ buttonStyle } bsSize="small" onClick={ this.handleShowTeamForm }>
<Glyphicon glyph="plus" />
</Button>
</Col>
</Row>
{ teams_row }
{ options_row }
<Row>
@@ -129,6 +165,16 @@ var SettingsPage = React.createClass({
});
var TeamList = React.createClass({
getInitialState: function() {
return {
showTeamListItemNew: false,
team: {
url: '',
name: '',
index: false
}
};
},
handleTeamRemove: function(index) {
console.log(index);
var teams = this.props.teams;
@@ -137,23 +183,64 @@ var TeamList = React.createClass({
},
handleTeamAdd: function(team) {
var teams = this.props.teams;
teams.push(team);
// check if team already exists and then change existing team or add new one
if (!team.index && teams[team.index]) {
teams[team.index].name = team.name;
teams[team.index].url = team.url;
} else {
teams.push(team);
}
this.setState({
showTeamListItemNew: false,
team: {
url: '',
name: '',
index: false
}
});
this.props.onTeamsChange(teams);
},
handleTeamEditing: function(teamName, teamUrl, teamIndex) {
this.setState({
showTeamListItemNew: true,
team: {
url: teamUrl,
name: teamName,
index: teamIndex
}
})
},
render: function() {
var thisObj = this;
var teamNodes = this.props.teams.map(function(team, i) {
var handleTeamRemove = function() {
thisObj.handleTeamRemove(i);
};
var handleTeamEditing = function() {
thisObj.handleTeamEditing(team.name, team.url, i);
};
return (
<TeamListItem index={ i } key={ "teamListItem" + i } name={ team.name } url={ team.url } onTeamRemove={ handleTeamRemove } />
<TeamListItem index={ i } key={ "teamListItem" + i } name={ team.name } url={ team.url } onTeamRemove={ handleTeamRemove } onTeamEditing={ handleTeamEditing }
/>
);
});
var addTeamForm;
if (this.props.showAddTeamForm || this.state.showTeamListItemNew) {
addTeamForm = <TeamListItemNew onTeamAdd={ this.handleTeamAdd } teamIndex={ this.state.team.index } teamName={ this.state.team.name } teamUrl={ this.state.team.url } />;
} else {
addTeamForm = '';
}
return (
<ListGroup class="teamList">
{ teamNodes }
<TeamListItemNew onTeamAdd={ this.handleTeamAdd } />
{ addTeamForm }
</ListGroup>
);
}
@@ -163,6 +250,9 @@ var TeamListItem = React.createClass({
handleTeamRemove: function() {
this.props.onTeamRemove();
},
handleTeamEditing: function() {
this.props.onTeamEditing();
},
render: function() {
var style = {
left: {
@@ -178,6 +268,10 @@ var TeamListItem = React.createClass({
</p>
</div>
<div className="pull-right">
<Button bsSize="xsmall" onClick={ this.handleTeamEditing }>
<Glyphicon glyph="pencil" />
</Button>
{ ' ' }
<Button bsSize="xsmall" onClick={ this.handleTeamRemove }>
<Glyphicon glyph="remove" />
</Button>
@@ -190,8 +284,9 @@ var TeamListItem = React.createClass({
var TeamListItemNew = React.createClass({
getInitialState: function() {
return {
name: '',
url: ''
name: this.props.teamName,
url: this.props.teamUrl,
index: this.props.teamIndex
};
},
handleSubmit: function(e) {
@@ -199,9 +294,15 @@ var TeamListItemNew = React.createClass({
e.preventDefault();
this.props.onTeamAdd({
name: this.state.name.trim(),
url: this.state.url.trim()
url: this.state.url.trim(),
index: this.state.index,
});
this.setState({
name: '',
url: '',
index: ''
});
this.setState(this.getInitialState());
},
handleNameChange: function(e) {
console.log('name');
@@ -216,9 +317,23 @@ var TeamListItemNew = React.createClass({
});
},
shouldEnableAddButton: function() {
return (this.state.name.trim() !== '') && (this.state.url.trim() !== '');
return (this.state.name.trim() !== '' || this.props.teamName !== '')
&& (this.state.url.trim() !== '' || this.props.teamUrl !== '');
},
render: function() {
var existingTeam = false;
if (this.state.name !== '' && this.state.url !== '') {
existingTeam = true;
}
var btnAddText;
if (existingTeam) {
btnAddText = 'Save';
} else {
btnAddText = 'Add';
}
return (
<ListGroupItem>
<form className="form-inline" onSubmit={ this.handleSubmit }>
@@ -234,7 +349,9 @@ var TeamListItemNew = React.createClass({
<input type="url" className="form-control" id="inputTeamURL" placeholder="https://example.com/team" value={ this.state.url } onChange={ this.handleURLChange } />
</div>
{ ' ' }
<Button type="submit" disabled={ !this.shouldEnableAddButton() }>Add</Button>
<Button type="submit" disabled={ !this.shouldEnableAddButton() }>
{ btnAddText }
</Button>
</form>
</ListGroupItem>
);

View File

@@ -2,6 +2,9 @@
const electron = require('electron');
const app = electron.app; // Module to control application life.
if (require('electron-squirrel-startup')) app.quit();
const BrowserWindow = electron.BrowserWindow; // Module to create native browser window.
const Menu = electron.Menu;
const Tray = electron.Tray;
@@ -13,6 +16,7 @@ const path = require('path');
var settings = require('./common/settings');
var certificateStore = require('./main/certificateStore').load(path.resolve(app.getPath('userData'), 'certificate.json'));
var appMenu = require('./main/menus/app');
const allowProtocolDialog = require('./main/allowProtocolDialog');
var argv = require('yargs').argv;
@@ -161,6 +165,8 @@ app.on('login', function(event, webContents, request, authInfo, callback) {
mainWindow.webContents.send('login-request', request, authInfo);
});
allowProtocolDialog.init(mainWindow);
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
app.on('ready', function() {
@@ -168,11 +174,12 @@ app.on('ready', function() {
// set up tray icon
trayIcon = new Tray(trayImages.normal);
trayIcon.setToolTip(app.getName());
var tray_menu = require('./main/menus/tray').createDefault();
trayIcon.setContextMenu(tray_menu);
trayIcon.on('click', function() {
mainWindow.focus();
});
trayIcon.on('right-click', () => {
trayIcon.popUpContextMenu();
});
trayIcon.on('balloon-click', function() {
mainWindow.focus();
});
@@ -194,12 +201,15 @@ app.on('ready', function() {
if (arg.mentionCount > 0) {
trayIcon.setImage(trayImages.mention);
trayIcon.setToolTip(arg.mentionCount + ' unread mentions');
}
else if (arg.unreadCount > 0) {
trayIcon.setImage(trayImages.unread);
trayIcon.setToolTip(arg.unreadCount + ' unread channels');
}
else {
trayIcon.setImage(trayImages.normal);
trayIcon.setToolTip(app.getName());
}
});
}
@@ -218,7 +228,7 @@ app.on('ready', function() {
// On HiDPI Windows environment, the taskbar icon is pixelated. So this line is necessary.
window_options.icon = path.resolve(__dirname, 'resources/appicon.png');
}
window_options.fullScreenable = true;
window_options.title = app.getName();
mainWindow = new BrowserWindow(window_options);
mainWindow.setFullScreenable(true); // fullscreenable option has no effect.
if (window_options.maximized) {
@@ -238,6 +248,12 @@ app.on('ready', function() {
});
ipc.emit('update-menu', true, config);
// set up context menu for tray icon
if (shouldShowTrayIcon()) {
const tray_menu = require('./main/menus/tray').createDefault(mainWindow);
trayIcon.setContextMenu(tray_menu);
}
// Open the DevTools.
// mainWindow.openDevTools();

View File

@@ -0,0 +1,61 @@
'use strict';
const {
app,
dialog,
ipcMain
} = require('electron');
const path = require('path');
const fs = require('fs');
const allowedProtocolFile = path.resolve(app.getPath('userData'), 'allowedProtocols.json')
var allowedProtocols = [];
function init(mainWindow) {
fs.readFile(allowedProtocolFile, 'utf-8', (err, data) => {
if (!err) {
allowedProtocols = JSON.parse(data);
}
initDialogEvent(mainWindow);
});
}
function initDialogEvent(mainWindow) {
ipcMain.on('confirm-protocol', (event, protocol, URL) => {
if (allowedProtocols.indexOf(protocol) !== -1) {
require('shell').openExternal(URL);
return;
}
dialog.showMessageBox(mainWindow, {
title: 'Non http(s) protocol',
message: `${protocol} link requires an external application.`,
detail: `The requested link is ${URL} . Do you want to continue?`,
type: 'warning',
buttons: [
'Yes',
`Yes (Save ${protocol} as allowed)`,
'No'
],
cancelId: 2,
noLink: true
}, (response) => {
switch (response) {
case 1:
allowedProtocols.push(protocol);
fs.writeFile(allowedProtocolFile, JSON.stringify(allowedProtocols), (err) => {
if (err) console.error(err);
});
// fallthrough
case 0:
require('shell').openExternal(URL);
break;
default:
break;
}
});
});
}
module.exports = {
init: init
};

View File

@@ -13,6 +13,25 @@ var createTemplate = function(mainWindow, config) {
var template = [];
const platformAppMenu = process.platform === 'darwin' ? [{
label: 'About ' + app_name,
role: 'about',
click: function(item, focusedWindow) {
electron.dialog.showMessageBox(mainWindow, {
buttons: ["OK"],
message: `${app_name} Desktop ${electron.app.getVersion()}`
});
}
}, {
type: 'separator'
}, {
label: 'Preferences...',
accelerator: 'CmdOrCtrl+,',
click: function(item, focusedWindow) {
mainWindow.loadURL('file://' + __dirname + '/browser/settings.html');
}
}, {
type: 'separator'
}, {
label: 'Hide ' + app_name,
accelerator: 'Command+H',
selector: 'hide:'
@@ -25,36 +44,33 @@ var createTemplate = function(mainWindow, config) {
selector: 'unhideAllApplications:'
}, {
type: 'separator'
}] : [];
}, {
label: 'Quit ' + app_name,
accelerator: 'CmdOrCtrl+Q',
click: function(item, focusedWindow) {
electron.app.quit();
}
}] : [{
label: 'Settings',
accelerator: 'CmdOrCtrl+,',
click: function(item, focusedWindow) {
mainWindow.loadURL('file://' + __dirname + '/browser/settings.html');
}
}, {
type: 'separator'
}, {
label: 'Quit',
accelerator: 'CmdOrCtrl+Q',
click: function(item, focusedWindow) {
electron.app.quit();
}
}];
template.push({
label: '&' + first_menu_name,
submenu: [{
label: 'About ' + app_name,
role: 'about',
click: function(item, focusedWindow) {
electron.dialog.showMessageBox(mainWindow, {
buttons: ["OK"],
message: `${app_name} Desktop ${electron.app.getVersion()}`
});
}
}, {
type: 'separator'
}, {
label: (process.platform === 'darwin') ? 'Preferences...' : 'Settings',
accelerator: 'CmdOrCtrl+,',
click: function(item, focusedWindow) {
mainWindow.loadURL('file://' + __dirname + '/browser/settings.html');
}
}, {
type: 'separator'
}, ...platformAppMenu, {
label: 'Quit',
accelerator: 'CmdOrCtrl+Q',
click: function(item, focusedWindow) {
electron.app.quit();
}
}]
submenu: [
...platformAppMenu
]
});
template.push({
label: '&Edit',
@@ -122,6 +138,8 @@ var createTemplate = function(mainWindow, config) {
focusedWindow.setFullScreen(!focusedWindow.isFullScreen());
}
}
}, {
type: 'separator'
}, {
label: 'Toggle Developer Tools',
accelerator: (function() {
@@ -205,6 +223,13 @@ var createTemplate = function(mainWindow, config) {
}
template.push(window_menu);
template.push({
label: '&Help',
submenu: [{
label: `Version ${electron.app.getVersion()}`,
enabled: false
}, ]
});
return template;
};

View File

@@ -1,18 +1,25 @@
'use strict';
const electron = require('electron');
const Menu = electron.Menu;
const MenuItem = electron.MenuItem;
const {
app,
Menu,
MenuItem
} = require('electron');
var createDefault = function() {
var menu = new Menu();
menu.append(new MenuItem({
function createDefault(mainWindow) {
return Menu.buildFromTemplate([{
label: `Open ${app.getName()}`,
click: () => {
mainWindow.show();
}
}, {
type: 'separator'
}, {
label: 'Quit',
click: function(item) {
require('app').quit();
app.quit();
}
}));
return menu;
}]);
}
module.exports = {

View File

@@ -1,14 +1,14 @@
{
"name": "mattermost-desktop",
"productName": "Mattermost",
"version": "1.2.0",
"version": "1.2.1",
"description": "Mattermost Desktop application for Windows, Mac and Linux",
"main": "main.js",
"author": {
"name": "Yuya Ochiai",
"email": "yuya0321@gmail.com"
},
"license": "MIT",
"license": "Apache-2.0",
"devDependencies": {
"electron-connect": "^0.3.3"
},