simple version-based notifications system

This commit is contained in:
Guillermo Rauch 2016-10-04 12:41:54 -07:00
parent 71ae9b7e00
commit d01d3868eb
9 changed files with 132 additions and 6 deletions

View file

@ -1,5 +1,9 @@
import {INIT} from '../constants/index';
import rpc from '../rpc';
export function init() {
rpc.emit('init');
return {
type: INIT
}
}

View file

@ -1,4 +1,11 @@
import {NOTIFICATION_DISMISS} from '../constants/notifications';
import ms from 'ms';
import {version} from '../../package';
import {satisfies} from 'semver';
import {remote} from 'electron';
import {
NOTIFICATION_MESSAGE,
NOTIFICATION_DISMISS
} from '../constants/notifications';
export function dismissNotification(id) {
return {
@ -6,3 +13,59 @@ export function dismissNotification(id) {
id
};
}
export function addNotificationMessage(text, url = null, dismissable = true) {
return {
type: NOTIFICATION_MESSAGE,
text,
url,
dismissable
};
}
export function fetchNotifications() {
return dispatch => {
const retry = err => {
setTimeout(() => dispatch(fetchNotifications()), ms(err ? '10s' : '5m'));
if (err) {
console.error('Notification messages fetch error', err.stack);
}
};
console.log('Checking for notification messages');
fetch('https://hyper-news.now.sh')
.then(res => {
res.json()
.then(data => {
const {messages} = data || {};
if (!messages) {
throw new Error('Bad response');
}
const message = messages.filter(msg => {
return matchVersion(msg.versions);
})[0];
if (message) {
dispatch(addNotificationMessage(
message.text,
message.url,
message.dismissable
));
} else {
console.log('No matching notification messages');
}
retry();
})
.catch(retry);
})
.catch(retry);
};
}
function matchVersion(versions) {
return versions.some(v => {
if (v === '*') {
return true;
}
return satisfies(version, v);
});
}

View file

@ -83,6 +83,7 @@ export default class Notification extends Component {
<a
className={css('dismissLink')}
onClick={this.handleDismiss}
style={{ color: this.props.userDismissColor }}
>[x]</a> :
null
}

View file

@ -36,6 +36,34 @@ export default class Notifications extends Component {
/>
}
{
this.props.messageShowing &&
<Notification
key="message"
backgroundColor="#FE354E"
text={this.props.messageText}
onDismiss={this.props.onDismissMessage}
userDismissable={this.props.messageDismissable}
userDismissColor="#AA2D3C"
>{
this.props.messageURL ? [
this.props.messageText,
' (',
<a
key="link"
style={{color: '#fff'}}
onClick={ev => {
window.require('electron').shell.openExternal(ev.target.href);
ev.preventDefault();
}}
href={this.props.messageURL}
>more</a>,
')'
] : null
}
</Notification>
}
{
this.props.updateShowing &&
<Notification

View file

@ -1 +1,2 @@
export const NOTIFICATION_MESSAGE = 'NOTIFICATION_MESSAGE';
export const NOTIFICATION_DISMISS = 'NOTIFICATION_DISMISS';

View file

@ -36,6 +36,13 @@ const NotificationsContainer = connect(
updateVersion: ui.updateVersion,
updateNote: ui.updateNotes.split('\n')[0]
});
} else if (notifications.message) {
Object.assign(state_, {
messageShowing: true,
messageText: ui.messageText,
messageURL: ui.messageURL,
messageDismissable: ui.messageDismissable
});
}
return state_;
@ -51,6 +58,9 @@ const NotificationsContainer = connect(
onDismissUpdate: () => {
dispatch(dismissNotification('updates'));
},
onDismissMessage: () => {
dispatch(dismissNotification('message'));
},
onUpdateInstall: () => {
dispatch(installUpdate());
}

View file

@ -12,6 +12,7 @@ import * as uiActions from './actions/ui';
import * as updaterActions from './actions/updater';
import * as sessionActions from './actions/sessions';
import * as termGroupActions from './actions/term-groups';
import {fetchNotifications} from './actions/notifications';
import HyperTermContainer from './containers/hyperterm';
import {loadConfig, reloadConfig} from './actions/config';
import configureStore from './store/configure-store';
@ -36,6 +37,7 @@ config.subscribe(() => {
// and subscribe to all user intents for example from menus
rpc.on('ready', () => {
store_.dispatch(init());
store_.dispatch(fetchNotifications());
store_.dispatch(uiActions.setFontSmoothing());
});

View file

@ -8,7 +8,7 @@ import {
UI_WINDOW_MAXIMIZE,
UI_WINDOW_UNMAXIMIZE
} from '../constants/ui';
import {NOTIFICATION_DISMISS} from '../constants/notifications';
import {NOTIFICATION_MESSAGE, NOTIFICATION_DISMISS} from '../constants/notifications';
import {
SESSION_ADD,
SESSION_RESIZE,
@ -62,13 +62,17 @@ const initial = Immutable({
notifications: {
font: false,
resize: false,
updates: false
updates: false,
message: false
},
foregroundColor: '#fff',
backgroundColor: '#000',
maximized: false,
updateVersion: null,
updateNotes: null,
messageText: null,
messageURL: null,
messageDismissable: null,
bell: 'SOUND',
bellSoundURL: 'lib-resource:hterm/audio/bell',
copyOnSelect: false,
@ -273,6 +277,14 @@ const reducer = (state = initial, action) => {
}, {deep: true});
break;
case NOTIFICATION_MESSAGE:
state_ = state.merge({
messageText: action.text,
messageURL: action.url || null,
messageDismissable: action.dismissable === true
});
break;
case UPDATE_AVAILABLE:
state_ = state.merge({
updateVersion: action.version,
@ -296,6 +308,10 @@ const reducer = (state = initial, action) => {
state_ = state_.merge({notifications: {resize: true}}, {deep: true});
}
if (state.messageText !== state_.messageText || state.messageURL !== state_.messageURL) {
state_ = state_.merge({notifications: {message: true}}, {deep: true});
}
if (state.updateVersion !== state_.updateVersion) {
state_ = state_.merge({notifications: {updates: true}}, {deep: true});
}

View file

@ -1,6 +1,6 @@
{
"name": "hyperterm",
"version": "1.0.0",
"version": "0.8.0",
"description": "Web app that runs in the renderer process",
"repository": "zeit/hyperterm",
"license": "MIT",
@ -28,8 +28,9 @@
"redux": "3.6.0",
"redux-thunk": "2.1.0",
"reselect": "2.5.4",
"uuid": "2.0.2",
"seamless-immutable": "6.1.3"
"seamless-immutable": "6.1.3",
"semver": "5.3.0",
"uuid": "2.0.2"
},
"devDependencies": {
"ava": "^0.16.0",