Added settings drawer with persistent settings

fix-builder
akshitkrnagpal 4 years ago committed by Saúl Ibarra Corretgé
parent 93d8268a68
commit 8f79e886fc
  1. 143
      app/features/conference/components/Conference.js
  2. 18
      app/features/navbar/actionTypes.js
  3. 34
      app/features/navbar/actions.js
  4. 38
      app/features/navbar/components/Navbar.js
  5. 1
      app/features/navbar/components/index.js
  6. 5
      app/features/navbar/index.js
  7. 39
      app/features/navbar/reducer.js
  8. 8
      app/features/navbar/styled/DrawerContainer.js
  9. 1
      app/features/navbar/styled/index.js
  10. 1
      app/features/redux/index.js
  11. 2
      app/features/redux/middleware.js
  12. 7
      app/features/redux/persistor.js
  13. 6
      app/features/redux/reducers.js
  14. 14
      app/features/redux/store.js
  15. 30
      app/features/settings/actionTypes.js
  16. 51
      app/features/settings/actions.js
  17. 60
      app/features/settings/components/SettingsAction.js
  18. 193
      app/features/settings/components/SettingsDrawer.js
  19. 2
      app/features/settings/components/index.js
  20. 7
      app/features/settings/index.js
  21. 24
      app/features/settings/middleware.js
  22. 53
      app/features/settings/reducer.js
  23. 9
      app/features/settings/styled/AvatarContainer.js
  24. 8
      app/features/settings/styled/ProfileContainer.js
  25. 2
      app/features/settings/styled/index.js
  26. 33
      app/features/utils/functions.js
  27. 17
      app/index.js
  28. 289
      package-lock.json
  29. 4
      package.json

@ -14,11 +14,17 @@ import {
} from 'jitsi-meet-electron-utils';
import config from '../../config';
import { setEmail, setName } from '../../settings';
import { Wrapper } from '../styled';
type Props = {
/**
* Redux dispatch.
*/
dispatch: Dispatch<*>;
/**
* React Router match object.
* This contains parameters passed through <Route /> component.
@ -26,9 +32,20 @@ type Props = {
match: Object;
/**
* Redux dispatch.
* Avatar URL.
*/
dispatch: Dispatch<*>;
_avatarURL: string;
/**
* Email of user.
*/
_email: string;
/**
* Name of user.
*/
_name: string;
};
/**
@ -76,6 +93,26 @@ class Conference extends Component<Props, *> {
this._ref.current.appendChild(script);
}
/**
* Keep profile settings in sync with Conference.
*
* @param {Props} prevProps - Component's prop values before update.
* @returns {void}
*/
componentDidUpdate(prevProps) {
const { props } = this;
if (props._avatarURL !== prevProps._avatarURL) {
this._setAvatarURL(props._avatarURL);
}
if (props._email !== prevProps._email) {
this._setEmail(props._email);
}
if (props._name !== prevProps._name) {
this._setName(props._name);
}
}
/**
* Remove conference on unmounting.
*
@ -131,7 +168,107 @@ class Conference extends Component<Props, *> {
setupWiFiStats(iframe);
this._api.on('readyToClose', () => this._navigateToHome());
this._api.on('videoConferenceJoined',
(conferenceInfo: Object) =>
this._onVideoConferenceJoined(conferenceInfo));
}
/**
* Updates redux state's user name from conference.
*
* @param {Object} params - Returned object from event.
* @param {string} id - Local Participant ID.
* @returns {void}
*/
_onDisplayNameChange(params: Object, id: string) {
if (params.id === id) {
this.props.dispatch(setName(params.displayname));
}
}
/**
* Updates redux state's email from conference.
*
* @param {Object} params - Returned object from event.
* @param {string} id - Local Participant ID.
* @returns {void}
*/
_onEmailChange(params: Object, id: string) {
if (params.id === id) {
this.props.dispatch(setEmail(params.email));
}
}
/**
* Saves conference info on joining it.
*
* @param {Object} conferenceInfo - Contains information about the current
* conference.
* @returns {void}
*/
_onVideoConferenceJoined(conferenceInfo: Object) {
this._setAvatarURL(this.props._avatarURL);
this._setEmail(this.props._email);
this._setName(this.props._name);
const { id } = conferenceInfo;
this._api.on('displayNameChange',
(params: Object) => this._onDisplayNameChange(params, id));
this._api.on('emailChange',
(params: Object) => this._onEmailChange(params, id));
}
/**
* Set Avatar URL from settings to conference.
*
* @param {string} avatarURL - Avatar URL.
* @returns {void}
*/
_setAvatarURL(avatarURL: string) {
this._api.executeCommand('avatarUrl', avatarURL);
}
/**
* Set email from settings to conference.
*
* @param {string} email - Email of user.
* @returns {void}
*/
_setEmail(email: string) {
this._api.executeCommand('email', email);
}
/**
* Set name from settings to conference.
*
* @param {string} name - Name of user.
* @returns {void}
*/
_setName(name: string) {
this._api.executeCommand('displayName', name);
}
}
/**
* Maps (parts of) the redux state to the React props.
*
* @param {Object} state - The redux state.
* @returns {{
* _avatarURL: string,
* _email: string,
* _name: string
* }}
*/
function _mapStateToProps(state: Object) {
return {
_avatarURL: state.settings.avatarURL,
_email: state.settings.email,
_name: state.settings.name
};
}
export default connect()(Conference);
export default connect(_mapStateToProps)(Conference);

