diff options
Diffstat (limited to 'packages/create-astro/src/components')
-rw-r--r-- | packages/create-astro/src/components/App.tsx | 93 | ||||
-rw-r--r-- | packages/create-astro/src/components/Confirm.tsx | 49 | ||||
-rw-r--r-- | packages/create-astro/src/components/Emoji.tsx | 5 | ||||
-rw-r--r-- | packages/create-astro/src/components/Exit.tsx | 9 | ||||
-rw-r--r-- | packages/create-astro/src/components/Finalize.tsx | 27 | ||||
-rw-r--r-- | packages/create-astro/src/components/Header.tsx | 20 | ||||
-rw-r--r-- | packages/create-astro/src/components/Help.tsx | 62 | ||||
-rw-r--r-- | packages/create-astro/src/components/Install.tsx | 19 | ||||
-rw-r--r-- | packages/create-astro/src/components/ProjectName.tsx | 24 | ||||
-rw-r--r-- | packages/create-astro/src/components/Select.tsx | 32 | ||||
-rw-r--r-- | packages/create-astro/src/components/Spacer.tsx | 5 | ||||
-rw-r--r-- | packages/create-astro/src/components/Spinner.tsx | 200 | ||||
-rw-r--r-- | packages/create-astro/src/components/Template.tsx | 23 | ||||
-rw-r--r-- | packages/create-astro/src/components/Version.tsx | 6 |
14 files changed, 574 insertions, 0 deletions
diff --git a/packages/create-astro/src/components/App.tsx b/packages/create-astro/src/components/App.tsx new file mode 100644 index 000000000..fd9192bb6 --- /dev/null +++ b/packages/create-astro/src/components/App.tsx @@ -0,0 +1,93 @@ +import React, {FC, useEffect} from 'react'; +import { prepareTemplate, isEmpty, emptyDir } from '../utils'; +import Header from './Header'; +import Install from './Install'; +import ProjectName from './ProjectName'; +import Template from './Template'; +import Confirm from './Confirm'; +import Finalize from './Finalize'; + +interface Context { + use: 'npm'|'yarn'; + run: boolean; + projectExists?: boolean; + force?: boolean; + projectName?: string; + template?: string; + templates: string[]; + ready?: boolean; +} + +const getStep = ({ projectName, projectExists: exists, template, force, ready }: Context) => { + switch (true) { + case !projectName: return { + key: 'projectName', + Component: ProjectName + }; + case projectName && exists === true && typeof force === 'undefined': return { + key: 'force', + Component: Confirm + } + case (exists === false || force) && !template: return { + key: 'template', + Component: Template + }; + case !ready: return { + key: 'install', + Component: Install + }; + default: return { + key: 'final', + Component: Finalize + } + } +} + +const App: FC<{ context: Context }> = ({ context }) => { + const [state, setState] = React.useState(context); + const step = React.useRef(getStep(context)); + const onSubmit = (value: string|boolean) => { + const { key } = step.current; + const newState = { ...state, [key]: value }; + step.current = getStep(newState) + setState(newState) + } + + useEffect(() => { + let isSubscribed = true + if (state.projectName && typeof state.projectExists === 'undefined') { + const newState = { ...state, projectExists: !isEmpty(state.projectName) }; + step.current = getStep(newState) + if (isSubscribed) { + setState(newState); + } + } + + if (state.projectName && (state.projectExists === false || state.force) && state.template) { + if (state.force) emptyDir(state.projectName); + prepareTemplate(context.use, state.template, state.projectName).then(() => { + if (isSubscribed) { + setState(v => { + const newState = {...v, ready: true }; + step.current = getStep(newState); + return newState; + }); + } + }); + } + + return () => { + isSubscribed = false; + } + }, [state]); + const { Component } = step.current; + + return ( + <> + <Header context={state}/> + <Component context={state} onSubmit={onSubmit} /> + </> + ) +}; + +export default App; diff --git a/packages/create-astro/src/components/Confirm.tsx b/packages/create-astro/src/components/Confirm.tsx new file mode 100644 index 000000000..1fd1aa862 --- /dev/null +++ b/packages/create-astro/src/components/Confirm.tsx @@ -0,0 +1,49 @@ +import React, { FC } from 'react'; +import { Box, Text, useInput, useApp } from 'ink'; +import Spacer from './Spacer'; +import Select from './Select'; + +const Confirm: FC<{ message?: any; context: any; onSubmit: (value: boolean) => void }> = ({ message, context: { projectName }, onSubmit }) => { + const { exit } = useApp(); + const handleSubmit = (v: boolean) => { + if (!v) return exit(); + onSubmit(v); + }; + + return ( + <> + <Box display="flex"> + {!message ? ( + <> + <Text color="#FFBE2D">{'[uh-oh]'}</Text> + <Text> + {' '} + It appears <Text color="#17C083">./{projectName}</Text> is not empty. Overwrite? + </Text> + </> + ) : ( + message + )} + </Box> + <Box display="flex"> + <Spacer width={6} /> + <Select + items={[ + { + value: false, + label: 'no' + }, + { + value: true, + label: 'yes', + description: <Text color="#FF1639">overwrite</Text>, + }, + ]} + onSelect={handleSubmit} + /> + </Box> + </> + ); +}; + +export default Confirm; diff --git a/packages/create-astro/src/components/Emoji.tsx b/packages/create-astro/src/components/Emoji.tsx new file mode 100644 index 000000000..3af9ae508 --- /dev/null +++ b/packages/create-astro/src/components/Emoji.tsx @@ -0,0 +1,5 @@ +import React from 'react'; +import { Text } from 'ink'; +import { isWin } from '../utils'; + +export default ({ children }) => isWin() ? null : <Text>{children}</Text> diff --git a/packages/create-astro/src/components/Exit.tsx b/packages/create-astro/src/components/Exit.tsx new file mode 100644 index 000000000..cc3096705 --- /dev/null +++ b/packages/create-astro/src/components/Exit.tsx @@ -0,0 +1,9 @@ +import React, { FC } from 'react'; +import { Box, Text } from 'ink'; +import { isDone } from '../utils'; + +const Exit: FC<{ didError?: boolean }> = ({ didError }) => isDone ? null : <Box marginTop={1} display="flex"> + <Text color={didError ? "#FF1639" : "#FFBE2D"}>[abort]</Text> + <Text> astro cancelled</Text> +</Box> +export default Exit; diff --git a/packages/create-astro/src/components/Finalize.tsx b/packages/create-astro/src/components/Finalize.tsx new file mode 100644 index 000000000..8d2a2103a --- /dev/null +++ b/packages/create-astro/src/components/Finalize.tsx @@ -0,0 +1,27 @@ +import React, { FC, useEffect } from 'react'; +import { Box, Text } from 'ink'; +import { cancelProcessListeners } from '../utils'; + +const Finalize: FC<{ context: any }> = ({ context: { use, projectName } }) => { + useEffect(() => { + cancelProcessListeners(); + process.exit(0); + }, []); + + return <> + <Box display="flex"> + <Text color="#17C083">{'[ yes ]'}</Text> + <Text> Project initialized at <Text color="#3894FF">./{projectName}</Text></Text> + </Box> + <Box display="flex" marginY={1}> + <Text dimColor>{'[ tip ]'}</Text> + <Box display="flex" marginLeft={1} flexDirection="column"> + <Text>Get started by running</Text> + <Text color="#3894FF">cd ./{projectName}</Text> + <Text color="#3894FF">{use} start</Text> + </Box> + </Box> + </>; +}; + +export default Finalize; diff --git a/packages/create-astro/src/components/Header.tsx b/packages/create-astro/src/components/Header.tsx new file mode 100644 index 000000000..1d894a60e --- /dev/null +++ b/packages/create-astro/src/components/Header.tsx @@ -0,0 +1,20 @@ +import React from 'react'; +import { Box, Text } from 'ink'; + +const getMessage = ({ projectName, template }) => { + switch (true) { + case !projectName: return <Text dimColor>Gathering mission details</Text>; + case !template: return <Text dimColor>Optimizing navigational system</Text>; + default: return <Text color="black" backgroundColor="white"> {projectName} </Text> + } +} + +const Header: React.FC<{ context: any }> = ({ context }) => ( + <Box width={48} display="flex" marginY={1}> + <Text backgroundColor="#882DE7" color="white">{' astro '}</Text> + <Box marginLeft={1}> + {getMessage(context)} + </Box> + </Box> +) +export default Header; diff --git a/packages/create-astro/src/components/Help.tsx b/packages/create-astro/src/components/Help.tsx new file mode 100644 index 000000000..88fcf5633 --- /dev/null +++ b/packages/create-astro/src/components/Help.tsx @@ -0,0 +1,62 @@ +import React, { FC } from 'react'; +import { Box, Text } from 'ink'; +import { ARGS, ARG } from '../config'; + +const Type: FC<{ type: any, enum?: string[] }> = ({ type, enum: e }) => { + if (type === Boolean) { + return <> + <Text color="#3894FF">true</Text> + <Text dimColor>|</Text> + <Text color="#3894FF">false</Text> + </> + } + if (e?.length > 0) { + return <> + {e.map((item, i, { length: len}) => { + if (i !== len - 1) { + return <Box key={item}> + <Text color="#17C083">{item}</Text> + <Text dimColor>|</Text> + </Box> + } + + return <Text color="#17C083" key={item}>{item}</Text> + })} + </> + } + + return <Text color="#3894FF">string</Text>; +} + +const Command: FC<{ name: string, info: ARG }> = ({ name, info: { alias, description, type, enum: e } }) => { + return ( + <Box display="flex" alignItems="flex-start"> + <Box width={24} display="flex" flexGrow={0}> + <Text color="whiteBright">--{name}</Text>{alias && <Text dimColor> -{alias}</Text>} + </Box> + <Box width={24}> + <Type type={type} enum={e} /> + </Box> + <Box> + <Text>{description}</Text> + </Box> + </Box> + ); +} + +const Help: FC<{ context: any }> = ({ context: { templates }}) => { + return ( + <> + <Box width={48} display="flex" marginY={1}> + <Text backgroundColor="#882DE7" color="white">{' astro '}</Text> + <Box marginLeft={1}> + <Text color="black" backgroundColor="white"> help </Text> + </Box> + </Box> + <Box marginBottom={1} marginLeft={2} display="flex" flexDirection="column"> + {Object.entries(ARGS).map(([name, info]) => <Command key={name} name={name} info={name === 'template' ? { ...info, enum: templates.map(({ value }) => value) } : info} /> )} + </Box> + </> + ) +}; +export default Help; diff --git a/packages/create-astro/src/components/Install.tsx b/packages/create-astro/src/components/Install.tsx new file mode 100644 index 000000000..d9d2bc9b6 --- /dev/null +++ b/packages/create-astro/src/components/Install.tsx @@ -0,0 +1,19 @@ +import React, { FC } from 'react'; +import { Box, Text } from 'ink'; +import Spacer from './Spacer'; +import Spinner from './Spinner'; + +const Install: FC<{ context: any }> = ({ context: { use } }) => { + return <> + <Box display="flex"> + <Spinner/> + <Text> Initiating launch sequence...</Text> + </Box> + <Box> + <Spacer /> + <Text color="white" dimColor>(aka running <Text color="#17C083">{use === 'npm' ? 'npm install' : 'yarn'}</Text>)</Text> + </Box> + </>; +}; + +export default Install; diff --git a/packages/create-astro/src/components/ProjectName.tsx b/packages/create-astro/src/components/ProjectName.tsx new file mode 100644 index 000000000..87b976494 --- /dev/null +++ b/packages/create-astro/src/components/ProjectName.tsx @@ -0,0 +1,24 @@ +import React, { FC } from 'react'; +import { Box, Text } from 'ink'; +import Spacer from './Spacer'; +import TextInput from 'ink-text-input'; +// @ts-expect-error +const { default: Input } = TextInput; + +const ProjectName: FC<{ onSubmit: (value: string) => void }> = ({ onSubmit }) => { + const [value, setValue] = React.useState(''); + const handleSubmit = (v: string) => onSubmit(v); + + return <> + <Box display="flex"> + <Text color="#17C083">{'[query]'}</Text> + <Text> What is your project name?</Text> + </Box> + <Box display="flex"> + <Spacer /> + <Input value={value} onChange={setValue} onSubmit={handleSubmit} placeholder="my-project" /> + </Box> + </>; +}; + +export default ProjectName; diff --git a/packages/create-astro/src/components/Select.tsx b/packages/create-astro/src/components/Select.tsx new file mode 100644 index 000000000..acf8eb29f --- /dev/null +++ b/packages/create-astro/src/components/Select.tsx @@ -0,0 +1,32 @@ +import SelectInput from 'ink-select-input'; +import React, { FC } from 'react'; +import { Text, Box } from 'ink'; +// @ts-expect-error +const { default: Select } = SelectInput; + +interface Props { + isSelected?: boolean; + label: string; + description?: string; +} +const Indicator: FC<Props> = ({ isSelected }) => isSelected ? <Text color="#3894FF">[ </Text> : <Text> </Text> +const Item: FC<Props> = ({isSelected = false, label, description }) => ( + <Box display="flex"> + <Text color={isSelected ? '#3894FF' : 'white'} dimColor={!isSelected}>{label}</Text> + {isSelected && description && typeof description === 'string' && <Text> {description}</Text>} + {isSelected && description && typeof description !== 'string' && <Box marginLeft={1}>{description}</Box>} + </Box> +); + +interface SelectProps { + items: { value: string|number|boolean, label: string, description?: any }[] + onSelect(value: string|number|boolean): void; +} +const CustomSelect: FC<SelectProps> = ({ items, onSelect }) => { + const handleSelect = ({ value }) => onSelect(value); + return ( + <Select indicatorComponent={Indicator} itemComponent={Item} items={items} onSelect={handleSelect} /> + ) +} + +export default CustomSelect; diff --git a/packages/create-astro/src/components/Spacer.tsx b/packages/create-astro/src/components/Spacer.tsx new file mode 100644 index 000000000..1e4e14561 --- /dev/null +++ b/packages/create-astro/src/components/Spacer.tsx @@ -0,0 +1,5 @@ +import React, { FC } from 'react'; +import { Box } from 'ink'; + +const Spacer: FC<{ width?: number }> = ({ width = 8 }) => <Box width={width} /> +export default Spacer; diff --git a/packages/create-astro/src/components/Spinner.tsx b/packages/create-astro/src/components/Spinner.tsx new file mode 100644 index 000000000..2d3335e1c --- /dev/null +++ b/packages/create-astro/src/components/Spinner.tsx @@ -0,0 +1,200 @@ +import React, { FC, useEffect, useState } from 'react'; +import { Box, Text } from 'ink'; + +const Spinner: FC<{ type?: keyof typeof spinners }> = ({ type = 'countdown' }) => { + const { interval, frames } = spinners[type]; + const [i, setI] = useState(0); + useEffect(() => { + const _ = setInterval(() => { + setI(v => (v < frames.length - 1) ? v + 1 : 0) + }, interval) + + return () => clearInterval(_); + }, []) + + return frames[i] +} + +const spinners = { + countdown: { + interval: 80, + frames: [ + <Box display="flex"> + <Text backgroundColor="#882DE7">{' '}</Text> + </Box>, + <Box display="flex"> + <Text backgroundColor="#882DE7">{' '}</Text> + </Box>, + <Box display="flex"> + <Text backgroundColor="#882DE7">{' '}</Text> + </Box>, + <Box display="flex"> + <Text backgroundColor="#882DE7">{' '}</Text> + </Box>, + <Box display="flex"> + <Text backgroundColor="#882DE7">{' '}</Text> + </Box>, + <Box display="flex"> + <Text backgroundColor="#6858F1">{' '}</Text> + <Text backgroundColor="#882DE7">{' '}</Text> + </Box>, + <Box display="flex"> + <Text backgroundColor="#5076F9">{' '}</Text> + <Text backgroundColor="#6858F1">{' '}</Text> + <Text backgroundColor="#882DE7">{' '}</Text> + </Box>, + <Box display="flex"> + <Text backgroundColor="#3894FF">{' '}</Text> + <Text backgroundColor="#5076F9">{' '}</Text> + <Text backgroundColor="#6858F1">{' '}</Text> + <Text backgroundColor="#882DE7">{' '}</Text> + </Box>, + <Box display="flex"> + <Text backgroundColor="#2CA5D2">{' '}</Text> + <Text backgroundColor="#3894FF">{' '}</Text> + <Text backgroundColor="#5076F9">{' '}</Text> + <Text backgroundColor="#6858F1">{' '}</Text> + <Text backgroundColor="#882DE7">{' '}</Text> + </Box>, + <Box display="flex"> + <Text backgroundColor="#23B1AF">{' '}</Text> + <Text backgroundColor="#2CA5D2">{' '}</Text> + <Text backgroundColor="#3894FF">{' '}</Text> + <Text backgroundColor="#5076F9">{' '}</Text> + <Text backgroundColor="#6858F1">{' '}</Text> + <Text backgroundColor="#882DE7">{' '}</Text> + </Box>, + <Box display="flex"> + <Text backgroundColor="#17C083">{' '}</Text> + <Text backgroundColor="#23B1AF">{' '}</Text> + <Text backgroundColor="#2CA5D2">{' '}</Text> + <Text backgroundColor="#3894FF">{' '}</Text> + <Text backgroundColor="#5076F9">{' '}</Text> + <Text backgroundColor="#6858F1">{' '}</Text> + <Text backgroundColor="#882DE7">{' '}</Text> + </Box>, + <Box display="flex"> + <Text backgroundColor="#17C083">{' '}</Text> + <Text backgroundColor="#23B1AF">{' '}</Text> + <Text backgroundColor="#2CA5D2">{' '}</Text> + <Text backgroundColor="#3894FF">{' '}</Text> + <Text backgroundColor="#5076F9">{' '}</Text> + <Text backgroundColor="#6858F1">{' '}</Text> + </Box>, + <Box display="flex"> + <Text backgroundColor="#17C083">{' '}</Text> + <Text backgroundColor="#23B1AF">{' '}</Text> + <Text backgroundColor="#2CA5D2">{' '}</Text> + <Text backgroundColor="#3894FF">{' '}</Text> + <Text backgroundColor="#5076F9">{' '}</Text> + </Box>, + <Box display="flex"> + <Text backgroundColor="#17C083">{' '}</Text> + <Text backgroundColor="#23B1AF">{' '}</Text> + <Text backgroundColor="#2CA5D2">{' '}</Text> + <Text backgroundColor="#3894FF">{' '}</Text> + </Box>, + <Box display="flex"> + <Text backgroundColor="#17C083">{' '}</Text> + <Text backgroundColor="#23B1AF">{' '}</Text> + <Text backgroundColor="#2CA5D2">{' '}</Text> + </Box>, + <Box display="flex"> + <Text backgroundColor="#17C083">{' '}</Text> + <Text backgroundColor="#23B1AF">{' '}</Text> + </Box>, + <Box display="flex"> + <Text backgroundColor="#17C083">{' '}</Text> + </Box>, + <Box display="flex"> + <Text backgroundColor="#17C083">{' '}</Text> + </Box>, + <Box display="flex"> + <Text backgroundColor="#17C083">{' '}</Text> + </Box>, + <Box display="flex"> + <Text backgroundColor="#17C083">{' '}</Text> + </Box>, + <Box display="flex"> + <Text backgroundColor="#17C083">{' '}</Text> + </Box>, + <Box display="flex"> + <Text backgroundColor="#17C083">{' '}</Text> + </Box>, + <Box display="flex"> + <Text backgroundColor="#23B1AF">{' '}</Text> + <Text backgroundColor="#17C083">{' '}</Text> + </Box>, + <Box display="flex"> + <Text backgroundColor="#2CA5D2">{' '}</Text> + <Text backgroundColor="#23B1AF">{' '}</Text> + <Text backgroundColor="#17C083">{' '}</Text> + </Box>, + <Box display="flex"> + <Text backgroundColor="#3894FF">{' '}</Text> + <Text backgroundColor="#2CA5D2">{' '}</Text> + <Text backgroundColor="#23B1AF">{' '}</Text> + <Text backgroundColor="#17C083">{' '}</Text> + </Box>, + <Box display="flex"> + <Text backgroundColor="#5076F9">{' '}</Text> + <Text backgroundColor="#3894FF">{' '}</Text> + <Text backgroundColor="#2CA5D2">{' '}</Text> + <Text backgroundColor="#23B1AF">{' '}</Text> + <Text backgroundColor="#17C083">{' '}</Text> + </Box>, + <Box display="flex"> + <Text backgroundColor="#6858F1">{' '}</Text> + <Text backgroundColor="#5076F9">{' '}</Text> + <Text backgroundColor="#3894FF">{' '}</Text> + <Text backgroundColor="#2CA5D2">{' '}</Text> + <Text backgroundColor="#23B1AF">{' '}</Text> + <Text backgroundColor="#17C083">{' '}</Text> + </Box>, + <Box display="flex"> + <Text backgroundColor="#882DE7">{' '}</Text> + <Text backgroundColor="#6858F1">{' '}</Text> + <Text backgroundColor="#5076F9">{' '}</Text> + <Text backgroundColor="#3894FF">{' '}</Text> + <Text backgroundColor="#2CA5D2">{' '}</Text> + <Text backgroundColor="#23B1AF">{' '}</Text> + <Text backgroundColor="#17C083">{' '}</Text> + </Box>, + <Box display="flex"> + <Text backgroundColor="#882DE7">{' '}</Text> + <Text backgroundColor="#6858F1">{' '}</Text> + <Text backgroundColor="#5076F9">{' '}</Text> + <Text backgroundColor="#3894FF">{' '}</Text> + <Text backgroundColor="#2CA5D2">{' '}</Text> + <Text backgroundColor="#23B1AF">{' '}</Text> + </Box>, + <Box display="flex"> + <Text backgroundColor="#882DE7">{' '}</Text> + <Text backgroundColor="#6858F1">{' '}</Text> + <Text backgroundColor="#5076F9">{' '}</Text> + <Text backgroundColor="#3894FF">{' '}</Text> + <Text backgroundColor="#2CA5D2">{' '}</Text> + </Box>, + <Box display="flex"> + <Text backgroundColor="#882DE7">{' '}</Text> + <Text backgroundColor="#6858F1">{' '}</Text> + <Text backgroundColor="#5076F9">{' '}</Text> + <Text backgroundColor="#3894FF">{' '}</Text> + </Box>, + <Box display="flex"> + <Text backgroundColor="#882DE7">{' '}</Text> + <Text backgroundColor="#6858F1">{' '}</Text> + <Text backgroundColor="#5076F9">{' '}</Text> + </Box>, + <Box display="flex"> + <Text backgroundColor="#882DE7">{' '}</Text> + <Text backgroundColor="#6858F1">{' '}</Text> + </Box>, + <Box display="flex"> + <Text backgroundColor="#882DE7">{' '}</Text> + </Box>, + ] + } +} + +export default Spinner; diff --git a/packages/create-astro/src/components/Template.tsx b/packages/create-astro/src/components/Template.tsx new file mode 100644 index 000000000..7fbab035d --- /dev/null +++ b/packages/create-astro/src/components/Template.tsx @@ -0,0 +1,23 @@ +import React, { FC } from 'react'; +import { Box, Text } from 'ink'; +import Spacer from './Spacer'; +import Select from './Select'; + +const Template: FC<{ context: any, onSubmit: (value: string) => void }> = ({ context: { templates }, onSubmit }) => { + const items = templates.map(({ title: label, ...rest }) => ({ ...rest, label })); + + return ( + <> + <Box display="flex"> + <Text color="#17C083">{'[query]'}</Text> + <Text> Which template should be used?</Text> + </Box> + <Box display="flex"> + <Spacer width={6} /> + <Select items={items} onSelect={onSubmit} /> + </Box> + </> + ); +}; + +export default Template; diff --git a/packages/create-astro/src/components/Version.tsx b/packages/create-astro/src/components/Version.tsx new file mode 100644 index 000000000..340952dc0 --- /dev/null +++ b/packages/create-astro/src/components/Version.tsx @@ -0,0 +1,6 @@ +import React, { FC } from 'react'; +import { Text } from 'ink'; +import pkg from '../../package.json'; + +const Version: FC = () => <Text color="#17C083">v{pkg.version}</Text>; +export default Version; |