diff options
author | akshitkrnagpal <akshitkrnagpal@gmail.com> | 2018-06-09 21:40:00 +0530 |
---|---|---|
committer | Saúl Ibarra Corretgé <s@saghul.net> | 2018-06-21 07:55:32 +0200 |
commit | 8f79e886fc56760694b3e1f470a8e61993b1ffba (patch) | |
tree | cb966209f7664e17cc92b56206eb2be42f412abf | |
parent | 93d8268a68db286cfec44f4020117c7087863402 (diff) |
Added settings drawer with persistent settings
29 files changed, 1006 insertions, 93 deletions
diff --git a/app/features/conference/components/Conference.js b/app/features/conference/components/Conference.js index 8683820..f308ef0 100644 --- a/app/features/conference/components/Conference.js +++ b/app/features/conference/components/Conference.js @@ -14,21 +14,38 @@ 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. */ match: Object; /** - * Redux dispatch. + * Avatar URL. */ - dispatch: Dispatch<*>; + _avatarURL: string; + + /** + * Email of user. + */ + _email: string; + + /** + * Name of user. + */ + _name: string; + }; /** @@ -77,6 +94,26 @@ class Conference extends Component<Props, *> { } /** + * 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. * * @returns {void} @@ -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); diff --git a/app/features/navbar/actionTypes.js b/app/features/navbar/actionTypes.js new file mode 100644 index 0000000..0c5efae --- /dev/null +++ b/app/features/navbar/actionTypes.js @@ -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'); diff --git a/app/features/navbar/actions.js b/app/features/navbar/actions.js new file mode 100644 index 0000000..af301b1 --- /dev/null +++ b/app/features/navbar/actions.js @@ -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 + }; +} diff --git a/app/features/navbar/components/Navbar.js b/app/features/navbar/components/Navbar.js index f767436..c193211 100644 --- a/app/features/navbar/components/Navbar.js +++ b/app/features/navbar/components/Navbar.js @@ -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'; @@ -24,6 +26,19 @@ class Navbar extends Component<*> { } /** + * 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. * * @returns {ReactElement[]} @@ -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); diff --git a/app/features/navbar/components/index.js b/app/features/navbar/components/index.js index ee1d016..0e08bca 100644 --- a/app/features/navbar/components/index.js +++ b/app/features/navbar/components/index.js @@ -1 +1,2 @@ +export { default as Logo } from './Logo'; export { default as Navbar } from './Navbar'; diff --git a/app/features/navbar/index.js b/app/features/navbar/index.js index 07635cb..da41a84 100644 --- a/app/features/navbar/index.js +++ b/app/features/navbar/index.js @@ -1 +1,6 @@ +export * from './actions'; +export * from './actionTypes'; export * from './components'; +export * from './styled'; + +export { default as reducer } from './reducer'; diff --git a/app/features/navbar/reducer.js b/app/features/navbar/reducer.js new file mode 100644 index 0000000..ec0e498 --- /dev/null +++ b/app/features/navbar/reducer.js @@ -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; + } +}; diff --git a/app/features/navbar/styled/DrawerContainer.js b/app/features/navbar/styled/DrawerContainer.js new file mode 100644 index 0000000..8f4a2dc --- /dev/null +++ b/app/features/navbar/styled/DrawerContainer.js @@ -0,0 +1,8 @@ +// @flow + +import styled from 'styled-components'; + +export default styled.div` + margin-right: 68px; + padding: 0 8px; +`; diff --git a/app/features/navbar/styled/index.js b/app/features/navbar/styled/index.js new file mode 100644 index 0000000..065175a --- /dev/null +++ b/app/features/navbar/styled/index.js @@ -0,0 +1 @@ +export { default as DrawerContainer } from './DrawerContainer'; diff --git a/app/features/redux/index.js b/app/features/redux/index.js index 487ab35..f76681b 100644 --- a/app/features/redux/index.js +++ b/app/features/redux/index.js @@ -1 +1,2 @@ +export { default as persistor } from './persistor'; export { default as store } from './store'; diff --git a/app/features/redux/middleware.js b/app/features/redux/middleware.js index 8b1a2cd..9938da5 100644 --- a/app/features/redux/middleware.js +++ b/app/features/redux/middleware.js @@ -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() ); diff --git a/app/features/redux/persistor.js b/app/features/redux/persistor.js new file mode 100644 index 0000000..dfc12ef --- /dev/null +++ b/app/features/redux/persistor.js @@ -0,0 +1,7 @@ +// @flow + +import { persistStore } from 'redux-persist'; + +import store from './store'; + +export default persistStore(store); diff --git a/app/features/redux/reducers.js b/app/features/redux/reducers.js index 256873c..667b922 100644 --- a/app/features/redux/reducers.js +++ b/app/features/redux/reducers.js @@ -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 }); diff --git a/app/features/redux/store.js b/app/features/redux/store.js index 2e4dc00..992e802 100644 --- a/app/features/redux/store.js +++ b/app/features/redux/store.js @@ -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); diff --git a/app/features/settings/actionTypes.js b/app/features/settings/actionTypes.js new file mode 100644 index 0000000..977ef4d --- /dev/null +++ b/app/features/settings/actionTypes.js @@ -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'); + diff --git a/app/features/settings/actions.js b/app/features/settings/actions.js new file mode 100644 index 0000000..dd5a383 --- /dev/null +++ b/app/features/settings/actions.js @@ -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 + }; +} diff --git a/app/features/settings/components/SettingsAction.js b/app/features/settings/components/SettingsAction.js new file mode 100644 index 0000000..080e3c6 --- /dev/null +++ b/app/features/settings/components/SettingsAction.js @@ -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); diff --git a/app/features/settings/components/SettingsDrawer.js b/app/features/settings/components/SettingsDrawer.js new file mode 100644 index 0000000..7869cd2 --- /dev/null +++ b/app/features/settings/components/SettingsDrawer.js @@ -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); diff --git a/app/features/settings/components/index.js b/app/features/settings/components/index.js new file mode 100644 index 0000000..63dd49d --- /dev/null +++ b/app/features/settings/components/index.js @@ -0,0 +1,2 @@ +export { default as SettingsAction } from './SettingsAction'; +export { default as SettingsDrawer } from './SettingsDrawer'; diff --git a/app/features/settings/index.js b/app/features/settings/index.js new file mode 100644 index 0000000..17b8002 --- /dev/null +++ b/app/features/settings/index.js @@ -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'; diff --git a/app/features/settings/middleware.js b/app/features/settings/middleware.js new file mode 100644 index 0000000..0d6e489 --- /dev/null +++ b/app/features/settings/middleware.js @@ -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; +}; diff --git a/app/features/settings/reducer.js b/app/features/settings/reducer.js new file mode 100644 index 0000000..02d42d5 --- /dev/null +++ b/app/features/settings/reducer.js @@ -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; + } +}; diff --git a/app/features/settings/styled/AvatarContainer.js b/app/features/settings/styled/AvatarContainer.js new file mode 100644 index 0000000..65e617e --- /dev/null +++ b/app/features/settings/styled/AvatarContainer.js @@ -0,0 +1,9 @@ +// @flow + +import styled from 'styled-components'; + +export default styled.div` + align-items: center; + display: flex; + flex-direction: column; +`; diff --git a/app/features/settings/styled/ProfileContainer.js b/app/features/settings/styled/ProfileContainer.js new file mode 100644 index 0000000..8daae3a --- /dev/null +++ b/app/features/settings/styled/ProfileContainer.js @@ -0,0 +1,8 @@ +// @flow + +import styled from 'styled-components'; + +export default styled.div` + margin: 0 auto; + width: 70%; +`; diff --git a/app/features/settings/styled/index.js b/app/features/settings/styled/index.js new file mode 100644 index 0000000..ed00704 --- /dev/null +++ b/app/features/settings/styled/index.js @@ -0,0 +1,2 @@ +export { default as AvatarContainer } from './AvatarContainer'; +export { default as ProfileContainer } from './ProfileContainer'; diff --git a/app/features/utils/functions.js b/app/features/utils/functions.js index a86d05f..5f31ddd 100644 --- a/app/features/utils/functions.js +++ b/app/features/utils/functions.js @@ -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; +} diff --git a/app/index.js b/app/index.js index 96fe943..215fca2 100644 --- a/app/index.js +++ b/app/index.js @@ -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> ); } diff --git a/package-lock.json b/package-lock.json index b2a49f9..d535fe7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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 }, "needle": { "version": "2.2.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/needle/-/needle-2.2.0.tgz", + "integrity": "sha512-eFagy6c+TYayorXw/qtAdSvaUpEbBsDwDyxYFgLZ0lTojfH7K+OdBqAF7TAFwDokJaGpubpSGG0wO3iC0XPi8w==", "dev": true, "optional": true, "requires": { @@ -5443,7 +5515,8 @@ }, "node-pre-gyp": { "version": "0.10.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.10.0.tgz", + "integrity": "sha512-G7kEonQLRbcA/mOoFoxvlMrw6Q6dPf92+t/l0DFSMuSlDoWaI9JWIyPwK0jyE1bph//CUEL65/Fz1m2vJbmjQQ==", "dev": true, "optional": true, "requires": { @@ -5461,7 +5534,8 @@ }, "nopt": { "version": "4.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", + "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", "dev": true, "optional": true, "requires": { @@ -5471,13 +5545,15 @@ }, "npm-bundled": { "version": "1.0.3", - "bundled": true, + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.0.3.tgz", + "integrity": "sha512-ByQ3oJ/5ETLyglU2+8dBObvhfWXX8dtPZDMePCahptliFX2iIuhyEszyFk401PZUNQH20vvdW5MLjJxkwU80Ow==", "dev": true, "optional": true }, "npm-packlist": { "version": "1.1.10", - "bundled": true, + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.1.10.tgz", + "integrity": "sha512-AQC0Dyhzn4EiYEfIUjCdMl0JJ61I2ER9ukf/sLxJUcZHfo+VyEfz2rMJgLZSS1v30OxPQe1cN0LZA1xbcaVfWA==", "dev": true, "optional": true, "requires": { @@ -5487,7 +5563,8 @@ }, "npmlog": { "version": "4.1.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", "dev": true, "optional": true, "requires": { @@ -5499,18 +5576,21 @@ }, "number-is-nan": { "version": "1.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", "dev": true }, "object-assign": { "version": "4.1.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", "dev": true, "optional": true }, "once": { "version": "1.4.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "dev": true, "requires": { "wrappy": "1.0.2" @@ -5518,19 +5598,22 @@ }, "os-homedir": { "version": "1.0.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", "dev": true, "optional": true }, "os-tmpdir": { "version": "1.0.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", "dev": true, "optional": true }, "osenv": { "version": "0.1.5", - "bundled": true, + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", "dev": true, "optional": true, "requires": { @@ -5540,19 +5623,22 @@ }, "path-is-absolute": { "version": "1.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "dev": true, "optional": true }, "process-nextick-args": { "version": "2.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", "dev": true, "optional": true }, "rc": { "version": "1.2.7", - "bundled": true, + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.7.tgz", + "integrity": "sha512-LdLD8xD4zzLsAT5xyushXDNscEjB7+2ulnl8+r1pnESlYtlJtVSoCMBGr30eDRJ3+2Gq89jK9P9e4tCEH1+ywA==", "dev": true, "optional": true, "requires": { @@ -5564,7 +5650,8 @@ "dependencies": { "minimist": { "version": "1.2.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true, "optional": true } @@ -5572,7 +5659,8 @@ }, "readable-stream": { "version": "2.3.6", - "bundled": true, + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "optional": true, "requires": { @@ -5587,7 +5675,8 @@ }, "rimraf": { "version": "2.6.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", + "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", "dev": true, "optional": true, "requires": { @@ -5596,42 +5685,49 @@ }, "safe-buffer": { "version": "5.1.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", + "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==", "dev": true }, "safer-buffer": { "version": "2.1.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true, "optional": true }, "sax": { "version": "1.2.4", - "bundled": true, + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", "dev": true, "optional": true }, "semver": { "version": "5.5.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", + "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", "dev": true, "optional": true }, "set-blocking": { "version": "2.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", "dev": true, "optional": true }, "signal-exit": { "version": "3.0.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", "dev": true, "optional": true }, "string-width": { "version": "1.0.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "dev": true, "requires": { "code-point-at": "1.1.0", @@ -5641,7 +5737,8 @@ }, "string_decoder": { "version": "1.1.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "optional": true, "requires": { @@ -5650,7 +5747,8 @@ }, "strip-ansi": { "version": "3.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "requires": { "ansi-regex": "2.1.1" @@ -5658,13 +5756,15 @@ }, "strip-json-comments": { "version": "2.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", "dev": true, "optional": true }, "tar": { "version": "4.4.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.1.tgz", + "integrity": "sha512-O+v1r9yN4tOsvl90p5HAP4AEqbYhx4036AGMm075fH9F8Qwi3oJ+v4u50FkT/KkvywNGtwkk0zRI+8eYm1X/xg==", "dev": true, "optional": true, "requires": { @@ -5679,13 +5779,15 @@ }, "util-deprecate": { "version": "1.0.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", "dev": true, "optional": true }, "wide-align": { "version": "1.1.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.2.tgz", + "integrity": "sha512-ijDLlyQ7s6x1JgCLur53osjm/UXUYD9+0PbYKrBsYisYXzCxN+HC3mYDNy/dWdmf3AwqwU3CXwDCvsNgGK1S0w==", "dev": true, "optional": true, "requires": { @@ -5694,12 +5796,14 @@ }, "wrappy": { "version": "1.0.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true }, "yallist": { "version": "3.0.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.2.tgz", + "integrity": "sha1-hFK0u36Dx8GI2AQcGoN8dz1ti7k=", "dev": true } } @@ -6580,8 +6684,7 @@ "imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=" }, "indent-string": { "version": "2.1.0", @@ -6924,6 +7027,11 @@ } } }, + "is-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=" + }, "is-object": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-object/-/is-object-1.0.1.tgz", @@ -7170,6 +7278,11 @@ "integrity": "sha512-aUnNwqMOXw3yvErjMPSQu6qIIzUmT1e5KcU1OZxRDU1g/am6mzBvcrmLAYwzmB59BHPrh5/tKaiF4OPhqRWESQ==", "dev": true }, + "js-md5": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/js-md5/-/js-md5-0.7.3.tgz", + "integrity": "sha512-ZC41vPSTLKGwIRjqDh8DfXoCrdQIyBgspJVPXHBGu4nZlAEvG3nf+jO9avM9RmLiGakg7vz974ms99nEV0tmTQ==" + }, "js-tokens": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", @@ -7770,7 +7883,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", - "dev": true, "requires": { "p-locate": "2.0.0", "path-exists": "3.0.0" @@ -7966,7 +8078,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", - "dev": true, "requires": { "pify": "3.0.0" } @@ -8938,7 +9049,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.2.0.tgz", "integrity": "sha512-Y/OtIaXtUPr4/YpMv1pCL5L5ed0rumAaAeBSj12F+bSlMdys7i8oQF/GUJmfpTS/QoaRrS/k6pma29haJpsMng==", - "dev": true, "requires": { "p-try": "1.0.0" } @@ -8947,7 +9057,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", - "dev": true, "requires": { "p-limit": "1.2.0" } @@ -8976,8 +9085,7 @@ "p-try": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", - "dev": true + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=" }, "pako": { "version": "1.0.6", @@ -9092,8 +9200,7 @@ "path-exists": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" }, "path-is-absolute": { "version": "1.0.1", @@ -9181,8 +9288,7 @@ "pify": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" }, "pinkie": { "version": "2.0.4", @@ -9208,6 +9314,14 @@ "find-up": "2.1.0" } }, + "pkg-up": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-2.0.0.tgz", + "integrity": "sha1-yBmscoBZpGHKscOImivjxJoATX8=", + "requires": { + "find-up": "2.1.0" + } + }, "plist": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/plist/-/plist-2.1.0.tgz", @@ -10357,6 +10471,19 @@ "deep-diff": "0.3.8" } }, + "redux-persist": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/redux-persist/-/redux-persist-5.10.0.tgz", + "integrity": "sha512-sSJAzNq7zka3qVHKce1hbvqf0Vf5DuTVm7dr4GtsqQVOexnrvbV47RWFiPxQ8fscnyiuWyD2O92DOxPl0tGCRg==" + }, + "redux-persist-electron-storage": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/redux-persist-electron-storage/-/redux-persist-electron-storage-1.1.2.tgz", + "integrity": "sha512-A2BGuNpV3DFuqQ4X/zd71FRZer5+WzbiXDyzXDiAFsegmKhPDzzasN/zR69T85pqdEQ3RQCMsyZFj+97RmjsVg==", + "requires": { + "electron-store": "1.3.0" + } + }, "regenerate": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz", diff --git a/package.json b/package.json index 0cb021d..d72e3cc 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "readmeFilename": "README.md", "license": "Apache-2.0", "dependencies": { + "@atlaskit/avatar": "11.0.1", "@atlaskit/button": "7.2.5", "@atlaskit/css-reset": "2.0.2", "@atlaskit/droplist": "6.0.1", @@ -43,12 +44,15 @@ "electron-window-state": "4.1.1", "history": "4.7.2", "jitsi-meet-electron-utils": "github:jitsi/jitsi-meet-electron-utils#1972c3bf0884ace68eb496894dabae593d6dbf49", + "js-md5": "0.7.3", "react": "16.3.2", "react-dom": "16.3.2", "react-redux": "5.0.7", "react-router-redux": "5.0.0-alpha.9", "redux": "4.0.0", "redux-logger": "3.0.6", + "redux-persist": "5.10.0", + "redux-persist-electron-storage": "1.1.2", "styled-components": "3.3.0" }, "devDependencies": { |