diff --git a/CHANGELOG.md b/CHANGELOG.md index a1d9fbd7..cada4c61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Solid React Application Generator +## 0.6.1 (August 28, 2019) + +#### Generator + +##### Added +* New prompt when creating a new application. The new prompt asks if the developer would like to install a full sample application or just a simple one-page application. This provides a way for people familiar with Solid to create new blank applications and get started quicker. + ## 0.6.0 (August 14, 2019) #### Solid React Application Base diff --git a/README.md b/README.md index e4e341c7..c2de3372 100644 --- a/README.md +++ b/README.md @@ -42,10 +42,11 @@ Once the generator is installed, you can create a new application with just a fe 1. In a console window, navigate to the desired parent folder of the new application. 2. Use the command: ``` yo @inrupt/solid-react ``` -3. You will be prompted to set: +3. You will be prompted: 1. An application name. This will also be the name of the new folder in which the new application lives. - 2. A version number. - 3. Whether the application is private or public. + 2. Whether or not to install a sample application, or a simple one-page application skeleton + 3. A version number. + 4. Whether the application is private or public. 4. Navigate into the new folder. 5. If you would like to start the application, simply run ``` npm run start ``` in the new folder, otherwise you can begin editing and writing your application! diff --git a/__tests__/app.js b/__tests__/app.js index 8c3f0b54..8497cabc 100644 --- a/__tests__/app.js +++ b/__tests__/app.js @@ -4,13 +4,13 @@ const assert = require('yeoman-assert'); const helpers = require('yeoman-test'); describe('generator-solid-react:app', () => { - beforeAll(() => { - return helpers - .run(path.join(__dirname, '../generators/app')) - .withPrompts({ someAnswer: true }); - }); + beforeAll(() => { + return helpers + .run(path.join(__dirname, '../generators/app')) + .withPrompts({ someAnswer: true }); + }); - it('creates files', () => { - assert.file(['dummyfile.txt']); - }); + it('creates files', () => { + assert.file(['dummyfile.txt']); + }); }); diff --git a/generators/app/index.js b/generators/app/index.js index b82c4dbb..24597df1 100644 --- a/generators/app/index.js +++ b/generators/app/index.js @@ -1,113 +1,204 @@ -const Generator = require("yeoman-generator"); -const chalk = require("chalk"); -const yosay = require("yosay"); -const figlet = require("figlet"); -const path = require("path"); -const mkdirp = require("mkdirp"); -const glob = require("glob"); -const voca = require("voca"); +const Generator = require('yeoman-generator'); +const chalk = require('chalk'); +const figlet = require('figlet'); +const path = require('path'); +const mkdirp = require('mkdirp'); +const voca = require('voca'); + +const fileList = [ + // ROOT FILES + { src: 'README.md' }, + { src: 'package.json' }, + // SRC ROOT FILES + { src: 'src/App.js' }, + { src: 'src/App.styled.js' }, + { src: 'src/App.test.js' }, + { src: 'src/i18n.js' }, + { src: 'src/index.css' }, + { src: 'src/index.js' }, + { src: 'src/serviceWorker.js' }, + // CONFIG FILES + { src: 'config/**', dest: 'config' }, + { src: 'scripts/**', dest: 'scripts' }, + // PUBLIC FILES + { src: 'public/**', dest: 'public' }, + // TEST FILES + { src: 'test/**', dest: 'test' }, + // COMPONENTS + { src: 'src/components/**', dest: 'src/components' }, + // CONSTANTS + { src: 'src/constants/**', dest: 'src/constants' }, + // CONTEXTS + { src: 'src/contexts/**', dest: 'src/contexts' }, + // HIGHER ORDER COMPONENTS + { src: 'src/hocs/**', dest: 'src/hocs' }, + // HOOKS + { src: 'src/hooks/**', dest: 'src/hooks' }, + // LAYOUTS + { src: 'src/layouts/**', dest: 'src/layouts' }, + // SERVICES + { src: 'src/services/**', dest: 'src/services' }, + // UTILS + { src: 'src/utils/**', dest: 'src/utils' }, + // DEFAULT CONTAINERS + { src: 'src/containers/Login/**', dest: 'src/containers/Login' }, + { + src: 'src/containers/PageNotFound/**', + dest: 'src/containers/PageNotFound', + }, + { src: 'src/containers/Register/**', dest: 'src/containers/Register' }, + { src: 'src/containers/Welcome/**', dest: 'src/containers/Welcome' }, +]; module.exports = class extends Generator { - constructor(args, opts) { - super(args, opts); - } + prompting() { + const done = this.async(); + this.log(chalk.cyan.bold('Welcome to the \n Solid React Generator')); + + return this.prompt([ + { + type: 'input', + name: 'appName', + message: 'Please enter your application name :', + store: true, + validate: appName => { + const pass = appName.match(/^[^\d\s!@£$%^&*()+=]+$/); + if (pass) { + return true; + } + return `${chalk.red( + 'Provide a valid "App name", digits and whitespaces not allowed' + )}`; + }, + default: voca.kebabCase(this.appname), // Default to current folder name + }, + { + type: 'confirm', + name: 'appInstalled', + message: + 'Solid React Generator can install an example application illustrating how to interact with Solid, or a basic application framework. Do you want to install the example application?', + }, + { + type: 'input', + name: 'appVersion', + message: 'Initial version:', + store: true, + validate: appVersion => { + const pass = appVersion.match( + /^\d{1,2}\.\d{1,2}\.\d{1,2}$/ + ); + if (pass) { + return true; + } + return `${chalk.red( + 'Provide a valid version (ex: 0.1.0)' + )}`; + }, + default: '0.1.0', + }, + { + type: 'list', + name: 'isPrivate', + message: 'Is this application private?', + choices: ['false', 'true'], + default: 'false', + }, + ]).then(answers => { + this.props = answers; + done(); + }); + } - prompting() { - const done = this.async(); - this.log(chalk.cyan.bold("Welcome to the \n Solid React Generator")); + writing() { + const { appName, appVersion, isPrivate, appInstalled } = this.props; + const pkgJson = { + name: appName, + version: appVersion, + private: isPrivate === 'true', + }; - return this.prompt([ - { - type: "input", - name: "appName", - message: "Please enter your application name :", - store: true, - validate: appName => { - const pass = appName.match(/^[^\d\s!@£$%^&*()+=]+$/); - if (pass) { - return true; - } - return `${chalk.red( - 'Provide a valid "App name", digits and whitespaces not allowed' - )}`; - }, - default: voca.kebabCase(this.appname) // Default to current folder name - }, - { - type: "input", - name: "appVersion", - message: "version:", - store: true, - validate: appVersion => { - const pass = appVersion.match(/^\d{1,2}\.\d{1,2}\.\d{1,2}$/); - if (pass) { - return true; - } - return `${chalk.red("Provide a valid version (ex: 0.1.0)")}`; - }, - default: "0.1.0" - }, - { - type: "list", - name: "isPrivate", - message: "Is it private?", - choices: ["false", "true"], - default: "false" - } - ]).then(answers => { - this.props = answers; - done(); - }); - } + // FINALIZE FILES TO INSTALL + this.log('Processing Configuration...'); + if (appInstalled) { + fileList.push( + { src: 'src/containers/index.js' }, + { src: 'src/routes.js' }, + { + src: 'src/constants/navigation.js', + dest: 'src/constants/navigation.js', + }, + { + src: 'src/containers/Profile/**', + dest: 'src/containers/Profile', + }, + { + src: 'src/containers/TicTacToe/**', + dest: 'src/containers/TicTacToe', + }, + { src: '.env' } + ); + } else { + fileList.push( + { src: 'src/_routes.lite.js', dest: 'src/routes.js' }, + { + src: 'src/containers/_index.lite.js', + dest: 'src/containers/index.js', + }, + { + src: 'src/constants/_navigation.lite.js', + dest: 'src/constants/navigation.js', + }, + { src: '.env.lite', dest: '.env' }, + { + src: 'src/components/AuthNavBar/_auth-nav-bar.lite.js', + dest: 'src/components/AuthNavBar/auth-nav-bar.component.js', + } + ); + } - writing() { - const { appName, appVersion, isPrivate } = this.props; - const pkgJson = { - name: appName, - version: appVersion, - private: isPrivate === "true" - }; + this.log(chalk.blue(this.templatePath('package.json'))); - this.log(chalk.blue(this.templatePath("package.json"))); - const fromTemplateFiles = glob.sync(this.templatePath("./*"), { - ignore: ["**/node_modules", "**/dist"], - dot: true - }); - - if (path.basename(this.destinationPath()) !== appName) { - this.log("Creating folder..."); - mkdirp(appName); - this.destinationRoot(this.destinationPath(appName)); - } + if (path.basename(this.destinationPath()) !== appName) { + this.log('Creating folder...'); + mkdirp(appName); + this.destinationRoot(this.destinationPath(appName)); + } - this.log("Copying app directory..."); + this.log('Copying app directory...'); - this.fs.copyTpl(fromTemplateFiles, this.destinationRoot(), { - title: voca.titleCase(appName) - }); - this.fs.extendJSON(this.destinationPath("package.json"), pkgJson); + // EXTEND PACKAGE.JSON WITH USER PROMPTS + this.fs.extendJSON(this.destinationPath('package.json'), pkgJson); - } + this.log(this.templatePath()); - install() { - this.log("Installing dependencies..."); - this.npmInstall(); - this.completed = true; - } + // WRITE NEW FILES BASED ON USER PROMPTS + fileList.forEach(newFile => { + return this.fs.copyTpl( + this.templatePath(newFile.src), + this.destinationPath(newFile.dest || newFile.src), + { title: voca.titleCase(appName) } + ); + }); + } + + install() { + this.log('Installing dependencies...'); + this.npmInstall(); + this.completed = true; + } - end() { - if (this.completed) { - this.log("Installation complete. Welcome to Solid"); - this.log( - chalk.bold.blue( - figlet.textSync("SOLID", { - font: "3D-ASCII", - horizontalLayout: "full", - verticalLayout: "full" - }) - ) - ); - return; + end() { + if (this.completed) { + this.log('Installation complete. Welcome to Solid'); + this.log( + chalk.bold.blue( + figlet.textSync('SOLID', { + font: '3D-ASCII', + horizontalLayout: 'full', + verticalLayout: 'full', + }) + ) + ); + } } - } }; diff --git a/generators/app/templates/.env.lite b/generators/app/templates/.env.lite new file mode 100644 index 00000000..d3d7ed08 --- /dev/null +++ b/generators/app/templates/.env.lite @@ -0,0 +1,3 @@ +REACT_APP_VERSION=$npm_package_version +REACT_APP_NAME=$npm_package_name +REACT_APP_COMPANY_NAME=inrupt Inc. diff --git a/generators/app/templates/src/_routes.lite.js b/generators/app/templates/src/_routes.lite.js new file mode 100644 index 00000000..2ad74f67 --- /dev/null +++ b/generators/app/templates/src/_routes.lite.js @@ -0,0 +1,31 @@ +import React, { Fragment } from 'react'; +import { PrivateLayout, PublicLayout, NotLoggedInLayout } from '@layouts'; +import { BrowserRouter as Router, Switch, Redirect } from 'react-router-dom'; + +import { Login, Register, PageNotFound, Welcome, RegistrationSuccess } from './containers'; + +const privateRoutes = [ + { + id: 'welcome', + path: '/welcome', + component: Welcome + } +]; + +const Routes = () => ( + + + + + + + + + + + + + +); + +export default Routes; diff --git a/generators/app/templates/src/components/AuthNavBar/_auth-nav-bar.lite.js b/generators/app/templates/src/components/AuthNavBar/_auth-nav-bar.lite.js new file mode 100644 index 00000000..7bd9935a --- /dev/null +++ b/generators/app/templates/src/components/AuthNavBar/_auth-nav-bar.lite.js @@ -0,0 +1,82 @@ +import React, { useState, useEffect, useCallback } from 'react'; +import { NavBar, Notification } from '@components'; +import { useTranslation } from 'react-i18next'; +import { NavBarContainer } from './children'; +import { LanguageDropdown } from '@util-components'; +import { ldflexHelper, errorToaster } from '@utils'; +import { NavigationItems } from '@constants'; + +type Props = { + webId: string +}; + +const AuthNavBar = React.memo((props: Props) => { + const [inboxes, setInbox] = useState([]); + const { t, i18n } = useTranslation(); + const navigation = NavigationItems.map(item => ({ ...item, label: t(item.label) })); + const { webId } = props; + /** + * Looks for all of the inbox containers in the pod and sets inboxes state + */ + const discoverInbox = useCallback(async () => { + try { + let inboxes = []; + /** + * Get user's global inbox path from pod. + */ + const globalInbox = await ldflexHelper.discoverInbox(webId); + + if (globalInbox) { + inboxes = [ + ...inboxes, + { path: globalInbox, inboxName: t('navBar.notifications.global'), shape: 'default' } + ]; + } + /** + * If user doesn't has inbox in his pod will show an error and link to + * know how fix it. + */ + if (inboxes.length === 0) + errorToaster(t('noInboxUser.message'), 'Error', { + label: t('noInboxUser.link.label'), + href: t('noInboxUser.link.href') + }); + setInbox(inboxes); + } catch (error) { + /** + * Show general errors + */ + errorToaster(error.message, t('navBar.notifications.fetchingError')); + } + }, [webId, inboxes]); + + useEffect(() => { + if (webId) { + discoverInbox(); + } + }, [webId]); + const { history } = props; + + return ( + , + id: 'language' + }, + { + component: () => , + id: 'notifications' + }, + { + component: props => , + id: 'profile' + } + ]} + /> + ); +}); + +export default AuthNavBar; diff --git a/generators/app/templates/src/components/AuthNavBar/auth-nav-bar.component.js b/generators/app/templates/src/components/AuthNavBar/auth-nav-bar.component.js index 34ce5c7b..133db5cf 100644 --- a/generators/app/templates/src/components/AuthNavBar/auth-nav-bar.component.js +++ b/generators/app/templates/src/components/AuthNavBar/auth-nav-bar.component.js @@ -4,6 +4,7 @@ import { useTranslation } from 'react-i18next'; import { NavBarContainer } from './children'; import { LanguageDropdown } from '@util-components'; import { ldflexHelper, errorToaster, storageHelper } from '@utils'; +import { NavigationItems } from '@constants'; type Props = { webId: string @@ -12,26 +13,8 @@ type Props = { const AuthNavBar = React.memo((props: Props) => { const [inboxes, setInbox] = useState([]); const { t, i18n } = useTranslation(); - const navigation = [ - { - id: 'welcome', - icon: '/img/icon/apps.svg', - label: t('navBar.welcome'), - to: '/welcome' - }, - { - id: 'profile', - icon: '/img/people.svg', - label: t('navBar.profile'), - to: '/profile' - }, - { - id: 'tictactoe', - icon: '/img/icon/tictactoe.svg', - label: t('navBar.tictactoe'), - to: '/tictactoe' - } - ]; + const navigation = NavigationItems.map(item => ({ ...item, label: t(item.label) })); + const { webId } = props; /** * Looks for all of the inbox containers in the pod and sets inboxes state diff --git a/generators/app/templates/src/components/AuthNavBar/children/NavbarProfile/nav-bar-profile.component.js b/generators/app/templates/src/components/AuthNavBar/children/NavbarProfile/nav-bar-profile.component.js index 56563326..5ed1acf0 100644 --- a/generators/app/templates/src/components/AuthNavBar/children/NavbarProfile/nav-bar-profile.component.js +++ b/generators/app/templates/src/components/AuthNavBar/children/NavbarProfile/nav-bar-profile.component.js @@ -6,6 +6,7 @@ import { Dropdown } from '@util-components'; import auth from 'solid-auth-client'; import data from '@solid/query-ldflex'; import { errorToaster } from '@utils'; +import { ProfileOptions } from '@constants/navigation'; export const ImageContainer = styled.div` width: 42px; @@ -111,18 +112,11 @@ class NavBarProfile extends Component { const { t, open, customClass } = this.props; const { imageLoaded, image } = this.state; - const profileOpts = [ - { - label: t('navBar.profile'), - onClick: this.profileRedirect, - icon: 'cog' - }, - { - label: t('navBar.logOut'), - onClick: this.logOut, - icon: 'lock' - } - ]; + const profileOpts = ProfileOptions.map(item => ({ + ...item, + label: t(item.label), + onClick: this[item.onClick] + })); return image ? (