@ -0,0 +1,18 @@
/**
* The type of (redux) action that opens specified Drawer.
*
* {
* type: OPEN_DRAWER,
* drawerComponent: React.ComponentType<*>
* }
*/
export const OPEN_DRAWER = Symbol('OPEN_DRAWER');
/**
* The type of (redux) action that closes all Drawer.
*
* {
* type: CLOSE_DRAWER
* }
*/
export const CLOSE_DRAWER = Symbol('CLOSE_DRAWER');

@ -0,0 +1,34 @@
// @flow
import type { ComponentType } from 'react';
import { CLOSE_DRAWER, OPEN_DRAWER } from './actionTypes';
/**
* Closes the drawers.
*
* @returns {{
* type: CLOSE_DRAWER,
* }}
*/
export function closeDrawer() {
return {
type: CLOSE_DRAWER
};
}
/**
* Opens the specified drawer.
*
* @param {string} drawerComponent - Component of the drawer.
* @returns {{
* type: OPEN_DRAWER,
* drawerComponent: ComponentType<*>
* }}
*/
export function openDrawer(drawerComponent: ComponentType<*>) {
return {
type: OPEN_DRAWER,
drawerComponent
};
}

@ -4,7 +4,9 @@
import Navigation, { AkGlobalItem } from '@atlaskit/navigation';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { SettingsAction, SettingsDrawer } from '../../settings';
import { isElectronMac } from '../../utils';
import HelpAction from './HelpAction';
@ -23,6 +25,19 @@ class Navbar extends Component<*> {
return <Logo />;
}
/**
* Get the array of Primary actions of Global Navigation.
*
* @returns {ReactElement[]}
*/
_getPrimaryActions() {
return [
<AkGlobalItem key = { 0 }>
<SettingsAction />
</AkGlobalItem>
];
}
/**
* Get the array of Secondary actions of Global Navigation.
*
@ -44,6 +59,12 @@ class Navbar extends Component<*> {
render() {
return (
<Navigation
drawers = { [
<SettingsDrawer
isOpen = { this.props._isSettingsDrawerOpen }
key = { 0 } />
] }
globalPrimaryActions = { this._getPrimaryActions() }
globalPrimaryIcon = { this._getPrimaryIcon() }
globalSecondaryActions = { this._getSecondaryActions() }
isElectronMac = { isElectronMac() }
@ -53,4 +74,19 @@ class Navbar extends Component<*> {
}
}
export default Navbar;
/**
* Maps (parts of) the redux state to the React props.
*
* @param {Object} state - The redux state.
* @returns {{
* _isSettingsDrawerOpen: boolean
* }}
*/
function _mapStateToProps(state: Object) {
return {
_isSettingsDrawerOpen: state.navbar.openDrawer === SettingsDrawer
};
}
export default connect(_mapStateToProps)(Navbar);

