From 7620e616d8de86a1a7cc92bd1cd2ab5e4e30d83c Mon Sep 17 00:00:00 2001 From: Josh <37798644+joshuaellis@users.noreply.github.com> Date: Tue, 19 Mar 2019 21:34:38 +0000 Subject: [PATCH] #352 Disable actions that require an internet connection (#368) * Online check component added * add reducer & action for online check component. also included in App.js * removed sidebar functionality when offline * removed depedency functionality when offline * removed functionality from create new project wizard when offline * fixed tests to align with disabled pattern * add infoBanner to Z-indexes for future usage. * removed random {' '}, changed IS_ONLINE_CHECK to SET_ONLINE_STATUS * pull request requested changes missed one file. * updated infobar to be fixed header. * flow fix * remove random whitespace * Pull request changes remove state type, move styled components to before redux part, change background color of infobar to be transparent. * enable menu items based on isOnline --- package.json | 2 +- src/actions/index.js | 10 ++- src/components/App/App.js | 2 + .../ApplicationMenu/ApplicationMenu.js | 18 +++- src/components/Button/ButtonBase.js | 1 + src/components/Button/FillButton.js | 1 + .../__snapshots__/ButtonBase.test.js.snap | 8 ++ .../__snapshots__/FillButton.test.js.snap | 1 + .../__snapshots__/StrokeButton.test.js.snap | 1 + .../__snapshots__/ButtonWithIcon.test.js.snap | 1 + .../CreateNewProjectWizard.js | 39 +++++++-- .../CreateNewProjectWizard/MainPane.js | 12 ++- .../CreateNewProjectWizard/SubmitButton.js | 8 +- .../DependencyDetails/DependencyDetails.js | 10 +-- .../DependencyDetailsTable.js | 29 ++++--- .../DependencyInfoFromNpm.js | 62 +++++++++++-- .../DependencyManagementPane.js | 48 +++++++---- .../DependencyUpdateRow.js | 66 +++++++++----- src/components/OnlineChecker/OnlineChecker.js | 86 +++++++++++++++++++ src/components/OnlineChecker/index.js | 2 + src/components/Sidebar/AddProjectButton.js | 12 ++- src/components/Sidebar/Sidebar.js | 32 +++++-- src/constants.js | 1 + src/reducers/app-status.reducer.js | 10 +++ 24 files changed, 379 insertions(+), 83 deletions(-) create mode 100644 src/components/OnlineChecker/OnlineChecker.js create mode 100644 src/components/OnlineChecker/index.js diff --git a/package.json b/package.json index 654a1629..38fbfe91 100644 --- a/package.json +++ b/package.json @@ -225,4 +225,4 @@ "icon": null, "createdAt": 1529502079329 } -} \ No newline at end of file +} diff --git a/src/actions/index.js b/src/actions/index.js index 0e5a0af7..08a43acd 100644 --- a/src/actions/index.js +++ b/src/actions/index.js @@ -15,6 +15,7 @@ import type { // // Action Types // +export const SET_ONLINE_STATUS = 'SET_ONLINE_STATUS'; export const REFRESH_PROJECTS_START = 'REFRESH_PROJECTS_START'; export const REFRESH_PROJECTS_ERROR = 'REFRESH_PROJECTS_ERROR'; export const REFRESH_PROJECTS_FINISH = 'REFRESH_PROJECTS_FINISH'; @@ -89,6 +90,11 @@ export const RESET_STATUS_TEXT = 'RESET_STATUS_TEXT'; // // Action Creators // +export const setOnlineStatus = (onlineStatus: boolean) => ({ + type: SET_ONLINE_STATUS, + onlineStatus, +}); + export const addProject = ( project: ProjectInternal, projectHomePath: string, @@ -447,7 +453,9 @@ export const showResetStatePrompt = () => ({ type: SHOW_RESET_STATE_PROMPT, }); -export const resetAllState = () => ({ type: RESET_ALL_STATE }); +export const resetAllState = () => ({ + type: RESET_ALL_STATE, +}); // Status text for Loading screen // todo: Check if we need a better naming as there are probably more status messages in the future. diff --git a/src/components/App/App.js b/src/components/App/App.js index cce84ae0..a47a8c84 100644 --- a/src/components/App/App.js +++ b/src/components/App/App.js @@ -17,6 +17,7 @@ import AppSettingsModal from '../AppSettingsModal'; import Initialization from '../Initialization'; import LoadingScreen from '../LoadingScreen'; import FeedbackButton from '../FeedbackButton'; +import OnlineChecker from '../OnlineChecker'; import type { Project } from '../../types'; @@ -33,6 +34,7 @@ class App extends PureComponent { {wasSuccessfullyInitialized => wasSuccessfullyInitialized && ( + diff --git a/src/components/ApplicationMenu/ApplicationMenu.js b/src/components/ApplicationMenu/ApplicationMenu.js index 03e2d566..e3056c42 100644 --- a/src/components/ApplicationMenu/ApplicationMenu.js +++ b/src/components/ApplicationMenu/ApplicationMenu.js @@ -22,6 +22,7 @@ import { getProjectsArray, } from '../../reducers/projects.reducer'; import { getDevServerTaskForProjectId } from '../../reducers/tasks.reducer'; +import { getOnlineState } from '../../reducers/app-status.reducer'; import type { Project, Task } from '../../types'; import type { Dispatch } from '../../actions/types'; @@ -32,6 +33,7 @@ type Props = { projects: Array, selectedProject: ?Project, devServerTask: ?Task, + isOnline: boolean, createNewProjectStart: Dispatch, showImportExistingProjectPrompt: Dispatch< typeof actions.showImportExistingProjectPrompt @@ -53,7 +55,10 @@ class ApplicationMenu extends Component { } componentDidUpdate(prevProps) { - if (this.props.selectedProject !== prevProps.selectedProject) { + if ( + this.props.selectedProject !== prevProps.selectedProject || + this.props.isOnline !== prevProps.isOnline + ) { this.buildMenu(this.props); } } @@ -76,6 +81,7 @@ class ApplicationMenu extends Component { selectProject, projects, reinstallDependencies, + isOnline, } = props; const template = [ @@ -87,6 +93,7 @@ class ApplicationMenu extends Component { label: isMac ? 'Create New Project' : 'Create &new project', click: createNewProjectStart, accelerator: 'CmdOrCtrl+N', + enabled: isOnline, }, { label: isMac @@ -94,6 +101,7 @@ class ApplicationMenu extends Component { : '&Import existing project...', click: showImportExistingProjectPrompt, accelerator: 'CmdOrCtrl+I', + enabled: isOnline, }, ], }, @@ -233,6 +241,7 @@ class ApplicationMenu extends Component { label: isMac ? 'Reinstall Dependencies' : 'Reinstall dependencies', click: () => reinstallDependencies(selectedProject.id), accelerator: 'CmdOrCtrl+alt+R', + enabled: isOnline, }, { type: 'separator' }, ]; @@ -308,7 +317,12 @@ const mapStateToProps = state => { : null; const projects = getProjectsArray(state); - return { selectedProject, devServerTask, projects }; + return { + selectedProject, + devServerTask, + projects, + isOnline: getOnlineState(state), + }; }; const mapDispatchToProps = { diff --git a/src/components/Button/ButtonBase.js b/src/components/Button/ButtonBase.js index 56c370ac..380dbcf7 100644 --- a/src/components/Button/ButtonBase.js +++ b/src/components/Button/ButtonBase.js @@ -84,6 +84,7 @@ const ButtonBaseStyles = styled.button` &:disabled { filter: grayscale(100%); opacity: 0.75; + cursor: initial; } &:not(:disabled):active { diff --git a/src/components/Button/FillButton.js b/src/components/Button/FillButton.js index 191fa8bb..eb6f8f76 100644 --- a/src/components/Button/FillButton.js +++ b/src/components/Button/FillButton.js @@ -10,6 +10,7 @@ type Props = { hoverColors?: Array, textColor: string, children: React$Node, + disabled?: boolean, }; export const wrapColorsInGradient = (colors?: Array | string) => { diff --git a/src/components/Button/__snapshots__/ButtonBase.test.js.snap b/src/components/Button/__snapshots__/ButtonBase.test.js.snap index acda4b95..96666486 100644 --- a/src/components/Button/__snapshots__/ButtonBase.test.js.snap +++ b/src/components/Button/__snapshots__/ButtonBase.test.js.snap @@ -27,6 +27,7 @@ exports[`ButtonBase component Button with size prop should render button for lar -webkit-filter: grayscale(100%); filter: grayscale(100%); opacity: 0.75; + cursor: initial; } .c1:not(:disabled):active { @@ -91,6 +92,7 @@ exports[`ButtonBase component Button with size prop should render button for lar -webkit-filter: grayscale(100%); filter: grayscale(100%); opacity: 0.75; + cursor: initial; } .c1:not(:disabled):active { @@ -158,6 +160,7 @@ exports[`ButtonBase component Button with size prop should render button for med -webkit-filter: grayscale(100%); filter: grayscale(100%); opacity: 0.75; + cursor: initial; } .c1:not(:disabled):active { @@ -222,6 +225,7 @@ exports[`ButtonBase component Button with size prop should render button for med -webkit-filter: grayscale(100%); filter: grayscale(100%); opacity: 0.75; + cursor: initial; } .c1:not(:disabled):active { @@ -289,6 +293,7 @@ exports[`ButtonBase component Button with size prop should render button for sma -webkit-filter: grayscale(100%); filter: grayscale(100%); opacity: 0.75; + cursor: initial; } .c1:not(:disabled):active { @@ -353,6 +358,7 @@ exports[`ButtonBase component Button with size prop should render button for sma -webkit-filter: grayscale(100%); filter: grayscale(100%); opacity: 0.75; + cursor: initial; } .c1:not(:disabled):active { @@ -420,6 +426,7 @@ exports[`ButtonBase component Button with size prop should render button for xsm -webkit-filter: grayscale(100%); filter: grayscale(100%); opacity: 0.75; + cursor: initial; } .c1:not(:disabled):active { @@ -484,6 +491,7 @@ exports[`ButtonBase component Button with size prop should render button for xsm -webkit-filter: grayscale(100%); filter: grayscale(100%); opacity: 0.75; + cursor: initial; } .c1:not(:disabled):active { diff --git a/src/components/Button/__snapshots__/FillButton.test.js.snap b/src/components/Button/__snapshots__/FillButton.test.js.snap index 75b8aa7b..9fed93d7 100644 --- a/src/components/Button/__snapshots__/FillButton.test.js.snap +++ b/src/components/Button/__snapshots__/FillButton.test.js.snap @@ -31,6 +31,7 @@ exports[`FillButton component should render button filled 1`] = ` -webkit-filter: grayscale(100%); filter: grayscale(100%); opacity: 0.75; + cursor: initial; } .c1:not(:disabled):active { diff --git a/src/components/Button/__snapshots__/StrokeButton.test.js.snap b/src/components/Button/__snapshots__/StrokeButton.test.js.snap index ab735c3e..9e28a098 100644 --- a/src/components/Button/__snapshots__/StrokeButton.test.js.snap +++ b/src/components/Button/__snapshots__/StrokeButton.test.js.snap @@ -57,6 +57,7 @@ exports[`StrokeButton component should render button with stroke outline 1`] = ` -webkit-filter: grayscale(100%); filter: grayscale(100%); opacity: 0.75; + cursor: initial; } .c3:not(:disabled):active { diff --git a/src/components/ButtonWithIcon/__snapshots__/ButtonWithIcon.test.js.snap b/src/components/ButtonWithIcon/__snapshots__/ButtonWithIcon.test.js.snap index c50e723e..91ade30b 100644 --- a/src/components/ButtonWithIcon/__snapshots__/ButtonWithIcon.test.js.snap +++ b/src/components/ButtonWithIcon/__snapshots__/ButtonWithIcon.test.js.snap @@ -27,6 +27,7 @@ exports[`ButtonWithIcon component should render button with icon 1`] = ` -webkit-filter: grayscale(100%); filter: grayscale(100%); opacity: 0.75; + cursor: initial; } .c3:not(:disabled):active { diff --git a/src/components/CreateNewProjectWizard/CreateNewProjectWizard.js b/src/components/CreateNewProjectWizard/CreateNewProjectWizard.js index e31ed10e..d1f62a89 100644 --- a/src/components/CreateNewProjectWizard/CreateNewProjectWizard.js +++ b/src/components/CreateNewProjectWizard/CreateNewProjectWizard.js @@ -10,6 +10,7 @@ import { getDefaultProjectPath, } from '../../reducers/app-settings.reducer'; import { getById } from '../../reducers/projects.reducer'; +import { getOnlineState } from '../../reducers/app-status.reducer'; import { getOnboardingCompleted } from '../../reducers/onboarding-status.reducer'; import { getProjectNameSlug } from '../../services/create-project.service'; import { checkIfProjectExists } from '../../services/create-project.service'; @@ -38,13 +39,16 @@ const { dialog } = remote; type Props = { settings: AppSettings, - projects: { [projectId: string]: ProjectInternal }, + projects: { + [projectId: string]: ProjectInternal, + }, projectHomePath: string, isVisible: boolean, isOnboardingCompleted: boolean, addProject: Dispatch, createNewProjectCancel: Dispatch, createNewProjectFinish: Dispatch, + isOnline: boolean, }; type State = { @@ -84,7 +88,10 @@ class CreateNewProjectWizard extends PureComponent { } updateFieldValue = (field: Field, value: any) => { - this.setState({ [field]: value, activeField: field }); + this.setState({ + [field]: value, + activeField: field, + }); if (field === 'projectName') { this.verifyProjectNameUniqueness(value); @@ -92,7 +99,9 @@ class CreateNewProjectWizard extends PureComponent { }; focusField = (field: ?Field) => { - this.setState({ activeField: field }); + this.setState({ + activeField: field, + }); }; verifyProjectNameUniqueness = (name: string) => { @@ -105,13 +114,17 @@ class CreateNewProjectWizard extends PureComponent { ); if (isAlreadyTaken) { - this.setState({ isProjectNameTaken: true }); + this.setState({ + isProjectNameTaken: true, + }); return; } // If this update fixes the problem, unset the error status if (!isAlreadyTaken && this.state.isProjectNameTaken) { - this.setState({ isProjectNameTaken: false }); + this.setState({ + isProjectNameTaken: false, + }); } }; @@ -226,7 +239,12 @@ class CreateNewProjectWizard extends PureComponent { }; render() { - const { isVisible, createNewProjectCancel, projectHomePath } = this.props; + const { + isVisible, + createNewProjectCancel, + projectHomePath, + isOnline, + } = this.props; const { projectName, projectType, @@ -238,7 +256,12 @@ class CreateNewProjectWizard extends PureComponent { isProjectNameTaken, } = this.state; - const project = { projectName, projectType, projectIcon, projectStarter }; + const project = { + projectName, + projectType, + projectIcon, + projectStarter, + }; const readyToBeBuilt = status !== 'filling-in-form'; @@ -271,6 +294,7 @@ class CreateNewProjectWizard extends PureComponent { handleSubmit={this.handleSubmit} hasBeenSubmitted={status !== 'filling-in-form'} isProjectNameTaken={isProjectNameTaken} + isOnline={isOnline} /> } backface={ @@ -295,6 +319,7 @@ const mapStateToProps = state => ({ isVisible: state.modal === 'new-project-wizard', isOnboardingCompleted: getOnboardingCompleted(state), settings: getAppSettings(state), + isOnline: getOnlineState(state), }); const mapDispatchToProps = { diff --git a/src/components/CreateNewProjectWizard/MainPane.js b/src/components/CreateNewProjectWizard/MainPane.js index 21e99243..fbaf6ff3 100644 --- a/src/components/CreateNewProjectWizard/MainPane.js +++ b/src/components/CreateNewProjectWizard/MainPane.js @@ -29,6 +29,7 @@ type Props = { updateFieldValue: (field: Field, value: any) => void, focusField: (field: ?Field) => void, handleSubmit: () => Promise | void, + isOnline: boolean, }; class MainPane extends PureComponent { @@ -141,14 +142,19 @@ class MainPane extends PureComponent { hasBeenSubmitted, isProjectNameTaken, handleSubmit, + isOnline, } = this.props; const { lastIndex, steps } = this.renderConditionalSteps(currentStepIndex); return ( {({ offset }) => ( @@ -163,7 +169,6 @@ class MainPane extends PureComponent { isProjectNameTaken={isProjectNameTaken} /> - {steps} )} @@ -175,6 +180,7 @@ class MainPane extends PureComponent { !projectName || this.isSubmitDisabled(currentStepIndex, lastIndex) } + isOnline={isOnline} readyToBeSubmitted={currentStepIndex >= lastIndex} hasBeenSubmitted={hasBeenSubmitted} onSubmit={handleSubmit} diff --git a/src/components/CreateNewProjectWizard/SubmitButton.js b/src/components/CreateNewProjectWizard/SubmitButton.js index 39881100..28d9d09b 100644 --- a/src/components/CreateNewProjectWizard/SubmitButton.js +++ b/src/components/CreateNewProjectWizard/SubmitButton.js @@ -13,6 +13,7 @@ import Spinner from '../Spinner'; type Props = { readyToBeSubmitted: boolean, hasBeenSubmitted: boolean, + isOnline: boolean, isDisabled: boolean, onSubmit: () => ?Promise, }; @@ -22,6 +23,7 @@ const SubmitButton = ({ hasBeenSubmitted, isDisabled, onSubmit, + isOnline, }: Props) => { const buttonText = hasBeenSubmitted ? 'Building...' @@ -31,7 +33,11 @@ const SubmitButton = ({ return ( { render() { - const { projectId, dependency } = this.props; + const { projectId, dependency, isOnline } = this.props; return ( @@ -32,12 +33,11 @@ class DependencyDetails extends PureComponent { reason="Optical symmetry between top and left edge of parent" > - {dependency.name} - {dependency.description} + {dependency.name} + {dependency.description} - { latestVersion={latestVersion} /> - diff --git a/src/components/DependencyDetailsTable/DependencyDetailsTable.js b/src/components/DependencyDetailsTable/DependencyDetailsTable.js index 8509a4e8..69ce3750 100644 --- a/src/components/DependencyDetailsTable/DependencyDetailsTable.js +++ b/src/components/DependencyDetailsTable/DependencyDetailsTable.js @@ -19,11 +19,12 @@ type Props = { projectId: string, dependency: Dependency, lastUpdatedAt: number, + isOnline: boolean, }; class DependencyDetailsTable extends Component { render() { - const { projectId, dependency, lastUpdatedAt } = this.props; + const { projectId, dependency, lastUpdatedAt, isOnline } = this.props; const packageHref = `https://www.npmjs.org/package/${dependency.name}`; let githubHref; @@ -57,38 +58,37 @@ class DependencyDetailsTable extends Component { - - + {lastUpdatedAt ? ( moment(lastUpdatedAt).fromNow() - ) : ( + ) : isOnline ? ( + ) : ( + '–' )} - - + - - + - NPM + NPM {githubHref && } {githubHref && ( - GitHub + GitHub )} {dependency.homepage && } {dependency.homepage && ( @@ -98,10 +98,15 @@ class DependencyDetailsTable extends Component { )} - - + React$Node, + isOnline: boolean, +}; + +type State = { + refresh: boolean, }; const FilterByIds = connectRefinementList(() => null); @@ -30,7 +38,9 @@ const Result = connectHits(({ hits, packageName, children }: any) => { if (!hit || hit.name !== packageName) { // TODO: Presumably there's a HOC to figure out loading state, I should // use that instead of just assuming if it doesn't exist, it's loading. - return children({ isLoading: true }); + return children({ + isLoading: true, + }); } const info = { @@ -43,21 +53,59 @@ const Result = connectHits(({ hits, packageName, children }: any) => { return children(info); }); -class DependencyInfoFromNpm extends Component { +class DependencyInfoFromNpm extends Component { + /* + * Requires internal state to handle the refresh of the cache. + */ + constructor(props) { + super(props); + this.state = { + refresh: false, + }; + } + /* + * When the app is offline and comes back online this will refresh the cache and search again for updates + * This is particularly important when the app is launched offline and then connects to internet. + */ + componentDidUpdate(prevProps) { + if (this.props.isOnline && this.props.isOnline !== prevProps.isOnline) { + this.refreshCache(); + } + } + refreshCache = () => { + this.setState( + { + refresh: true, + }, + () => { + this.setState({ + refresh: false, + }); + } + ); + }; render() { - const { packageName, children } = this.props; - + const { packageName, children, isOnline } = this.props; return ( - + - {children} + + {children} + ); } } -export default DependencyInfoFromNpm; +const mapStateToProps = state => ({ + isOnline: getOnlineState(state), +}); + +export default connect( + mapStateToProps, + null +)(DependencyInfoFromNpm); diff --git a/src/components/DependencyManagementPane/DependencyManagementPane.js b/src/components/DependencyManagementPane/DependencyManagementPane.js index acd07b4b..f8de7e4a 100644 --- a/src/components/DependencyManagementPane/DependencyManagementPane.js +++ b/src/components/DependencyManagementPane/DependencyManagementPane.js @@ -6,6 +6,7 @@ import IconBase from 'react-icons-kit'; import { plus } from 'react-icons-kit/feather/plus'; import { getSelectedProject } from '../../reducers/projects.reducer'; +import { getOnlineState } from '../../reducers/app-status.reducer'; import { COLORS, GUPPY_REPO_URL } from '../../constants'; import Module from '../Module'; @@ -23,6 +24,7 @@ import type { Project } from '../../types'; type Props = { project: Project, + isOnline: boolean, }; type State = { @@ -58,7 +60,9 @@ class DependencyManagementPane extends PureComponent { ) ); - this.setState({ selectedDependencyIndex: newDependencyIndex }); + this.setState({ + selectedDependencyIndex: newDependencyIndex, + }); } // If the last dependency was deleted, we need to shift focus to the new last dependency @@ -90,15 +94,21 @@ class DependencyManagementPane extends PureComponent { const index = this.props.project.dependencies.findIndex( ({ name }) => name === dependencyName ); - this.setState({ selectedDependencyIndex: index }); + this.setState({ + selectedDependencyIndex: index, + }); }; openAddNewDependencyModal = () => { - this.setState({ addingNewDependency: true }); + this.setState({ + addingNewDependency: true, + }); }; closeAddNewDependencyModal = () => { - this.setState({ addingNewDependency: false }); + this.setState({ + addingNewDependency: false, + }); }; renderListAddon = (dependency, isSelected) => { @@ -118,7 +128,7 @@ class DependencyManagementPane extends PureComponent { ); }; - renderMainContents = (selectedDependency, projectId) => { + renderMainContents = (selectedDependency, projectId, isOnline) => { if ( selectedDependency.status === 'installing' || selectedDependency.status === 'queued-install' @@ -133,6 +143,7 @@ class DependencyManagementPane extends PureComponent { return ( @@ -142,9 +153,8 @@ class DependencyManagementPane extends PureComponent { render() { const { id, dependencies } = this.props.project; const { selectedDependencyIndex, addingNewDependency } = this.state; - const selectedDependency = dependencies[selectedDependencyIndex]; - + const { isOnline } = this.props; return ( { isSelected={selectedDependencyIndex === index} onClick={() => this.selectDependency(dependency.name)} > - {dependency.name} + {dependency.name} {this.renderListAddon( dependency, selectedDependencyIndex === index @@ -185,21 +195,27 @@ class DependencyManagementPane extends PureComponent { See the bug in action: https://imgur.com/a/SanrY61 `} > - - - + + Add New - + Dependency - {this.renderMainContents(selectedDependency, id)} + {this.renderMainContents(selectedDependency, id, isOnline)} - (props.isOnline ? 1 : 0.5)}; + pointer-events: ${props => (props.isOnline ? 'auto' : 'none')}; &:hover { border: 2px dashed ${COLORS.gray[400]}; color: ${COLORS.gray[600]}; @@ -327,6 +344,7 @@ const MainContent = Card.extend` const mapStateToProps = state => ({ project: getSelectedProject(state), + isOnline: getOnlineState(state), }); export default connect(mapStateToProps)(DependencyManagementPane); diff --git a/src/components/DependencyUpdateRow/DependencyUpdateRow.js b/src/components/DependencyUpdateRow/DependencyUpdateRow.js index f2a6fcea..d5f2c7f5 100644 --- a/src/components/DependencyUpdateRow/DependencyUpdateRow.js +++ b/src/components/DependencyUpdateRow/DependencyUpdateRow.js @@ -8,6 +8,8 @@ import { check } from 'react-icons-kit/feather/check'; import * as actions from '../../actions'; import { COLORS } from '../../constants'; +import { getOnlineState } from '../../reducers/app-status.reducer'; + import { FillButton } from '../Button'; import Label from '../Label'; import Spinner from '../Spinner'; @@ -21,7 +23,8 @@ type Props = { projectId: string, dependency: Dependency, isLoadingNpmInfo: boolean, - latestVersion: ?string, + latestVersion: string, + isOnline: boolean, // From redux: updateDependency: Dispatch, }; @@ -34,14 +37,17 @@ class DependencyUpdateRow extends Component { isLoadingNpmInfo, latestVersion, updateDependency, + isOnline, } = this.props; - if (isLoadingNpmInfo || !latestVersion) { - return ( - - - - ); + if (isOnline) { + if (isLoadingNpmInfo || !latestVersion) { + return ( + + + + ); + } } const isUpToDate = dependency.version === latestVersion; @@ -49,15 +55,23 @@ class DependencyUpdateRow extends Component { return isUpToDate ? ( - + - Up-to-date + Up - to - date ) : ( updateDependency(projectId, dependency.name, latestVersion) } @@ -68,21 +82,19 @@ class DependencyUpdateRow extends Component { } render() { - const { dependency, latestVersion } = this.props; + const { dependency, latestVersion, isOnline } = this.props; return ( - Latest Version - {latestVersion || '--'} + Latest Version + {latestVersion || '--'} - - Installed Version - {dependency.version} + Installed Version + {dependency.version} - - {this.renderActionColumn()} + {this.renderActionColumn()} ); } @@ -99,6 +111,14 @@ const Col = styled.div` text-align: center; `; +const UpdateCol = styled.div` + width: 150px; + text-align: center; + opacity: ${props => (props.isOnline ? 1 : 0.5)}; + pointer-events: ${props => (props.isOnline ? 'auto' : 'none')}; +`; + +// eslint-disable-next-line no-unexpected-multiline const VersionLabel = styled(Label)` color: ${COLORS.gray[600]}; `; @@ -120,7 +140,13 @@ const UpToDate = styled.div` font-weight: 500; `; +const mapStateToProps = state => ({ + isOnline: getOnlineState(state), +}); + export default connect( - null, - { updateDependency: actions.updateDependency } + mapStateToProps, + { + updateDependency: actions.updateDependency, + } )(DependencyUpdateRow); diff --git a/src/components/OnlineChecker/OnlineChecker.js b/src/components/OnlineChecker/OnlineChecker.js new file mode 100644 index 00000000..e38ffc43 --- /dev/null +++ b/src/components/OnlineChecker/OnlineChecker.js @@ -0,0 +1,86 @@ +//@flow +/* + This component checks to see if the the user is online or not. + Failing to be online should feedback to the user as opposed to leaving them hanging. +*/ +import React from 'react'; +import { connect } from 'react-redux'; +import styled from 'styled-components'; +import { Z_INDICES, COLORS } from '../../constants'; + +import * as actions from '../../actions'; +import { getOnlineState } from '../../reducers/app-status.reducer'; + +import type { Dispatch } from '../../actions/types'; + +type Props = { + isOnline: boolean, + setOnlineStatus: Dispatch, +}; + +class OnlineChecker extends React.PureComponent { + componentDidMount() { + window.addEventListener('online', this.check); + window.addEventListener('offline', this.check); + } + check = () => { + this.props.setOnlineStatus(navigator.onLine); + }; + componentWillUnmount() { + window.removeEventListener('online', this.check); + window.removeEventListener('offline', this.check); + } + render() { + const { isOnline } = this.props; + if (!isOnline) { + return ( + + + + + +

