Skip to content
This repository has been archived by the owner on Jun 17, 2021. It is now read-only.

Gatsby starter #335

Merged
merged 24 commits into from
Jan 21, 2019
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@
"electron-updater": "3.1.2",
"fix-path": "2.1.0",
"gatsby-cli": "1.1.58",
"js-yaml": "3.12.0",
"node-fetch": "2.3.0",
"ps-tree": "1.1.0",
"react-custom-scrollbars": "4.2.1",
"rimraf": "2.6.2",
Expand Down
10 changes: 10 additions & 0 deletions src/actions/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ export const CREATE_NEW_PROJECT_CANCEL = 'CREATE_NEW_PROJECT_CANCEL';
export const CREATE_NEW_PROJECT_FINISH = 'CREATE_NEW_PROJECT_FINISH';
export const ADD_PROJECT = 'ADD_PROJECT';
export const SHOW_MODAL = 'SHOW_MODAL';
export const SHOW_STARTER_SELECTION = 'SHOW_STARTER_SELECTION';
export const HIDE_STARTER_SELECTION = 'HIDE_STARTER_SELECTION';
export const CHANGE_PROJECT_HOME_PATH = 'CHANGE_PROJECT_HOME_PATH';
export const HIDE_MODAL = 'HIDE_MODAL';
export const DISMISS_SIDEBAR_INTRO = 'DISMISS_SIDEBAR_INTRO';
Expand Down Expand Up @@ -417,4 +419,12 @@ export const showResetStatePrompt = () => ({
type: SHOW_RESET_STATE_PROMPT,
});

export const showStarterSelectionModal = () => ({
type: SHOW_STARTER_SELECTION,
});

export const hideStarterSelectionModal = () => ({
type: HIDE_STARTER_SELECTION,
});

export const resetAllState = () => ({ type: RESET_ALL_STATE });
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
} from '../../reducers/projects.reducer';
import { COLORS } from '../../constants';

import Divider from '../Divider';
import Spacer from '../Spacer';
import Spinner from '../Spinner';
import ExternalLink from '../ExternalLink';
Expand Down Expand Up @@ -211,12 +212,6 @@ const Description = styled.div`
margin-right: 120px;
`;

const Divider = styled.div`
width: 100%;
height: 1px;
background: ${COLORS.gray[100]};
`;