@ -1 +1,2 @@
export { default as Logo } from './Logo';
export { default as Navbar } from './Navbar';

@ -1 +1,6 @@
export * from './actions';
export * from './actionTypes';
export * from './components';
export * from './styled';
export { default as reducer } from './reducer';

@ -0,0 +1,39 @@
// @flow
import type { ComponentType } from 'react';
import { CLOSE_DRAWER, OPEN_DRAWER } from './actionTypes';
type State = {
openDrawer: typeof undefined | ComponentType<*>
};
const DEFAULT_STATE = {
openDrawer: undefined
};
/**
* Reduces redux actions for features/settings.
*
* @param {State} state - Current reduced redux state.
* @param {Object} action - Action which was dispatched.
* @returns {State} - Updated reduced redux state.
*/
export default (state: State = DEFAULT_STATE, action: Object) => {
switch (action.type) {
case CLOSE_DRAWER:
return {
...state,
openDrawer: undefined
};
case OPEN_DRAWER:
return {
...state,
openDrawer: action.drawerComponent
};
default:
return state;
}
};

@ -0,0 +1,8 @@
// @flow
import styled from 'styled-components';
export default styled.div`
margin-right: 68px;
padding: 0 8px;
`;

@ -0,0 +1 @@
export { default as DrawerContainer } from './DrawerContainer';

@ -1 +1,2 @@
export { default as persistor } from './persistor';
export { default as store } from './store';

@ -4,8 +4,10 @@ import { applyMiddleware } from 'redux';
import { createLogger } from 'redux-logger';
import { middleware as routerMiddleware } from '../router';
import { middleware as settingsMiddleware } from '../settings';
export default applyMiddleware(
routerMiddleware,
settingsMiddleware,
createLogger()
);

@ -0,0 +1,7 @@
// @flow
import { persistStore } from 'redux-persist';
import store from './store';
export default persistStore(store);

@ -2,8 +2,12 @@
import { combineReducers } from 'redux';
import { reducer as navbarReducer } from '../navbar';
import { reducer as routerReducer } from '../router';
import { reducer as settingsReducer } from '../settings';
export default combineReducers({
router: routerReducer
navbar: navbarReducer,
router: routerReducer,
settings: settingsReducer
});

@ -1,8 +1,20 @@
// @flow
import { createStore } from 'redux';
import { persistReducer } from 'redux-persist';
import createElectronStorage from 'redux-persist-electron-storage';
import middleware from './middleware';
import reducers from './reducers';
export default createStore(reducers, middleware);
const persistConfig = {
key: 'root',
storage: createElectronStorage(),
whitelist: [
'settings'
]
};
const persistedReducer = persistReducer(persistConfig, reducers);
export default createStore(persistedReducer, middleware);

@ -0,0 +1,30 @@
/**
* The type of (redux) action that sets the Avatar URL.
*
* {
* type: SET_AVATAR_URL,
* avatarURL: string
* }
*/
export const SET_AVATAR_URL = Symbol('SET_AVATAR_URL');
/**
* The type of (redux) action that sets the email of the user.
*
* {
* type: SET_EMAIL,
* email: string
* }
*/
export const SET_EMAIL = Symbol('SET_EMAIL');
/**
* The type of (redux) action that sets the name of the user.
*
* {
* type: SET_NAME,
* name: string
* }
*/
export const SET_NAME = Symbol('SET_NAME');