You are currently offline, some functions will not be available

+
+ ); + } + return null; + } +} + +const InfoBar = styled.div` + width: 100vw; + display: flex; + justify-content: center; + position: fixed; + left: 0; + top: 0; + z-index: ${Z_INDICES.infoBanner}; + background-color: ${COLORS.transparentWhite[100]}; + padding: 16px; + align-content: center; + box-shadow: 0px 2px 2px 0px ${COLORS.transparentBlack[900]}; +`; + +const SVG = styled.svg` + width: 16px; + height: 16px; + fill: ${COLORS.red[500]}; + margin: 2px 8px; +`; + +const mapStateToProps = state => ({ + isOnline: getOnlineState(state), +}); + +export default connect( + mapStateToProps, + { + setOnlineStatus: actions.setOnlineStatus, + } +)(OnlineChecker); diff --git a/src/components/OnlineChecker/index.js b/src/components/OnlineChecker/index.js new file mode 100644 index 00000000..4b6d27f7 --- /dev/null +++ b/src/components/OnlineChecker/index.js @@ -0,0 +1,2 @@ +// @flow +export { default } from './OnlineChecker'; diff --git a/src/components/Sidebar/AddProjectButton.js b/src/components/Sidebar/AddProjectButton.js index 26b0b8ad..f442f630 100644 --- a/src/components/Sidebar/AddProjectButton.js +++ b/src/components/Sidebar/AddProjectButton.js @@ -10,10 +10,16 @@ type Props = { size: number, isVisible: boolean, onClick: () => void, + isOnline: boolean, }; -const AddProjectButton = ({ size, isVisible, onClick }: Props) => ( -