hyper/lib/actions/ui.js
Donald Green 5d7142c2df Return of the Bell (#2938)
* 1. Restored the ability to turn the "bell" sound on and off using the "bell" config parameter.
2. Restored the ability to change the bell sound by providing a URL. These changes allow for a web url or local absolute file path to an audio file.

The goal with these changes was to fix the issue causing the bell to never sound due to a difference in the underlying terminal emulators configurations from the previous one. While in the area, also decided to make sure that the sound can be changed by supplying a web url to an audio file or an absolute path to an audio file within the local machine

* Code style changes

* Code style changes

* 1. More code style changes

* 1. Spacing changes to try and abide by the linter

* 1. Applied all suggested changes by eslint

* 1. Removed functionality to specify a remote url to set a sound file for the bell sound. The amount of effort for handling when there is no internet connection, queuing and so forth wasn't worth keeping the feature. It is likely that the url could be used to download the file in which the user would be able to specify the file path tho this download file.
2. Created a new property that gets passed down from the terms container all the way to the individual term. We want to be able to evaluate if the bellSoundURL has changed to determine if we really need to read the sound file.
3. Moved logic to read the audio file into the main process. Setup a new action in the 'actions/ui' that will update the bell sound when it is finished and ready. This should prevent blocking the terminal from loading and thus increasing loading times.

* 1. Modified the file reading method to be more generic to increase reusability.
2. Updated the "arrBuf2Base64" method to utilize the node Buffer class which helped to reduce some complexity and seems to run more efficiently.
3. Removed the CONFIG_ASYNC action and reducer in favor of reusing CONFIG_RELOAD when the process is finished reading the file for the bell sound. In order to achieve this, we had to merge the config from "config.getConfig()" method with the "bellSound" property before dispatching to "reloadConfig".

* 1. Removed reference to now removed method

* 1. Removed the arrBuf2Base64 as it seemed unnecessary now that the function would be reduced to a single line. Moved the one-liner into file.js. Removed references to arrBuf2Base64 method.
2. Refactored the logic that handles reloading the config when it has been updated to fix an issue that would set the bell sound back to the default sound when the config is saved without changing the value of "bellSoundURL". Setup now to either read file and reload the config, or reuse the "bellSound" value saved in state and reload the config. This removes an inefficiency with the reloadConfig being dispatched twice when "bellSoundURL" has changed as well.

* 1. Removed a file that contained a single function, referenced in only one place that is performing a fairly simple task.
2. Updated the "getBase64FileData" method to use "Buffer.from()" instead of "Buffer()" due to messages stating that "Buffer()" is deprecated due to security and usability issues.

* Adjustments and regression issues fixed

1. Updated the default config file to better explain the supported options for the "bell" config property.
2. Rearranged the bellSoundURL default property to make it easier to find should one decide to change the bell sound.
3. Typos fixed in comments.
4. Update fetchFileData to utilize the configData provided as function argument. There appeared to be no reason to reference different sources of config data within the same method.

* 1. Removed the "BELL_STYLE" constant since it was only being used in one place.
2. Updated comment block to accurately reflect the current logic and made the comment much more concise.
2019-10-03 02:08:40 +02:00

334 lines
8.3 KiB
JavaScript

import {php_escapeshellcmd as escapeShellCmd} from 'php-escape-shell';
import {isExecutable} from '../utils/file';
import getRootGroups from '../selectors';
import findBySession from '../utils/term-groups';
import notify from '../utils/notify';
import rpc from '../rpc';
import {requestSession, sendSessionData, setActiveSession} from '../actions/sessions';
import {
UI_FONT_SIZE_SET,
UI_FONT_SIZE_INCR,
UI_FONT_SIZE_DECR,
UI_FONT_SIZE_RESET,
UI_FONT_SMOOTHING_SET,
UI_MOVE_LEFT,
UI_MOVE_RIGHT,
UI_MOVE_TO,
UI_MOVE_NEXT_PANE,
UI_MOVE_PREV_PANE,
UI_WINDOW_GEOMETRY_CHANGED,
UI_WINDOW_MOVE,
UI_OPEN_FILE,
UI_ENTER_FULLSCREEN,
UI_LEAVE_FULLSCREEN,
UI_OPEN_SSH_URL,
UI_CONTEXTMENU_OPEN,
UI_COMMAND_EXEC
} from '../constants/ui';
import {setActiveGroup} from './term-groups';
import parseUrl from 'parse-url';
const {stat} = window.require('fs');
export function openContextMenu(uid, selection) {
return (dispatch, getState) => {
dispatch({
type: UI_CONTEXTMENU_OPEN,
uid,
effect() {
const state = getState();
const show = !state.ui.quickEdit;
if (show) {
rpc.emit('open context menu', selection);
}
}
});
};
}
export function increaseFontSize() {
return (dispatch, getState) => {
dispatch({
type: UI_FONT_SIZE_INCR,
effect() {
const state = getState();
const old = state.ui.fontSizeOverride || state.ui.fontSize;
const value = old + 1;
dispatch({
type: UI_FONT_SIZE_SET,
value
});
}
});
};
}
export function decreaseFontSize() {
return (dispatch, getState) => {
dispatch({
type: UI_FONT_SIZE_DECR,
effect() {
const state = getState();
const old = state.ui.fontSizeOverride || state.ui.fontSize;
// when the font-size is really small, below 5px, xterm starts showing up issues.
const value = old > 5 ? old - 1 : old;
dispatch({
type: UI_FONT_SIZE_SET,
value
});
}
});
};
}
export function resetFontSize() {
return {
type: UI_FONT_SIZE_RESET
};
}
export function setFontSmoothing() {
return dispatch => {
setTimeout(() => {
const devicePixelRatio = window.devicePixelRatio;
const fontSmoothing = devicePixelRatio < 2 ? 'subpixel-antialiased' : 'antialiased';
dispatch({
type: UI_FONT_SMOOTHING_SET,
fontSmoothing
});
}, 100);
};
}
export function windowGeometryUpdated() {
return {
type: UI_WINDOW_GEOMETRY_CHANGED
};
}
// Find all sessions that are below the given
// termGroup uid in the hierarchy:
const findChildSessions = (termGroups, uid) => {
const group = termGroups[uid];
if (group.sessionUid) {
return [uid];
}
return group.children.reduce((total, childUid) => total.concat(findChildSessions(termGroups, childUid)), []);
};
// Get the index of the next or previous group,
// depending on the movement direction:
const getNeighborIndex = (groups, uid, type) => {
if (type === UI_MOVE_NEXT_PANE) {
return (groups.indexOf(uid) + 1) % groups.length;
}
return (groups.indexOf(uid) + groups.length - 1) % groups.length;
};
function moveToNeighborPane(type) {
return () => (dispatch, getState) => {
dispatch({
type,
effect() {
const {sessions, termGroups} = getState();
const {uid} = findBySession(termGroups, sessions.activeUid);
const childGroups = findChildSessions(termGroups.termGroups, termGroups.activeRootGroup);
if (childGroups.length === 1) {
//eslint-disable-next-line no-console
console.log('ignoring move for single group');
} else {
const index = getNeighborIndex(childGroups, uid, type);
const {sessionUid} = termGroups.termGroups[childGroups[index]];
dispatch(setActiveSession(sessionUid));
}
}
});
};
}
export const moveToNextPane = moveToNeighborPane(UI_MOVE_NEXT_PANE);
export const moveToPreviousPane = moveToNeighborPane(UI_MOVE_PREV_PANE);
const getGroupUids = state => {
const rootGroups = getRootGroups(state);
return rootGroups.map(({uid}) => uid);
};
export function moveLeft() {
return (dispatch, getState) => {
dispatch({
type: UI_MOVE_LEFT,
effect() {
const state = getState();
const uid = state.termGroups.activeRootGroup;
const groupUids = getGroupUids(state);
const index = groupUids.indexOf(uid);
const next = groupUids[index - 1] || groupUids[groupUids.length - 1];
if (!next || uid === next) {
//eslint-disable-next-line no-console
console.log('ignoring left move action');
} else {
dispatch(setActiveGroup(next));
}
}
});
};
}
export function moveRight() {
return (dispatch, getState) => {
dispatch({
type: UI_MOVE_RIGHT,
effect() {
const state = getState();
const groupUids = getGroupUids(state);
const uid = state.termGroups.activeRootGroup;
const index = groupUids.indexOf(uid);
const next = groupUids[index + 1] || groupUids[0];
if (!next || uid === next) {
//eslint-disable-next-line no-console
console.log('ignoring right move action');
} else {
dispatch(setActiveGroup(next));
}
}
});
};
}
export function moveTo(i) {
return (dispatch, getState) => {
if (i === 'last') {
// Finding last tab index
const {termGroups} = getState().termGroups;
i =
Object.keys(termGroups)
.map(uid => termGroups[uid])
.filter(({parentUid}) => !parentUid).length - 1;
}
dispatch({
type: UI_MOVE_TO,
index: i,
effect() {
const state = getState();
const groupUids = getGroupUids(state);
const uid = state.termGroups.activeRootGroup;
if (uid === groupUids[i]) {
//eslint-disable-next-line no-console
console.log('ignoring same uid');
} else if (groupUids[i]) {
dispatch(setActiveGroup(groupUids[i]));
} else {
//eslint-disable-next-line no-console
console.log('ignoring inexistent index', i);
}
}
});
};
}
export function windowMove(window) {
return dispatch => {
dispatch({
type: UI_WINDOW_MOVE,
window,
effect() {
dispatch(setFontSmoothing());
}
});
};
}
export function windowGeometryChange() {
return dispatch => {
dispatch({
type: UI_WINDOW_MOVE,
effect() {
dispatch(setFontSmoothing());
}
});
};
}
export function openFile(path) {
return dispatch => {
dispatch({
type: UI_OPEN_FILE,
effect() {
stat(path, (err, stats) => {
if (err) {
notify('Unable to open path', `"${path}" doesn't exist.`, {error: err});
} else {
let command = escapeShellCmd(path).replace(/ /g, '\\ ');
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());
});
}
});
};
}
export function enterFullScreen() {
return {
type: UI_ENTER_FULLSCREEN
};
}
export function leaveFullScreen() {
return {
type: UI_LEAVE_FULLSCREEN
};
}
export function openSSH(url) {
return dispatch => {
dispatch({
type: UI_OPEN_SSH_URL,
effect() {
let parsedUrl = parseUrl(url, true);
let command = parsedUrl.protocol + ' ' + (parsedUrl.user ? `${parsedUrl.user}@` : '') + parsedUrl.resource;
if (parsedUrl.port) command += ' -p ' + parsedUrl.port;
command += '\n';
rpc.once('session add', ({uid}) => {
rpc.once('session data', () => {
dispatch(sendSessionData(uid, command));
});
});
dispatch(requestSession());
}
});
};
}
export function execCommand(command, fn, e) {
return dispatch =>
dispatch({
type: UI_COMMAND_EXEC,
command,
effect() {
if (fn) {
fn(e);
} else {
rpc.emit('command', command);
}
}
});
}