@ -0,0 +1,51 @@
// @flow
import { SET_AVATAR_URL, SET_EMAIL, SET_NAME } from './actionTypes';
/**
* Set Avatar URL.
*
* @param {string} avatarURL - Avatar URL.
* @returns {{
* type: SET_AVATAR_URL,
* avatarURL: string
* }}
*/
export function setAvatarURL(avatarURL: string) {
return {
type: SET_AVATAR_URL,
avatarURL
};
}
/**
* Set the email of the user.
*
* @param {string} email - Email of the user.
* @returns {{
* type: SET_EMAIL,
* email: string
* }}
*/
export function setEmail(email: string) {
return {
type: SET_EMAIL,
email
};
}
/**
* Set the name of the user.
*
* @param {string} name - Name of the user.
* @returns {{
* type: SET_NAME,
* name: string
* }}
*/
export function setName(name: string) {
return {
type: SET_NAME,
name
};
}

@ -0,0 +1,60 @@
// @flow
import SettingsIcon from '@atlaskit/icon/glyph/settings';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import type { Dispatch } from 'redux';
import { openDrawer } from '../../navbar';
import SettingsDrawer from './SettingsDrawer';
type Props = {
/**
* Redux dispatch.
*/
dispatch: Dispatch<*>;
};
/**
* Setttings Action for Navigation Bar.
*/
class SettingsAction extends Component<Props, *> {
/**
* Initializes a new {@code SettingsAction} instance.
*
* @inheritdoc
*/
constructor() {
super();
this._onIconClick = this._onIconClick.bind(this);
}
/**
* Render function of component.
*
* @returns {ReactElement}
*/
render() {
return (
<SettingsIcon
onClick = { this._onIconClick } />
);
}
_onIconClick: (*) => void;
/**
* Open Settings drawer when SettingsAction is clicked.
*
* @returns {void}
*/
_onIconClick() {
this.props.dispatch(openDrawer(SettingsDrawer));
}
}
export default connect()(SettingsAction);