const StatsItemHighlight = styled.span`
font-weight: 600;
-webkit-font-smoothing: antialiased;
Expand Down
17 changes: 15 additions & 2 deletions src/components/CreateNewProjectWizard/BuildPane.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ type Props = {
projectName: string,
projectType: ?ProjectType,
projectIcon: ?string,
projectStarter: string,
status: Status,
projectHomePath: string,
handleCompleteBuild: (project: ProjectInternal) => void,
Expand Down Expand Up @@ -79,7 +80,12 @@ class BuildPane extends PureComponent<Props, State> {
}

buildProject = () => {
const { projectName, projectType, projectIcon } = this.props;
const {
projectName,
projectType,
projectIcon,
projectStarter: projectStarterInput,
} = this.props;

if (!projectName || !projectType || !projectIcon) {
console.error('Missing one of:', {
Expand All @@ -92,8 +98,15 @@ class BuildPane extends PureComponent<Props, State> {
);
}

// Add url to starter if not passed
// Todo: We need error handling to show a notification that it failed to use the starter (e.g. starter doesn't exists or wrong url/name)
// --> Probably just needed if we allow the user to enter an url to a starter.
const projectStarter = !projectStarterInput.includes('http')
? 'https://github.com/gatsbyjs/' + projectStarterInput
: projectStarterInput;

createProject(
{ projectName, projectType, projectIcon },
{ projectName, projectType, projectIcon, projectStarter },
this.props.projectHomePath,
this.handleStatusUpdate,
this.handleError,
Expand Down
92 changes: 54 additions & 38 deletions src/components/CreateNewProjectWizard/CreateNewProjectWizard.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// @flow
import React, { PureComponent } from 'react';
import React, { Fragment, PureComponent } from 'react';
import { connect } from 'react-redux';
import Transition from 'react-transition-group/Transition';
import { remote } from 'electron';
Expand All @@ -20,19 +20,26 @@ import Debounced from '../Debounced';
import MainPane from './MainPane';
import SummaryPane from './SummaryPane';
import BuildPane from './BuildPane';
import SelectStarterDialog from './Gatsby/SelectStarterDialog';

import type { Field, Status, Step } from './types';

import type { ProjectType, ProjectInternal, AppSettings } from '../../types';
import type { Dispatch } from '../../actions/types';

const FORM_STEPS: Array<Field> = ['projectName', 'projectType', 'projectIcon'];
const FORM_STEPS: Array<Field> = [
'projectName',
'projectType',
'projectStarter',
'projectIcon',
];
const { dialog } = remote;

type Props = {
settings: AppSettings,
projects: { [projectId: string]: ProjectInternal },
projectHomePath: string,
projectStarter: string,
isVisible: boolean,
isOnboardingCompleted: boolean,
addProject: Dispatch<typeof actions.addProject>,
Expand All @@ -44,6 +51,7 @@ type State = {
projectName: string,
projectType: ?ProjectType,
projectIcon: ?string,
projectStarter: string,
activeField: ?Field,
settings: ?AppSettings,
status: Status,
Expand All @@ -55,6 +63,7 @@ const initialState = {
projectName: '',
projectType: null,
projectIcon: null,
projectStarter: '',
activeField: 'projectName',
status: 'filling-in-form',
currentStep: 'projectName',
Expand Down Expand Up @@ -194,55 +203,62 @@ class CreateNewProjectWizard extends PureComponent<Props, State> {
projectName,
projectType,
projectIcon,
projectStarter,
activeField,
status,
currentStep,
isProjectNameTaken,
} = this.state;

const project = { projectName, projectType, projectIcon };
const project = { projectName, projectType, projectIcon, projectStarter };

const readyToBeBuilt = status !== 'filling-in-form';

return (
<Transition in={isVisible} timeout={300}>
{transitionState => (
<TwoPaneModal
isFolded={readyToBeBuilt}
transitionState={transitionState}
isDismissable={status !== 'building-project'}
onDismiss={createNewProjectCancel}
leftPane={
<Debounced on={activeField} duration={40}>
<SummaryPane
currentStep={currentStep}
<Fragment>
<TwoPaneModal
isFolded={readyToBeBuilt}
transitionState={transitionState}
isDismissable={status !== 'building-project'}
onDismiss={createNewProjectCancel}
leftPane={
<Debounced on={activeField} duration={40}>
<SummaryPane
currentStep={currentStep}
activeField={activeField}
projectType={projectType}
/>
</Debounced>
}
rightPane={
<MainPane
{...project}
status={status}
activeField={activeField}
projectType={projectType}
currentStepIndex={FORM_STEPS.indexOf(currentStep)}
updateFieldValue={this.updateFieldValue}
focusField={this.focusField}
handleSubmit={this.handleSubmit}
hasBeenSubmitted={status !== 'filling-in-form'}
isProjectNameTaken={isProjectNameTaken}
/>
</Debounced>
}
rightPane={
<MainPane
{...project}
status={status}
activeField={activeField}
currentStepIndex={FORM_STEPS.indexOf(currentStep)}
updateFieldValue={this.updateFieldValue}
focusField={this.focusField}
handleSubmit={this.handleSubmit}
hasBeenSubmitted={status !== 'filling-in-form'}
isProjectNameTaken={isProjectNameTaken}
/>
}
backface={
<BuildPane
{...project}
status={status}
projectHomePath={projectHomePath}
handleCompleteBuild={this.finishBuilding}
/>
}
/>
}
backface={
<BuildPane
{...project}
status={status}
projectHomePath={projectHomePath}
handleCompleteBuild={this.finishBuilding}
/>
}
/>
<SelectStarterDialog
updateFieldValue={this.updateFieldValue}
selectedStarter={projectStarter}
/>
</Fragment>
)}
</Transition>
);
Expand All @@ -252,7 +268,7 @@ class CreateNewProjectWizard extends PureComponent<Props, State> {
const mapStateToProps = state => ({
projects: getById(state),
projectHomePath: getDefaultProjectPath(state),
isVisible: state.modal === 'new-project-wizard',
isVisible: state.modal && state.modal.includes('new-project-wizard'),
isOnboardingCompleted: getOnboardingCompleted(state),
settings: getAppSettings(state),
});
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// @flow
import React, { PureComponent } from 'react';
import { connect } from 'react-redux';
// import styled from 'styled-components';

// import { COLORS } from '../../constants';

import TextInputWithButton from '../../TextInputWithButton';

import * as actions from '../../../actions';
import type { Dispatch } from '../../../actions/types';

type State = {
gatsbyStarter: string,
};

type Props = {
projectStarter: string,
isFocused: boolean,
handleFocus: string => void,
onSelect: string => void,
onFocus: () => void,
showStarterSelection: Dispatch<typeof actions.showStarterSelectionModal>,
};

class ProjectStarter extends PureComponent<Props, State> {
state = {
gatsbyStarter: '',
};

static getDerivedStateFromProps(nextProps: Props, prevState: State) {
return {
gatsbyStarter: nextProps.projectStarter,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, so I think in general this is a not-ideal pattern. I feel like either this component should own the state entirely, so it doesn't receive projectStarter from props, or it should always delegate to the parent's projectStarter.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you're right but with-out this it would require me to modify modal state as I need to have the info about the entered starter before opening the modal.
So the user can dismiss the changes made in the modal. Anyway I'm not changing this as I'll check if I can remove the second modal - so this won't be needed anymore.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's acutally an anti-pattern to override local state unconditionally with every prop change.
There is a nice post you-probably-dont-need-derived-state about this in the React.js blog.

We don't need it here anymore as I'm rewriting this. But I wanted to note this as I've learned this and I wasn't aware of it.

};
}

// Change method needed so we can dismiss the selection on close click of toastr
changeGatsbyStarter = (selectedStarter: string) => {
console.log('change starter', selectedStarter, this);
this.setState(
{
gatsbyStarter: selectedStarter,
},
() => {
console.log('updated', this.state);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like we don't need this callback?

Also, there's another console.log above it.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that's right. Sorry, I've missed that debug stuff. I'll remove it.

}
);
};

handleSelect = () => {
this.props.onSelect(this.state.gatsbyStarter);

// clear temporary state value
// this.setState({
// gatsbyStarter: '',
// });
};

render() {
const {
handleFocus,
projectStarter,
isFocused,
onSelect,
showStarterSelection,
} = this.props;

return (
<TextInputWithButton
handleFocus={handleFocus}
isFocused={isFocused}
value={projectStarter}
onChange={onSelect}
onClick={showStarterSelection}
/>
);
}
}
const mapDispatchToProps = {
showStarterSelection: actions.showStarterSelectionModal,
};

export default connect(
null,
mapDispatchToProps
)(ProjectStarter);
Loading