diff --git a/app/index.js b/app/index.js index eecf46c1..e8b7daba 100644 --- a/app/index.js +++ b/app/index.js @@ -23,6 +23,15 @@ app.config = config; app.plugins = plugins; app.getWindows = () => new Set([...windowSet]); // return a clone +// function to retrive the last focused window in windowSet; +// added to app object in order to expose it to plugins. +app.getLastFocusedWindow = () => { + if (!windowSet.size) return null; + return Array.from(windowSet).reduce((lastWindow, win) => { + return win.focusTime > lastWindow.focusTime ? win : lastWindow; + }); +}; + if (isDev) { console.log('running in dev mode'); } else { @@ -61,6 +70,7 @@ app.on('ready', () => { const win = new BrowserWindow(browserOptions); windowSet.add(win); + win.loadURL(url); const rpc = createRPC(win); @@ -84,7 +94,17 @@ app.on('ready', () => { rpc.on('init', () => { win.show(); - if (fn) fn(win); + + // If no callback is passed to createWindow, + // a new session will be created by default. + if (!fn) fn = (win) => win.rpc.emit('session add req'); + + // app.windowCallback is the createWindow callback + // that can be setted before the 'ready' app event + // and createWindow deifinition. It's exeuted in place of + // the callback passed as parameter, and deleted right after. + (app.windowCallback || fn)(win); + delete (app.windowCallback); // auto updates if (!isDev && process.platform !== 'linux') { @@ -214,6 +234,13 @@ app.on('ready', () => { } }); + // Keep track of focus time of every window, to figure out + // which one of the existing window is the last focused. + // Works nicely even if a window is closed and removed. + win.on('focus', () => { + win.focusTime = process.uptime(); + }); + // the window can be closed by the browser process itself win.on('close', () => { windowSet.delete(win); @@ -268,3 +295,17 @@ app.on('ready', () => { function initSession (opts, fn) { fn(uuid.v4(), new Session(opts)); } + +app.on('open-file', (event, path) => { + const lastWindow = app.getLastFocusedWindow(); + const callback = win => win.rpc.emit('open file', { path }); + if (lastWindow) { + callback(lastWindow); + } else if (!lastWindow && app.hasOwnProperty('createWindow')) { + app.createWindow(callback); + } else { + // if createWindow not exists yet ('ready' event was not fired), + // sets his callback to an app.windowCallback property. + app.windowCallback = callback; + } +}); diff --git a/hyperterm-web-0.0.1.tgz b/hyperterm-web-0.0.1.tgz new file mode 100644 index 00000000..d1751743 Binary files /dev/null and b/hyperterm-web-0.0.1.tgz differ diff --git a/lib/actions/index.js b/lib/actions/index.js index e44c73cc..b4f7a714 100644 --- a/lib/actions/index.js +++ b/lib/actions/index.js @@ -1,7 +1,5 @@ -import { requestSession } from './sessions'; +import rpc from '../rpc'; export function init () { - return (dispatch) => { - dispatch(requestSession()); - }; + rpc.emit('init'); } diff --git a/lib/actions/sessions.js b/lib/actions/sessions.js index 0a003f51..7e70bafc 100644 --- a/lib/actions/sessions.js +++ b/lib/actions/sessions.js @@ -21,14 +21,6 @@ import { export function addSession (uid, shell, pid) { return (dispatch, getState) => { - const { sessions } = getState(); - - // normally this would be encoded as an effect - // but the `SESSION_ADD` action is pretty expensive - // and we want to get this out as soon as possible - const initial = null == sessions.activeUid; - if (initial) rpc.emit('init'); - dispatch({ type: SESSION_ADD, uid, diff --git a/lib/actions/ui.js b/lib/actions/ui.js index 0ca2b0a5..50befc93 100644 --- a/lib/actions/ui.js +++ b/lib/actions/ui.js @@ -1,6 +1,9 @@ +import * as shellEscape from 'php-escape-shell'; import { setActiveSession } from './sessions'; import { keys } from '../utils/object'; import { last } from '../utils/array'; +import { isExecutable } from '../utils/file'; +import notify from '../utils/notify'; import rpc from '../rpc'; import { requestSession, @@ -16,9 +19,12 @@ import { UI_MOVE_RIGHT, UI_MOVE_TO, UI_SHOW_PREFERENCES, - UI_WINDOW_MOVE + UI_WINDOW_MOVE, + UI_OPEN_FILE } from '../constants/ui'; +const { stat } = window.require('fs'); + export function increaseFontSize () { return (dispatch, getState) => { dispatch({ @@ -172,3 +178,35 @@ export function windowMove () { }); }; } + +export function openFile (path) { + return (dispatch, getState) => { + dispatch({ + type: UI_OPEN_FILE, + effect () { + stat(path, (err, stats) => { + if (err) { + console.error(err.stack); + notify('Unable to open path', `"${path}" doesn't exist.`); + } else { + // We need to use 'php-escape-shell' property this way + // until this eslint issue will be fixed: + // https://github.com/eslint/eslint/issues/6755 + let command = shellEscape.php_escapeshellcmd(path); + if (stats.isDirectory()) { + command = `cd ${command}\n`; + } else if (stats.isFile() && isExecutable(stats)) { + command += '\n'; + } + rpc.once('session add', ({ uid }) => { + rpc.once('session data', () => { + dispatch(sendSessionData(uid, command)); + }); + }); + } + dispatch(requestSession()); + }); + } + }); + }; +} diff --git a/lib/constants/ui.js b/lib/constants/ui.js index 24814f7c..65389467 100644 --- a/lib/constants/ui.js +++ b/lib/constants/ui.js @@ -8,3 +8,4 @@ export const UI_MOVE_RIGHT = 'UI_MOVE_RIGHT'; export const UI_MOVE_TO = 'UI_MOVE_TO'; export const UI_SHOW_PREFERENCES = 'UI_SHOW_PREFERENCES'; export const UI_WINDOW_MOVE = 'UI_WINDOW_MOVE'; +export const UI_OPEN_FILE = 'UI_OPEN_FILE'; diff --git a/lib/index.js b/lib/index.js index 698003db..f4aa38f3 100644 --- a/lib/index.js +++ b/lib/index.js @@ -100,6 +100,10 @@ rpc.on('preferences', () => { store_.dispatch(uiActions.showPreferences()); }); +rpc.on('open file', ({ path }) => { + store_.dispatch(uiActions.openFile(path)); +}); + rpc.on('update available', ({ releaseName, releaseNotes }) => { store_.dispatch(updaterActions.updateAvailable(releaseName, releaseNotes)); }); diff --git a/lib/utils/file.js b/lib/utils/file.js new file mode 100644 index 00000000..ff2bab65 --- /dev/null +++ b/lib/utils/file.js @@ -0,0 +1,17 @@ +// Based on https://github.com/kevva/executable +// Since this module doesn't expose the function to check stat mode only, +// his logic is pasted here. +// Opened an issue and a pull request about it, +// to maybe switch to module in the future: +// Issue: https://github.com/kevva/executable/issues/9 +// PR: https://github.com/kevva/executable/pull/10 + +export function isExecutable (fileStat) { + if (process.platform === 'win32') return true; + + return Boolean( + (fileStat['mode'] & parseInt('0001', 8)) || + (fileStat['mode'] & parseInt('0010', 8)) || + (fileStat['mode'] & parseInt('0100', 8)) + ); +} diff --git a/package.json b/package.json index 51fef0a9..a5d111ff 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "mousetrap": "1.6.0", "ms": "0.7.1", "object-values": "1.0.0", + "php-escape-shell": "1.0.0", "react": "15.3.0", "react-addons-pure-render-mixin": "15.3.0", "react-deep-force-update": "2.0.1",