@ -0,0 +1,193 @@
// @flow
import Avatar from '@atlaskit/avatar';
import FieldText from '@atlaskit/field-text';
import ArrowLeft from '@atlaskit/icon/glyph/arrow-left';
import { AkCustomDrawer } from '@atlaskit/navigation';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import type { Dispatch } from 'redux';
import { closeDrawer, DrawerContainer, Logo } from '../../navbar';
import { AvatarContainer, ProfileContainer } from '../styled';
import { setEmail, setName } from '../actions';
type Props = {
/**
* Redux dispatch.
*/
dispatch: Dispatch<*>;
/**
* Is the drawer open or not.
*/
isOpen: boolean;
/**
* Avatar URL.
*/
_avatarURL: string;
/**
* Email of the user.
*/
_email: string;
/**
* Name of the user.
*/
_name: string;
};
/**
* Drawer that open when SettingsAction is clicked.
*/
class SettingsDrawer extends Component<Props, *> {
/**
* Initializes a new {@code SettingsDrawer} instance.
*
* @inheritdoc
*/
constructor(props) {
super(props);
this._onBackButton = this._onBackButton.bind(this);
this._onEmailBlur = this._onEmailBlur.bind(this);
this._onEmailFormSubmit = this._onEmailFormSubmit.bind(this);
this._onNameBlur = this._onNameBlur.bind(this);
this._onNameFormSubmit = this._onNameFormSubmit.bind(this);
}
/**
* Render function of component.
*
* @returns {ReactElement}
*/
render() {
return (
<AkCustomDrawer
backIcon = { <ArrowLeft label = 'Back' /> }
isOpen = { this.props.isOpen }
onBackButton = { this._onBackButton }
primaryIcon = { <Logo /> } >
<DrawerContainer>
<ProfileContainer>
<AvatarContainer>
<Avatar
size = 'xlarge'
src = { this.props._avatarURL } />
</AvatarContainer>
<form onSubmit = { this._onNameFormSubmit }>
<FieldText
label = 'Name'
onBlur = { this._onNameBlur }
shouldFitContainer = { true }
type = 'text'
value = { this.props._name } />
</form>
<form onSubmit = { this._onEmailFormSubmit }>
<FieldText
label = 'Email'
onBlur = { this._onEmailBlur }
shouldFitContainer = { true }
type = 'text'
value = { this.props._email } />
</form>
</ProfileContainer>
</DrawerContainer>
</AkCustomDrawer>
);
}
_onBackButton: (*) => void;
/**
* Closes the drawer when back button is clicked.
*
* @returns {void}
*/
_onBackButton() {
this.props.dispatch(closeDrawer());
}
_onEmailBlur: (*) => void;
/**
* Updates Avatar URL in (redux) state when email is updated.
*
* @param {SyntheticInputEvent<HTMLInputElement>} event - Event by which
* this function is called.
* @returns {void}
*/
_onEmailBlur(event: SyntheticInputEvent<HTMLInputElement>) {
this.props.dispatch(setEmail(event.currentTarget.value));
}
_onEmailFormSubmit: (*) => void;
/**
* Prevents submission of the form and updates email.
*
* @param {SyntheticEvent<HTMLFormElement>} event - Event by which
* this function is called.
* @returns {void}
*/
_onEmailFormSubmit(event: SyntheticEvent<HTMLFormElement>) {
event.preventDefault();
// $FlowFixMe
this.props.dispatch(setEmail(event.currentTarget.elements[0].value));
}
_onNameBlur: (*) => void;
/**
* Updates Avatar URL in (redux) state when name is updated.
*
* @param {SyntheticInputEvent<HTMLInputElement>} event - Event by which
* this function is called.
* @returns {void}
*/
_onNameBlur(event: SyntheticInputEvent<HTMLInputElement>) {
this.props.dispatch(setName(event.currentTarget.value));
}
_onNameFormSubmit: (*) => void;
/**
* Prevents submission of the form and updates name.
*
* @param {SyntheticEvent<HTMLFormElement>} event - Event by which
* this function is called.
* @returns {void}
*/
_onNameFormSubmit(event: SyntheticEvent<HTMLFormElement>) {
event.preventDefault();
// $FlowFixMe
this.props.dispatch(setName(event.currentTarget.elements[0].value));
}
}
/**
* Maps (parts of) the redux state to the React props.
*
* @param {Object} state - The redux state.
* @returns {{
* _avatarURL: string,
* _email: string,
* _name: string
* }}
*/
function _mapStateToProps(state: Object) {
return {
_avatarURL: state.settings.avatarURL,
_email: state.settings.email,
_name: state.settings.name
};
}
export default connect(_mapStateToProps)(SettingsDrawer);

@ -0,0 +1,2 @@
export { default as SettingsAction } from './SettingsAction';
export { default as SettingsDrawer } from './SettingsDrawer';

@ -0,0 +1,7 @@
export * from './actions';
export * from './actionTypes';
export * from './components';
export * from './styled';
export { default as middleware } from './middleware';
export { default as reducer } from './reducer';

@ -0,0 +1,24 @@
// @flow
import { getAvatarURL } from '../utils';
import { SET_EMAIL, SET_NAME } from './actionTypes';
import { setAvatarURL } from './actions';
export default (store: Object) => (next: Function) => (action: Object) => {
const result = next(action);
const state = store.getState();
switch (action.type) {
case SET_EMAIL:
case SET_NAME: {
const avatarURL = getAvatarURL({
email: state.settings.email,
id: state.settings.name
});
store.dispatch(setAvatarURL(avatarURL));
}
}
return result;
};

