mirror of
https://github.com/wasp-lang/wasp.git
synced 2024-11-24 03:35:17 +03:00
Implement Entity generator (code needs cleanup).
This commit is contained in:
parent
8396974686
commit
8fafa01504
@ -1,11 +1,30 @@
|
||||
{{={= =}=}}
|
||||
import React, { Component } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
|
||||
{=# entities =}
|
||||
import * as {= entityLowerName =}State from '{= entityStatePath =}'
|
||||
import * as {= entityLowerName =}Actions from '{= entityActionsPath =}'
|
||||
import {= entity.name =} from '{= entityClassPath =}'
|
||||
{=/ entities =}
|
||||
|
||||
|
||||
export default class {= page.name =} extends Component {
|
||||
export class {= page.name =} extends Component {
|
||||
// TODO: Add propTypes.
|
||||
|
||||
render() {
|
||||
return (
|
||||
{=& page.content =}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(state => ({
|
||||
{=# entities =}
|
||||
{= entityLowerName =}List: {= entityLowerName =}State.selectors.all(state)
|
||||
{=/ entities =}
|
||||
}), {
|
||||
{=# entities =}
|
||||
add{= entityUpperName =}: {= entityLowerName =}Actions.add
|
||||
{=/ entities =}
|
||||
})({= page.name =})
|
||||
|
22
stic/data/Generator/templates/react-app/src/entities/_entity/_Entity.js
vendored
Normal file
22
stic/data/Generator/templates/react-app/src/entities/_entity/_Entity.js
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
{{={= =}=}}
|
||||
export default class {= entity.name =} {
|
||||
_data = {}
|
||||
|
||||
constructor (data = {}) {
|
||||
this._data = {
|
||||
{=# entity.fields =}
|
||||
{= name =}: data.{= name =},
|
||||
{=/ entity.fields =}
|
||||
}
|
||||
}
|
||||
|
||||
{=# entity.fields =}
|
||||
get {= name =} () {
|
||||
return this._data.{= name =}
|
||||
}
|
||||
{=/ entity.fields =}
|
||||
|
||||
toData () {
|
||||
return this._data
|
||||
}
|
||||
}
|
2
stic/data/Generator/templates/react-app/src/entities/_entity/actionTypes.js
vendored
Normal file
2
stic/data/Generator/templates/react-app/src/entities/_entity/actionTypes.js
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
{{={= =}=}}
|
||||
export const ADD = 'entities/{= entityLowerName =}/ADD'
|
11
stic/data/Generator/templates/react-app/src/entities/_entity/actions.js
vendored
Normal file
11
stic/data/Generator/templates/react-app/src/entities/_entity/actions.js
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
{{={= =}=}}
|
||||
import * as types from './actionTypes'
|
||||
|
||||
|
||||
/**
|
||||
* @param {{= entity.name =}} {= entityLowerName =}
|
||||
*/
|
||||
export const add = ({= entityLowerName =}) => ({
|
||||
type: types.ADD,
|
||||
data: {= entityLowerName =}.toData()
|
||||
})
|
41
stic/data/Generator/templates/react-app/src/entities/_entity/state.js
vendored
Normal file
41
stic/data/Generator/templates/react-app/src/entities/_entity/state.js
vendored
Normal file
@ -0,0 +1,41 @@
|
||||
{{={= =}=}}
|
||||
import { createSelector } from 'reselect'
|
||||
|
||||
import {= entity.name =} from './{= entity.name =}'
|
||||
import * as types from './actionTypes'
|
||||
|
||||
|
||||
// We assume that root reducer of the app will put this reducer under
|
||||
// key ROOT_REDUCER_KEY.
|
||||
const ROOT_REDUCER_KEY = 'entities/{= entity.name =}'
|
||||
|
||||
const initialState = {
|
||||
all: []
|
||||
}
|
||||
|
||||
const reducer = (state = initialState, action) => {
|
||||
switch (action.type) {
|
||||
case types.ADD:
|
||||
return {
|
||||
...state,
|
||||
all: [ ...state.all, action.data ]
|
||||
}
|
||||
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let selectors = {}
|
||||
selectors.root = (state) => state[ROOT_REDUCER_KEY]
|
||||
|
||||
/**
|
||||
* @returns {{= entity.name =}[]}
|
||||
*/
|
||||
selectors.all = createSelector(selectors.root, (state) => {
|
||||
return state.all.map(data => new {= entity.name =}(data))
|
||||
})
|
||||
|
||||
|
||||
export { reducer, initialState, selectors, ROOT_REDUCER_KEY }
|
@ -1,12 +1,15 @@
|
||||
{{={= =}=}}
|
||||
import { combineReducers } from 'redux'
|
||||
|
||||
// import * as dataState from './modules/data/state'
|
||||
{=# entities =}
|
||||
import * as {= entityLowerName =}State from '{= entityStatePath =}'
|
||||
{=/ entities =}
|
||||
|
||||
|
||||
const states = [
|
||||
// dataState,
|
||||
// Add reducer here to add it to the app.
|
||||
{=# entities =}
|
||||
{= entityLowerName =}State,
|
||||
{=/ entities =}
|
||||
]
|
||||
|
||||
const keyToReducer = states.reduce((acc, state) => {
|
||||
|
@ -42,6 +42,7 @@ library:
|
||||
- text
|
||||
- aeson
|
||||
- directory
|
||||
- split
|
||||
|
||||
executables:
|
||||
stic-exe:
|
||||
|
@ -7,10 +7,12 @@ module Generator.Generators
|
||||
) where
|
||||
|
||||
import Data.Aeson ((.=), object, ToJSON(..))
|
||||
import Data.Char (toLower, toUpper)
|
||||
import System.FilePath (FilePath, (</>), (<.>))
|
||||
|
||||
import Generator.FileDraft
|
||||
import Wasp
|
||||
import qualified Util
|
||||
|
||||
|
||||
generateWebApp :: Wasp -> [FileDraft]
|
||||
@ -39,6 +41,8 @@ generatePublicDir wasp
|
||||
, "manifest.json"
|
||||
]
|
||||
|
||||
-- * Src dir
|
||||
|
||||
generateSrcDir :: Wasp -> [FileDraft]
|
||||
generateSrcDir wasp
|
||||
= (createCopyFileDraft ("src" </> "logo.png") ("src" </> "logo.png"))
|
||||
@ -48,13 +52,31 @@ generateSrcDir wasp
|
||||
, "App.css"
|
||||
, "index.js"
|
||||
, "index.css"
|
||||
, "reducers.js"
|
||||
, "router.js"
|
||||
, "serviceWorker.js"
|
||||
, "store/index.js"
|
||||
, "store/middleware/logger.js"
|
||||
]
|
||||
++ generatePages wasp
|
||||
++ generateEntities wasp
|
||||
++ [generateReducersJs wasp]
|
||||
|
||||
generateReducersJs :: Wasp -> FileDraft
|
||||
generateReducersJs wasp = createTemplateFileDraft dstPath srcPath templateData
|
||||
where
|
||||
srcPath = "src" </> "reducers.js"
|
||||
dstPath = srcPath
|
||||
templateData = object
|
||||
[ "wasp" .= wasp
|
||||
, "entities" .= map toEntityData (getEntities wasp)
|
||||
]
|
||||
toEntityData entity = object
|
||||
[ "entity" .= entity
|
||||
, "entityLowerName" .= entityLowerName entity
|
||||
, "entityStatePath" .= ("./" ++ (entityStatePath entity))
|
||||
]
|
||||
|
||||
-- * Pages
|
||||
|
||||
generatePages :: Wasp -> [FileDraft]
|
||||
generatePages wasp = generatePage wasp <$> getPages wasp
|
||||
@ -64,8 +86,106 @@ generatePage wasp page = createTemplateFileDraft dstPath srcPath templateData
|
||||
where
|
||||
srcPath = "src" </> "_Page.js"
|
||||
dstPath = "src" </> (pageName page) <.> "js"
|
||||
templateData = object ["wasp" .= wasp, "page" .= page]
|
||||
templateData = object
|
||||
[ "wasp" .= wasp
|
||||
, "page" .= page
|
||||
, "entities" .= map toEntityData (getEntities wasp)
|
||||
]
|
||||
toEntityData entity = object
|
||||
[ "entity" .= entity
|
||||
, "entityLowerName" .= entityLowerName entity
|
||||
, "entityUpperName" .= entityUpperName entity
|
||||
, "entityStatePath" .= ("./" ++ (entityStatePath entity))
|
||||
, "entityActionsPath" .= ("./" ++ (entityActionsPath entity))
|
||||
, "entityClassPath" .= ("./" ++ (entityClassPath entity))
|
||||
]
|
||||
|
||||
-- * Entities
|
||||
|
||||
generateEntities :: Wasp -> [FileDraft]
|
||||
generateEntities wasp = concat $ generateEntity wasp <$> getEntities wasp
|
||||
|
||||
-- TODO(martin): Create EntityData object that will contain more stuff,
|
||||
-- like small camel case name and similar, that will be representation used in the
|
||||
-- template files, instead of Entity directly.
|
||||
-- Then build that from Entity and pass that to templates.
|
||||
-- I could even have one data type per template.
|
||||
-- Or, have function that builds json?
|
||||
|
||||
-- TODO(martin): Also, I should extract entity stuff into separate module,
|
||||
-- there is too much logic here already.
|
||||
|
||||
-- TODO(martin): This file has lot of duplication + is missing tests, work on that.
|
||||
|
||||
generateEntity :: Wasp -> Entity -> [FileDraft]
|
||||
generateEntity wasp entity =
|
||||
[ generateEntityClass wasp entity
|
||||
, generateEntityState wasp entity
|
||||
, generateEntityActionTypes wasp entity
|
||||
, generateEntityActions wasp entity
|
||||
]
|
||||
|
||||
generateEntityClass :: Wasp -> Entity -> FileDraft
|
||||
generateEntityClass wasp entity = createTemplateFileDraft dstPath srcPath templateData
|
||||
where
|
||||
srcPath = "src" </> "entities" </> "_entity" </> "_Entity.js"
|
||||
dstPath = "src" </> entityClassPath entity
|
||||
templateData = object
|
||||
[ "wasp" .= wasp
|
||||
, "entity" .= entity
|
||||
]
|
||||
|
||||
generateEntityState :: Wasp -> Entity -> FileDraft
|
||||
generateEntityState wasp entity = createTemplateFileDraft dstPath srcPath templateData
|
||||
where
|
||||
srcPath = "src" </> "entities" </> "_entity" </> "state.js"
|
||||
dstPath = "src" </> entityStatePath entity
|
||||
templateData = object
|
||||
[ "wasp" .= wasp
|
||||
, "entity" .= entity
|
||||
]
|
||||
|
||||
generateEntityActionTypes :: Wasp -> Entity -> FileDraft
|
||||
generateEntityActionTypes wasp entity = createTemplateFileDraft dstPath srcPath templateData
|
||||
where
|
||||
srcPath = "src" </> "entities" </> "_entity" </> "actionTypes.js"
|
||||
dstPath = "src" </> "entities" </> (entityDirName entity) </> "actionTypes.js"
|
||||
templateData = object
|
||||
[ "wasp" .= wasp
|
||||
, "entity" .= entity
|
||||
, "entityLowerName" .= entityLowerName entity
|
||||
]
|
||||
|
||||
generateEntityActions :: Wasp -> Entity -> FileDraft
|
||||
generateEntityActions wasp entity = createTemplateFileDraft dstPath srcPath templateData
|
||||
where
|
||||
srcPath = "src" </> "entities" </> "_entity" </> "actions.js"
|
||||
dstPath = "src" </> entityActionsPath entity
|
||||
templateData = object
|
||||
[ "wasp" .= wasp
|
||||
, "entity" .= entity
|
||||
, "entityLowerName" .= entityLowerName entity
|
||||
]
|
||||
|
||||
entityDirName :: Entity -> String
|
||||
entityDirName entity = Util.camelToKebabCase (entityName entity)
|
||||
|
||||
entityLowerName :: Entity -> String
|
||||
entityLowerName Entity{entityName=name} = (toLower $ head name) : (tail name)
|
||||
|
||||
entityUpperName :: Entity -> String
|
||||
entityUpperName Entity{entityName=name} = (toUpper $ head name) : (tail name)
|
||||
|
||||
entityStatePath :: Entity -> String
|
||||
entityStatePath entity = "entities" </> (entityDirName entity) </> "state.js"
|
||||
|
||||
entityActionsPath :: Entity -> String
|
||||
entityActionsPath entity = "entities" </> (entityDirName entity) </> "actions.js"
|
||||
|
||||
entityClassPath :: Entity -> String
|
||||
entityClassPath entity = "entities" </> (entityDirName entity) </> (entityName entity) <.> "js"
|
||||
|
||||
-- * Helpers
|
||||
|
||||
-- | Creates template file draft that uses given path as both src and dst path
|
||||
-- and wasp as template data.
|
||||
|
16
stic/src/Util.hs
Normal file
16
stic/src/Util.hs
Normal file
@ -0,0 +1,16 @@
|
||||
module Util
|
||||
( camelToKebabCase
|
||||
) where
|
||||
|
||||
import Data.Char (isUpper, toLower)
|
||||
|
||||
|
||||
camelToKebabCase :: String -> String
|
||||
camelToKebabCase "" = ""
|
||||
camelToKebabCase camel@(camelHead:camelTail) = kebabHead:kebabTail
|
||||
where
|
||||
kebabHead = toLower camelHead
|
||||
kebabTail = concat $ map
|
||||
(\(a, b) -> (if (isCamelHump (a, b)) then ['-'] else []) ++ [toLower b])
|
||||
(zip camel camelTail)
|
||||
isCamelHump (a, b) = (not . isUpper) a && isUpper b
|
@ -16,6 +16,8 @@ module Wasp
|
||||
, Entity (..)
|
||||
, EntityField (..)
|
||||
, EntityFieldType (..)
|
||||
, getEntities
|
||||
, addEntity
|
||||
) where
|
||||
|
||||
import Data.Aeson ((.=), object, ToJSON(..))
|
||||
@ -89,6 +91,12 @@ data EntityField = EntityField
|
||||
|
||||
data EntityFieldType = EftString | EftBoolean deriving (Show, Eq)
|
||||
|
||||
getEntities :: Wasp -> [Entity]
|
||||
getEntities (Wasp elems) = [entity | (WaspElementEntity entity) <- elems]
|
||||
|
||||
addEntity :: Wasp -> Entity -> Wasp
|
||||
addEntity (Wasp elems) entity = Wasp $ (WaspElementEntity entity):elems
|
||||
|
||||
-- * ToJSON instances.
|
||||
|
||||
-- NOTE(martin): Here I define general transformation of App into JSON that I can then easily use
|
||||
@ -107,9 +115,25 @@ instance ToJSON Page where
|
||||
, "route" .= pageRoute page
|
||||
, "content" .= pageContent page
|
||||
]
|
||||
|
||||
instance ToJSON Entity where
|
||||
toJSON entity = object
|
||||
[ "name" .= entityName entity
|
||||
, "fields" .= entityFields entity
|
||||
]
|
||||
|
||||
instance ToJSON EntityField where
|
||||
toJSON entityField = object
|
||||
[ "name" .= entityFieldName entityField
|
||||
, "type" .= entityFieldType entityField
|
||||
]
|
||||
|
||||
instance ToJSON EntityFieldType where
|
||||
toJSON EftString = "string"
|
||||
toJSON EftBoolean = "boolean"
|
||||
|
||||
instance ToJSON Wasp where
|
||||
toJSON wasp = object
|
||||
[ "app" .= getApp wasp
|
||||
, "pages" .= getPages wasp
|
||||
]
|
||||
|
||||
|
17
stic/test/UtilTest.hs
Normal file
17
stic/test/UtilTest.hs
Normal file
@ -0,0 +1,17 @@
|
||||
module UtilTest where
|
||||
|
||||
import Test.Tasty.Hspec
|
||||
|
||||
import Util
|
||||
|
||||
|
||||
spec_camelToKebabCase :: Spec
|
||||
spec_camelToKebabCase = do
|
||||
"foobar" ~> "foobar"
|
||||
"s3" ~> "s3"
|
||||
"fooBarBar" ~> "foo-bar-bar"
|
||||
"s3Folder" ~> "s3-folder"
|
||||
"S3Folder" ~> "s3-folder"
|
||||
where
|
||||
camel ~> kebab = it (camel ++ " -> " ++ kebab) $ do
|
||||
camelToKebabCase camel `shouldBe` kebab
|
Loading…
Reference in New Issue
Block a user