From d01d3868eb8c119f9eee24337401b81f7b267954 Mon Sep 17 00:00:00 2001 From: Guillermo Rauch Date: Tue, 4 Oct 2016 12:41:54 -0700 Subject: [PATCH] simple version-based notifications system --- lib/actions/index.js | 4 ++ lib/actions/notifications.js | 65 ++++++++++++++++++++++++++++++++- lib/components/notification.js | 1 + lib/components/notifications.js | 28 ++++++++++++++ lib/constants/notifications.js | 1 + lib/containers/notifications.js | 10 +++++ lib/index.js | 2 + lib/reducers/ui.js | 20 +++++++++- package.json | 7 ++-- 9 files changed, 132 insertions(+), 6 deletions(-) diff --git a/lib/actions/index.js b/lib/actions/index.js index 8a0fa3b7..432a4b01 100644 --- a/lib/actions/index.js +++ b/lib/actions/index.js @@ -1,5 +1,9 @@ +import {INIT} from '../constants/index'; import rpc from '../rpc'; export function init() { rpc.emit('init'); + return { + type: INIT + } } diff --git a/lib/actions/notifications.js b/lib/actions/notifications.js index 10cddfd7..b33e1f8d 100644 --- a/lib/actions/notifications.js +++ b/lib/actions/notifications.js @@ -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); + }); +} diff --git a/lib/components/notification.js b/lib/components/notification.js index e57f127e..ada20b62 100644 --- a/lib/components/notification.js +++ b/lib/components/notification.js @@ -83,6 +83,7 @@ export default class Notification extends Component { [x] : null } diff --git a/lib/components/notifications.js b/lib/components/notifications.js index 5569e3b3..c61697f5 100644 --- a/lib/components/notifications.js +++ b/lib/components/notifications.js @@ -36,6 +36,34 @@ export default class Notifications extends Component { /> } + { + this.props.messageShowing && + { + this.props.messageURL ? [ + this.props.messageText, + ' (', + { + window.require('electron').shell.openExternal(ev.target.href); + ev.preventDefault(); + }} + href={this.props.messageURL} + >more, + ')' + ] : null + } + + } + { this.props.updateShowing && { dispatch(dismissNotification('updates')); }, + onDismissMessage: () => { + dispatch(dismissNotification('message')); + }, onUpdateInstall: () => { dispatch(installUpdate()); } diff --git a/lib/index.js b/lib/index.js index aec419ca..818bf2df 100644 --- a/lib/index.js +++ b/lib/index.js @@ -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()); }); diff --git a/lib/reducers/ui.js b/lib/reducers/ui.js index cd74901e..4a108c85 100644 --- a/lib/reducers/ui.js +++ b/lib/reducers/ui.js @@ -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}); } diff --git a/package.json b/package.json index 55ddf17e..c2e03edb 100644 --- a/package.json +++ b/package.json @@ -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",