@ -0,0 +1,53 @@
// @flow
import os from 'os';
import { getAvatarURL } from '../utils';
import { SET_AVATAR_URL, SET_EMAIL, SET_NAME } from './actionTypes';
type State = {
avatarURL: string,
email: string,
name: string
};
const username = os.userInfo().username;
const DEFAULT_STATE = {
avatarURL: getAvatarURL({ id: username }),
email: '',
name: username
};
/**
* Reduces redux actions for features/settings.
*
* @param {State} state - Current reduced redux state.
* @param {Object} action - Action which was dispatched.
* @returns {State} - Updated reduced redux state.
*/
export default (state: State = DEFAULT_STATE, action: Object) => {
switch (action.type) {
case SET_AVATAR_URL:
return {
...state,
avatarURL: action.avatarURL
};
case SET_EMAIL:
return {
...state,
email: action.email
};
case SET_NAME:
return {
...state,
name: action.name
};
default:
return state;
}
};

@ -0,0 +1,9 @@
// @flow
import styled from 'styled-components';
export default styled.div`
align-items: center;
display: flex;
flex-direction: column;
`;

@ -0,0 +1,8 @@
// @flow
import styled from 'styled-components';
export default styled.div`
margin: 0 auto;
width: 70%;
`;

@ -0,0 +1,2 @@
export { default as AvatarContainer } from './AvatarContainer';
export { default as ProfileContainer } from './ProfileContainer';

@ -3,6 +3,7 @@
// @flow
import { shell } from 'electron';
import md5 from 'js-md5';
/**
* Opens the provided link in default broswer.
@ -22,3 +23,35 @@ export function openExternalLink(link: string) {
export function isElectronMac() {
return process.platform === 'darwin';
}
/**
* Returns the Avatar URL to be used.
*
* @param {string} key - Unique key to generate Avatar URL.
* @returns {string}
*/
export function getAvatarURL({ email, id }: {
email: string,
id: string
}) {
let key = email || id;
let urlPrefix;
let urlSuffix;
// If the ID looks like an e-mail address, we'll use Gravatar because it
// supports e-mail addresses.
if (key && key.indexOf('@') > 0) {
// URL prefix and suffix of gravatar service.
urlPrefix = 'https://www.gravatar.com/avatar/';
urlSuffix = '?d=wavatar&size=200';
} else {
key = id;
// Otherwise, use a default (meeples, of course).
urlPrefix = 'https://abotars.jitsi.net/meeple/';
urlSuffix = '';
}
return urlPrefix + md5.hex(key.trim().toLowerCase()) + urlSuffix;
}

@ -1,16 +1,17 @@
// @flow
import React, { Component } from 'react';
import { render } from 'react-dom';
import { Provider } from 'react-redux';
/**
* AtlasKit components will deflect from appearance if css-reset is not present.
*/
import '@atlaskit/css-reset';
import React, { Component } from 'react';
import { render } from 'react-dom';
import { Provider } from 'react-redux';
import { PersistGate } from 'redux-persist/integration/react';
import { App } from './features/app';
import { store } from './features/redux';
import { persistor, store } from './features/redux';
/**
* Component encapsulating App component with redux store using provider.
@ -24,7 +25,11 @@ class Root extends Component<*> {
render() {
return (
<Provider store = { store }>
<App />
<PersistGate
loading = { null }
persistor = { persistor }>
<App />
</PersistGate>
</Provider>
);
}

289
package-lock.json generated

@ -3105,6 +3105,30 @@
"typedarray": "0.0.6"
}
},
"conf": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/conf/-/conf-1.4.0.tgz",
"integrity": "sha512-bzlVWS2THbMetHqXKB8ypsXN4DQ/1qopGwNJi1eYbpwesJcd86FBjFciCQX/YwAhp9bM7NVnPFqZ5LpV7gP0Dg==",
"requires": {
"dot-prop": "4.2.0",
"env-paths": "1.0.0",
"make-dir": "1.3.0",
"pkg-up": "2.0.0",
"write-file-atomic": "2.3.0"
},
"dependencies": {
"write-file-atomic": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.3.0.tgz",
"integrity": "sha512-xuPeK4OdjWqtfi59ylvVL0Yn35SF3zgcAcv7rBPFHVaEapaDr4GdGgm3j7ckTwH9wHL7fGmgfAnb0+THrHb8tA==",
"requires": {
"graceful-fs": "4.1.11",
"imurmurhash": "0.1.4",
"signal-exit": "3.0.2"
}
}
}
},
"console-browserify": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz",
@ -3777,6 +3801,14 @@
"domelementtype": "1.3.0"
}
},
"dot-prop": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz",
"integrity": "sha512-tUMXrxlExSW6U2EXiiKGSBVdYgtV8qlHL+C10TsW4PURY/ic+eaysnSkwB4kA/mBlCyy/IKDJ+Lc3wbWeaXtuQ==",
"requires": {
"is-obj": "1.0.1"
}
},
"duplexer3": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz",
@ -4062,6 +4094,14 @@
}
}
},
"electron-store": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/electron-store/-/electron-store-1.3.0.tgz",
"integrity": "sha512-r1Pdl5MwpiCxgbsl0qnwv/GABO5+J/JTO16+KyqL+bOITIk9o3cq3Sw69uO9NgPkpfcKeEwxtJFbtbiBlGTiDA==",
"requires": {
"conf": "1.4.0"
}
},
"electron-to-chromium": {
"version": "1.3.48",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.48.tgz",
@ -4141,8 +4181,7 @@
"env-paths": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/env-paths/-/env-paths-1.0.0.tgz",
"integrity": "sha1-QWgTO0K7BcOKNbGuQ5fIKYqzaeA=",
"dev": true
"integrity": "sha1-QWgTO0K7BcOKNbGuQ5fIKYqzaeA="
},
"envinfo": {
"version": "4.4.2",
@ -4982,7 +5021,6 @@
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz",
"integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=",
"dev": true,
"requires": {
"locate-path": "2.0.0"
}
@ -5188,24 +5226,28 @@
"dependencies": {
"abbrev": {
"version": "1.1.1",
"bundled": true,
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
"dev": true,
"optional": true
},
"ansi-regex": {
"version": "2.1.1",
"bundled": true,
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
"integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
"dev": true
},
"aproba": {
"version": "1.2.0",
"bundled": true,
"resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
"integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==",
"dev": true,
"optional": true
},
"are-we-there-yet": {
"version": "1.1.4",
"bundled": true,
"resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz",
"integrity": "sha1-u13KOCu5TwXhUZQ3PRb9O6HKEQ0=",
"dev": true,
"optional": true,
"requires": {
@ -5215,12 +5257,14 @@
},
"balanced-match": {
"version": "1.0.0",
"bundled": true,
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
"dev": true
},
"brace-expansion": {
"version": "1.1.11",
"bundled": true,
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dev": true,
"requires": {
"balanced-match": "1.0.0",
@ -5229,34 +5273,40 @@
},
"chownr": {
"version": "1.0.1",
"bundled": true,
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.0.1.tgz",
"integrity": "sha1-4qdQQqlVGQi+vSW4Uj1fl2nXkYE=",
"dev": true,
"optional": true
},
"code-point-at": {
"version": "1.1.0",
"bundled": true,
"resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
"integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=",
"dev": true
},
"concat-map": {
"version": "0.0.1",
"bundled": true,
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
"dev": true
},
"console-control-strings": {
"version": "1.1.0",
"bundled": true,
"resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
"integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=",
"dev": true
},
"core-util-is": {
"version": "1.0.2",
"bundled": true,
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
"dev": true,
"optional": true
},
"debug": {
"version": "2.6.9",
"bundled": true,
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"dev": true,
"optional": true,
"requires": {
@ -5265,25 +5315,29 @@
},
"deep-extend": {
"version": "0.5.1",
"bundled": true,
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.5.1.tgz",
"integrity": "sha512-N8vBdOa+DF7zkRrDCsaOXoCs/E2fJfx9B9MrKnnSiHNh4ws7eSys6YQE4KvT1cecKmOASYQBhbKjeuDD9lT81w==",
"dev": true,
"optional": true
},
"delegates": {
"version": "1.0.0",
"bundled": true,
"resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
"integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=",
"dev": true,
"optional": true
},
"detect-libc": {
"version": "1.0.3",
"bundled": true,
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
"integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=",
"dev": true,
"optional": true
},
"fs-minipass": {
"version": "1.2.5",
"bundled": true,
"resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.5.tgz",
"integrity": "sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ==",
"dev": true,
"optional": true,
"requires": {
@ -5292,13 +5346,15 @@
},
"fs.realpath": {
"version": "1.0.0",
"bundled": true,
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
"dev": true,
"optional": true
},
"gauge": {
"version": "2.7.4",
"bundled": true,
"resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz",
"integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=",
"dev": true,
"optional": true,
"requires": {
@ -5314,7 +5370,8 @@
},
"glob": {
"version": "7.1.2",
"bundled": true,
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
"integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==",
"dev": true,
"optional": true,
"requires": {
@ -5328,13 +5385,15 @@
},
"has-unicode": {
"version": "2.0.1",
"bundled": true,
"resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
"integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=",
"dev": true,
"optional": true
},
"iconv-lite": {
"version": "0.4.21",
"bundled": true,
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.21.tgz",
"integrity": "sha512-En5V9za5mBt2oUA03WGD3TwDv0MKAruqsuxstbMUZaj9W9k/m1CV/9py3l0L5kw9Bln8fdHQmzHSYtvpvTLpKw==",
"dev": true,
"optional": true,
"requires": {
@ -5343,7 +5402,8 @@
},
"ignore-walk": {
"version": "3.0.1",
"bundled": true,
"resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.1.tgz",
"integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==",
"dev": true,
"optional": true,
"requires": {
@ -5352,7 +5412,8 @@
},
"inflight": {
"version": "1.0.6",
"bundled": true,
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
"dev": true,
"optional": true,
"requires": {
@ -5362,18 +5423,21 @@
},
"inherits": {
"version": "2.0.3",
"bundled": true,
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
"dev": true
},
"ini": {
"version": "1.3.5",
"bundled": true,
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz",
"integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==",
"dev": true,
"optional": true
},
"is-fullwidth-code-point": {
"version": "1.0.0",
"bundled": true,
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
"integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
"dev": true,
"requires": {
"number-is-nan": "1.0.1"
@ -5381,13 +5445,15 @@
},
"isarray": {
"version": "1.0.0",
"bundled": true,
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
"dev": true,
"optional": true
},
"minimatch": {
"version": "3.0.4",
"bundled": true,
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"dev": true,
"requires": {
"brace-expansion": "1.1.11"
@ -5395,12 +5461,14 @@
},
"minimist": {
"version": "0.0.8",
"bundled": true,
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
"integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
"dev": true
},
"minipass": {
"version": "2.2.4",
"bundled": true,
"resolved": "https://registry.npmjs.org/minipass/-/minipass-2.2.4.tgz",
"integrity": "sha512-hzXIWWet/BzWhYs2b+u7dRHlruXhwdgvlTMDKC6Cb1U7ps6Ac6yQlR39xsbjWJE377YTCtKwIXIpJ5oP+j5y8g==",
"dev": true,
"requires": {
"safe-buffer": "5.1.1",
@ -5409,7 +5477,8 @@
},
"minizlib": {
"version": "1.1.0",
"bundled": true,
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.1.0.tgz",
"integrity": "sha512-4T6Ur/GctZ27nHfpt9THOdRZNgyJ9FZchYO1ceg5S8Q3DNLCKYy44nCZzgCJgcvx2UM8czmqak5BCxJMrq37lA==",
"dev": true,
"optional": true,
"requires": {
@ -5418,7 +5487,8 @@
},
"mkdirp": {
"version": "0.5.1",
"bundled": true,
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
"integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
"dev": true,
"requires": {
"minimist": "0.0.8"
@ -5426,13 +5496,15 @@
},
"ms": {
"version": "2.0.0",
"bundled": true,
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
"dev": true,
"optional": true