Updated TODO example with new code.

This commit is contained in:
Martin Sosic 2020-02-06 12:23:02 +01:00
parent f77f19a6bb
commit 0b9c72eec0
25 changed files with 12391 additions and 14433 deletions

View File

@ -0,0 +1 @@
Generated on 2020-02-06 11:14:43.678730919 UTC by waspc version "0.1.0.0" .

File diff suppressed because it is too large Load Diff

View File

@ -3,15 +3,15 @@
"version": "0.0.0",
"private": true,
"dependencies": {
"@material-ui/core": "^4.0.0-rc.0",
"lodash": "^4.17.11",
"react": "^16.8.5",
"react-dom": "^16.8.5",
"react-redux": "^7.0.3",
"react-router-dom": "^5.0.0",
"react-scripts": "2.1.8",
"redux": "^4.0.1",
"redux-starter-kit": "^0.5.1",
"@material-ui/core": "^4.9.1",
"@reduxjs/toolkit": "^1.2.3",
"lodash": "^4.17.15",
"react": "^16.12.0",
"react-dom": "^16.12.0",
"react-redux": "^7.1.3",
"react-router-dom": "^5.1.2",
"react-scripts": "3.3.1",
"redux": "^4.0.5",
"uuid": "^3.4.0"
},
"scripts": {
@ -23,10 +23,16 @@
"eslintConfig": {
"extends": "react-app"
},
"browserslist": [
">0.2%",
"not dead",
"not ie <= 11",
"not op_mini all"
]
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}

View File

@ -0,0 +1 @@
.aboutPage { color: black; text-align: center; }

View File

@ -0,0 +1,37 @@
import React, { Component } from 'react'
import { connect } from 'react-redux'
import Todo from "./ext-src/Todo.js"
import * as taskState from './entities/task/state.js'
import * as taskActions from './entities/task/actions.js'
import Task from './entities/task/Task.js'
import NewTaskForm from './entities/task/components/NewTaskForm.js'
import TaskList from './entities/task/components/TaskList.js'
import './About.css'
export class About extends Component {
// TODO: Add propTypes.
render() {
return (
<div className="aboutPage">
<h1> About </h1>
<p>This page was built with <a href="https://wasp-lang.dev">Wasp</a>!</p>
<p> Check out source code &nbsp;
<a href="https://github.com/wasp-lang/wasp/tree/master/examples/todoApp">here</a>.
</p>
</div>
)
}
}
export default connect(state => ({
taskList: taskState.selectors.all(state)
}), {
addTask: taskActions.add,
updateTask: taskActions.update,
removeTask: taskActions.remove
})(About)

View File

@ -1,17 +0,0 @@
div {
color: green;
}
.mainContainer {
display: flex;
flex-direction: column;
align-items: center;
}
.taskListContainer {
width: 60%;
}
.selected {
border: 1px solid black;
}

View File

@ -1,17 +1,15 @@
import React, { Component } from 'react'
import { connect } from 'react-redux'
import Todo from "././ext-src/Todo"
import Todo from "./ext-src/Todo.js"
import * as taskState from '././entities/task/state.js'
import * as taskActions from '././entities/task/actions.js'
import Task from '././entities/task/Task.js'
import * as taskState from './entities/task/state.js'
import * as taskActions from './entities/task/actions.js'
import Task from './entities/task/Task.js'
import NewTaskForm from './entities/task/components/NewTaskForm.js'
import TaskList from './entities/task/components/TaskList.js'
import NewTaskForm from '././entities/task/components/NewTaskForm.js'
import TaskList from '././entities/task/components/TaskList.js'
import '././Main.css'
import './ext-src/Main.css'
export class Main extends Component {
@ -19,13 +17,19 @@ export class Main extends Component {
render() {
return (
<Todo
<>
{ /* Here we use Todo React component that we imported at the beginning of this file. */ }
<Todo
addTask={this.props.addTask}
taskList={this.props.taskList}
updateTask={this.props.updateTask}
removeTask={this.props.removeTask}
>
</Todo>
<div className="mainPage__nav">
<a href="/about" className="mainPage__nav__aboutLink"> About </a>
</div>
</>
)
}
}

View File

@ -1,4 +1,3 @@
import uuidv4 from 'uuid/v4'
export default class Task {
@ -7,8 +6,8 @@ export default class Task {
constructor (data = {}) {
this._data = {
id: data.id || uuidv4(),
description: data.description,
isDone: data.isDone,
description: data.description,
}
}
@ -16,12 +15,12 @@ export default class Task {
return this._data.id
}
get description () {
return this._data.description
}
get isDone () {
return this._data.isDone
}
get description () {
return this._data.description
}
toData () {
return this._data

View File

@ -11,12 +11,12 @@ export const add = (task) => ({
/**
* @param {String} id
* @param {Task} updatedTask
* @param {Object} data - Partial data that will be merged with existing task.
*/
export const update = (id, updatedTask) => ({
export const update = (id, data) => ({
type: types.UPDATE,
id,
data: updatedTask.toData()
data
})
/**

View File

@ -1,5 +1,6 @@
import _ from 'lodash'
import React from 'react'
import PropTypes from 'prop-types'
import FormControlLabel from '@material-ui/core/FormControlLabel'
import Switch from '@material-ui/core/Switch'
@ -10,12 +11,15 @@ import Task from '../Task'
export default class NewTaskForm extends React.Component {
// TODO: Add propTypes.
static propTypes = {
onCreate: PropTypes.func,
submitButtonLabel: PropTypes.string
}
state = {
fields: {
description: '',
isDone: false,
description: '',
}
}
@ -29,8 +33,8 @@ export default class NewTaskForm extends React.Component {
}
resetAllFields = () => {
this.setField('description', '')
this.setField('isDone', false)
this.setField('description', '')
}
toggleField = (name) => {
@ -48,24 +52,20 @@ export default class NewTaskForm extends React.Component {
render() {
return (
<div style={ { margin: '20px' } }>
<div className={this.props.className}>
<form noValidate onSubmit={this.handleSubmit} action="javascript:void(0);">
<div>
<TextField
label="description"
placeholder="What needs to be done?"
value={this.getField('description')}
onChange={event => this.setField('description', event.target.value)}
margin="normal"
fullWidth
InputLabelProps={{
shrink: true
}}
/>
</div>
</form>
</div>
)

View File

@ -29,13 +29,6 @@ export class TaskList extends React.Component {
taskBeingEdited: null
}
updateTaskField = (fieldName, newFieldValue, task) => {
const updatedTask = new Task(
{ ...task.toData(), [fieldName]: newFieldValue }
)
this.props.updateTask(task.id, updatedTask)
}
setAsBeingEdited = task => this.setState({
taskBeingEdited: task.id
})
@ -48,41 +41,28 @@ export class TaskList extends React.Component {
this.setState({ taskBeingEdited: null })
}
renderTaskDescription =
(task) => task.isDone ? <s>{task.description}</s> : task.description
render() {
const taskListToShow = this.props.filter ?
this.props.taskList.filter(this.props.filter) :
this.props.taskList
return (
<div style={ { margin: '20px' } }>
<div className={this.props.className}>
<Paper>
<Table>
<TableHead>
<TableHead style={{display: 'none'}}>
<TableRow>
<TableCell>description</TableCell>
<TableCell>isDone</TableCell>
<TableCell width="50%">isDone</TableCell>
<TableCell width="50%">description</TableCell>
</TableRow>
</TableHead>
<TableBody>
{taskListToShow.map((task) => (
<TableRow key={task.id}>
<ClickAwayListener onClickAway={() => this.finishEditing(task) }>
<TableCell
onDoubleClick={() => this.setAsBeingEdited(task)}
>
{this.props.editable && this.isBeingEdited(task) ? (
<TextField
value={task.description}
onChange={e => this.updateTaskField(
'description', e.target.value, task
)}
/>
) : (
task.description
)}
</TableCell>
</ClickAwayListener>
<TableCell>
<Checkbox
checked={task.isDone}
@ -91,11 +71,27 @@ export class TaskList extends React.Component {
'aria-label': 'checkbox'
}}
disabled={!this.props.editable}
onChange={e => this.updateTaskField(
'isDone', e.target.checked, task
onChange={e => this.props.updateTask(
task.id, { 'isDone': e.target.checked }
)}
/>
</TableCell>
<ClickAwayListener onClickAway={() => this.finishEditing(task) }>
<TableCell
onDoubleClick={() => this.setAsBeingEdited(task)}
>
{this.props.editable && this.isBeingEdited(task) ? (
<TextField
value={task.description}
onChange={e => this.props.updateTask(
task.id, { 'description': e.target.value }
)}
/>
) : (
this.renderTaskDescription(task)
)}
</TableCell>
</ClickAwayListener>
</TableRow>
))}
</TableBody>

View File

@ -24,7 +24,10 @@ const reducer = (state = initialState, action) => {
return {
...state,
all: state.all.map(
task => task.id === action.id ? action.data : task
task =>
task.id === action.id
? { ...task, ...action.data }
: task
)
}

View File

@ -0,0 +1,71 @@
div {
color: green;
}
button.selected {
border: 1px solid black;
}
.hidden {
visibility: hidden;
}
.todos {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
}
.todos__container {
width: 550px;
}
.todos__toggleAndInput {
display: flex;
align-items: flex-end;
margin-bottom: 10px;
}
.todos__toggleButton {
height: 40px;
margin-bottom: 8px;
margin-left: 20px;
background-color: white;
border: none;
display: inline-block;
font-size: 24px;
cursor: pointer;
}
.todos__newTaskForm {
width: 100%;
margin-left: 10px;
}
.todos__footer {
width: 100%;
margin-top: 10px;
display: flex;
justify-content: space-between;
}
.todos__footer__filters .filter:not(:last-child) {
margin-right: 10px;
}
.mainPage__nav {
text-align: center;
margin-top: 30px;
}
.mainPage__nav__aboutLink {
color: lightgray;
font-size: 12px;
text-decoration: none;
}
.mainPage__nav__aboutLink:hover {
text-decoration: underline;
}

View File

@ -0,0 +1,108 @@
// As seen here, we can import npm packages used in code generated by Wasp, which will be clearly defined.
// In the future, we will of course also be able to specify additional packages as dependencies.
import React from 'react'
// As seen here, we can import specific components/code generated by Wasp.
// These will have well defined and documented APIs and paths.
// Note that Task, NewTaskForm and TaskList are generated based on the declarations
// we made in todoApp.wasp file.
import Task from '../entities/task/Task'
import NewTaskForm from '../entities/task/components/NewTaskForm'
import TaskList from '../entities/task/components/TaskList'
const TASK_FILTER_TYPES = Object.freeze({
ALL: 'all',
ACTIVE: 'active',
COMPLETED: 'completed'
})
const TASK_FILTERS = Object.freeze({
[TASK_FILTER_TYPES.ALL]: null,
[TASK_FILTER_TYPES.ACTIVE]: task => !task.isDone,
[TASK_FILTER_TYPES.COMPLETED]: task => task.isDone
})
export default class Todo extends React.Component {
// TODO: prop types.
state = {
taskFilterName: TASK_FILTER_TYPES.ALL
}
toggleIsDoneForAllTasks = () => {
const areAllDone = this.props.taskList.every(t => t.isDone)
this.props.taskList.map(t => this.props.updateTask(t.id, { isDone: !areAllDone }))
}
deleteCompletedTasks = () => {
this.props.taskList.map((t) => { if (t.isDone) this.props.removeTask(t.id) })
}
isAnyTaskCompleted = () => this.props.taskList.some(t => t.isDone)
isThereAnyTask = () => this.props.taskList.length > 0
TaskFilterButton = ({ filterType, label }) => (
<button
className={'filter ' + (this.state.taskFilterName === filterType ? 'selected' : '')}
onClick={() => this.setState({ taskFilterName: filterType })}
>
{label}
</button>
)
render = () => {
return (
<div className="todos">
<div className="todos__container">
<h1> Todos </h1>
<div className="todos__toggleAndInput">
<button
disabled={!this.isThereAnyTask()}
className="todos__toggleButton"
onClick={this.toggleIsDoneForAllTasks}>
</button>
<NewTaskForm
className="todos__newTaskForm"
onCreate={task => this.props.addTask(task)}
submitButtonLabel={'Create new task'}
/>
</div>
{ this.isThereAnyTask() && (<>
<TaskList
editable
filter={TASK_FILTERS[this.state.taskFilterName]}
/>
<div className="todos__footer">
<div className="todos__footer__itemsLeft">
{ this.props.taskList.filter(task => !task.isDone).length } items left
</div>
<div className="todos__footer__filters">
<this.TaskFilterButton filterType={TASK_FILTER_TYPES.ALL} label="All" />
<this.TaskFilterButton filterType={TASK_FILTER_TYPES.ACTIVE} label="Active" />
<this.TaskFilterButton filterType={TASK_FILTER_TYPES.COMPLETED} label="Completed" />
</div>
<div className="todos__footer__clearCompleted">
<button
className={this.isAnyTaskCompleted() ? '' : 'hidden' }
onClick={this.deleteCompletedTasks}>
Clear completed
</button>
</div>
</div>
</>)}
</div>
</div>
)
}
}

View File

@ -1,89 +0,0 @@
import React from 'react'
import Task from '../entities/task/Task'
import NewTaskForm from '../entities/task/components/NewTaskForm'
import TaskList from '../entities/task/components/TaskList'
import * as config from './config'
const TASK_FILTER_TYPES = Object.freeze({
ALL: 'all',
ACTIVE: 'active',
COMPLETED: 'completed'
})
const TASK_FILTERS = Object.freeze({
[TASK_FILTER_TYPES.ALL]: null,
[TASK_FILTER_TYPES.ACTIVE]: task => !task.isDone,
[TASK_FILTER_TYPES.COMPLETED]: task => task.isDone
})
export default class Todo extends React.Component {
state = {
taskFilterName: TASK_FILTER_TYPES.ALL
}
toggleIsDoneForAllTasks = () => {
const areAllDone = this.props.taskList.every(t => t.isDone)
{/* TODO: This feels clumsy / complicated. Is there a better way than using id (maybe not)?
Should we consider passing just data to update, not the whole object, so we don't have to
create new object here? Maybe we can change this update, or have a second update method. */}
this.props.taskList.map(
(t) => this.props.updateTask(t.id, new Task ({ ...t.toData(), isDone: !areAllDone }))
)
}
deleteCompletedTasks = () => {
this.props.taskList.map((t) => { if (t.isDone) this.props.removeTask(t.id) })
}
TaskFilterButton = ({ filterType, label }) => (
<button
className={this.state.taskFilterName === filterType ? 'selected' : null}
onClick={() => this.setState({ taskFilterName: filterType })}
>
{label}
</button>
)
render = () => {
return (
<div className="mainContainer">
<h1> { config.appName } </h1>
<button onClick={this.toggleIsDoneForAllTasks}>
Toggle completion {/* TODO: Use icon (but we need to either install @material-ui/icons
or add font-awesome to the index.html. */}
</button>
<NewTaskForm
onCreate={task => this.props.addTask(task)}
submitButtonLabel={'Create new task'}
/>
<div className="taskListContainer">
<TaskList
editable
filter={TASK_FILTERS[this.state.taskFilterName]}
/>
</div>
<div className="footer">
<div>
{ this.props.taskList.filter(task => !task.isDone).length } items left
{ this.props.taskList.some(t => t.isDone) &&
<button onClick={this.deleteCompletedTasks}>Clear completed</button>
}
</div>
<div>
<this.TaskFilterButton filterType={TASK_FILTER_TYPES.ALL} label="All" />
<this.TaskFilterButton filterType={TASK_FILTER_TYPES.ACTIVE} label="Active" />
<this.TaskFilterButton filterType={TASK_FILTER_TYPES.COMPLETED} label="Completed" />
</div>
</div>
</div>
)
}
}

View File

@ -1,2 +0,0 @@
export const appName = 'Todos'

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

View File

@ -2,12 +2,14 @@ import React from 'react'
import { Route, BrowserRouter as Router } from 'react-router-dom'
import Main from './Main'
import About from './About'
const router = (
<Router>
<div>
<Route exact path="/" component={ Main }/>
<Route exact path="/about" component={ About }/>
</div>
</Router>
)

View File

@ -1,4 +1,4 @@
import * as reduxStarterKit from 'redux-starter-kit'
import * as RTK from '@reduxjs/toolkit'
import loggerMiddleware from './middleware/logger'
@ -9,13 +9,13 @@ import loggerMiddleware from './middleware/logger'
*/
export const configureStore = (reducer, preloadedState) => {
const middleware = [
loggerMiddleware,
...reduxStarterKit.getDefaultMiddleware()
...RTK.getDefaultMiddleware(),
loggerMiddleware
]
const enhancers = []
const store = reduxStarterKit.configureStore({
const store = RTK.configureStore({
reducer,
preloadedState,
middleware,

View File

@ -0,0 +1,71 @@
div {
color: green;
}
button.selected {
border: 1px solid black;
}
.hidden {
visibility: hidden;
}
.todos {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
}
.todos__container {
width: 550px;
}
.todos__toggleAndInput {
display: flex;
align-items: flex-end;
margin-bottom: 10px;
}
.todos__toggleButton {
height: 40px;
margin-bottom: 8px;
margin-left: 20px;
background-color: white;
border: none;
display: inline-block;
font-size: 24px;
cursor: pointer;
}
.todos__newTaskForm {
width: 100%;
margin-left: 10px;
}
.todos__footer {
width: 100%;
margin-top: 10px;
display: flex;
justify-content: space-between;
}
.todos__footer__filters .filter:not(:last-child) {
margin-right: 10px;
}
.mainPage__nav {
text-align: center;
margin-top: 30px;
}
.mainPage__nav__aboutLink {
color: lightgray;
font-size: 12px;
text-decoration: none;
}
.mainPage__nav__aboutLink:hover {
text-decoration: underline;
}

View File

@ -0,0 +1,108 @@
// As seen here, we can import npm packages used in code generated by Wasp, which will be clearly defined.
// In the future, we will of course also be able to specify additional packages as dependencies.
import React from 'react'
// As seen here, we can import specific components/code generated by Wasp.
// These will have well defined and documented APIs and paths.
// Note that Task, NewTaskForm and TaskList are generated based on the declarations
// we made in todoApp.wasp file.
import Task from '@wasp/entities/task/Task'
import NewTaskForm from '@wasp/entities/task/components/NewTaskForm'
import TaskList from '@wasp/entities/task/components/TaskList'
const TASK_FILTER_TYPES = Object.freeze({
ALL: 'all',
ACTIVE: 'active',
COMPLETED: 'completed'
})
const TASK_FILTERS = Object.freeze({
[TASK_FILTER_TYPES.ALL]: null,
[TASK_FILTER_TYPES.ACTIVE]: task => !task.isDone,
[TASK_FILTER_TYPES.COMPLETED]: task => task.isDone
})
export default class Todo extends React.Component {
// TODO: prop types.
state = {
taskFilterName: TASK_FILTER_TYPES.ALL
}
toggleIsDoneForAllTasks = () => {
const areAllDone = this.props.taskList.every(t => t.isDone)
this.props.taskList.map(t => this.props.updateTask(t.id, { isDone: !areAllDone }))
}
deleteCompletedTasks = () => {
this.props.taskList.map((t) => { if (t.isDone) this.props.removeTask(t.id) })
}
isAnyTaskCompleted = () => this.props.taskList.some(t => t.isDone)
isThereAnyTask = () => this.props.taskList.length > 0
TaskFilterButton = ({ filterType, label }) => (
<button
className={'filter ' + (this.state.taskFilterName === filterType ? 'selected' : '')}
onClick={() => this.setState({ taskFilterName: filterType })}
>
{label}
</button>
)
render = () => {
return (
<div className="todos">
<div className="todos__container">
<h1> Todos </h1>
<div className="todos__toggleAndInput">
<button
disabled={!this.isThereAnyTask()}
className="todos__toggleButton"
onClick={this.toggleIsDoneForAllTasks}>
</button>
<NewTaskForm
className="todos__newTaskForm"
onCreate={task => this.props.addTask(task)}
submitButtonLabel={'Create new task'}
/>
</div>
{ this.isThereAnyTask() && (<>
<TaskList
editable
filter={TASK_FILTERS[this.state.taskFilterName]}
/>
<div className="todos__footer">
<div className="todos__footer__itemsLeft">
{ this.props.taskList.filter(task => !task.isDone).length } items left
</div>
<div className="todos__footer__filters">
<this.TaskFilterButton filterType={TASK_FILTER_TYPES.ALL} label="All" />
<this.TaskFilterButton filterType={TASK_FILTER_TYPES.ACTIVE} label="Active" />
<this.TaskFilterButton filterType={TASK_FILTER_TYPES.COMPLETED} label="Completed" />
</div>
<div className="todos__footer__clearCompleted">
<button
className={this.isAnyTaskCompleted() ? '' : 'hidden' }
onClick={this.deleteCompletedTasks}>
Clear completed
</button>
</div>
</div>
</>)}
</div>
</div>
)
}
}

View File

@ -1,89 +0,0 @@
import React from 'react'
import Task from '@wasp/entities/task/Task'
import NewTaskForm from '@wasp/entities/task/components/NewTaskForm'
import TaskList from '@wasp/entities/task/components/TaskList'
import * as config from './config'
const TASK_FILTER_TYPES = Object.freeze({
ALL: 'all',
ACTIVE: 'active',
COMPLETED: 'completed'
})
const TASK_FILTERS = Object.freeze({
[TASK_FILTER_TYPES.ALL]: null,
[TASK_FILTER_TYPES.ACTIVE]: task => !task.isDone,
[TASK_FILTER_TYPES.COMPLETED]: task => task.isDone
})
export default class Todo extends React.Component {
state = {
taskFilterName: TASK_FILTER_TYPES.ALL
}
toggleIsDoneForAllTasks = () => {
const areAllDone = this.props.taskList.every(t => t.isDone)
{/* TODO: This feels clumsy / complicated. Is there a better way than using id (maybe not)?
Should we consider passing just data to update, not the whole object, so we don't have to
create new object here? Maybe we can change this update, or have a second update method. */}
this.props.taskList.map(
(t) => this.props.updateTask(t.id, new Task ({ ...t.toData(), isDone: !areAllDone }))
)
}
deleteCompletedTasks = () => {
this.props.taskList.map((t) => { if (t.isDone) this.props.removeTask(t.id) })
}
TaskFilterButton = ({ filterType, label }) => (
<button
className={this.state.taskFilterName === filterType ? 'selected' : null}
onClick={() => this.setState({ taskFilterName: filterType })}
>
{label}
</button>
)
render = () => {
return (
<div className="mainContainer">
<h1> { config.appName } </h1>
<button onClick={this.toggleIsDoneForAllTasks}>
Toggle completion {/* TODO: Use icon (but we need to either install @material-ui/icons
or add font-awesome to the index.html. */}
</button>
<NewTaskForm
onCreate={task => this.props.addTask(task)}
submitButtonLabel={'Create new task'}
/>
<div className="taskListContainer">
<TaskList
editable
filter={TASK_FILTERS[this.state.taskFilterName]}
/>
</div>
<div className="footer">
<div>
{ this.props.taskList.filter(task => !task.isDone).length } items left
{ this.props.taskList.some(t => t.isDone) &&
<button onClick={this.deleteCompletedTasks}>Clear completed</button>
}
</div>
<div>
<this.TaskFilterButton filterType={TASK_FILTER_TYPES.ALL} label="All" />
<this.TaskFilterButton filterType={TASK_FILTER_TYPES.ACTIVE} label="Active" />
<this.TaskFilterButton filterType={TASK_FILTER_TYPES.COMPLETED} label="Completed" />
</div>
</div>
</div>
)
}
}

View File

@ -1,2 +0,0 @@
export const appName = 'Todos'

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

View File

@ -1,44 +1,21 @@
// Goal of this file is to re-create a TODO app from http://todomvc.com
import Todo from "@ext/Todo.js" // Imports non-wasp code from external code dir (ext/).
import Todo from "Todo" // Imports from external code dir (src/).
// -- Entities
entity Task {
description :: string,
isDone :: boolean
isDone :: boolean,
description :: string
}
// -- App and pages
app todoApp {
title: "ToDo App"
}
// IDEA: `@connect Task as taskList` -> this would make it more obvious what is available, also we don't need to automatically try to guess what to import.
page Main {
route: "/",
style: {=css
div {
color: green;
}
.mainContainer {
display: flex;
flex-direction: column;
align-items: center;
}
.taskListContainer {
width: 60%;
}
.selected {
border: 1px solid black;
}
css=},
content: {=jsx
style: "@ext/Main.css",
// TODO: We need to make this nicer / more explicit, it is not clear where is this coming from (these props).
// Also, this wiring is not elegant.
content: {=jsx <>
{ /* Here we use Todo React component that we imported at the beginning of this file. */ }
<Todo
addTask={this.props.addTask}
taskList={this.props.taskList}
@ -46,27 +23,55 @@ page Main {
removeTask={this.props.removeTask}
>
</Todo>
<div className="mainPage__nav">
<a href="/about" className="mainPage__nav__aboutLink"> About </a>
</div>
</> jsx=}
}
page About {
route: "/about",
style: {=css
.aboutPage { color: black; text-align: center; }
css=},
content: {=jsx
<div className="aboutPage">
<h1> About </h1>
<p>This page was built with <a href="https://wasp-lang.dev">Wasp</a>!</p>
<p> Check out source code &nbsp;
<a href="https://github.com/wasp-lang/wasp/tree/master/examples/todoApp">here</a>.
</p>
</div>
jsx=}
}
// Entity form definition.
entity-form<Task> NewTaskForm {
fields: {
description: {
show: true
show: true,
label: none,
placeholder: "What needs to be done?"
},
isDone: {
show: false,
defaultValue: false // Although not shown, this field will be set to "false".
defaultValue: false
}
},
submit: {
onEnter: true, // Parsed but not generated yet.
onEnter: true,
button: { show: false }
}
}
// Entity list definition.
entity-list<Task> TaskList {
// Options TBD, not supported for now.
showHeader: false,
fields: {
description: {
// The contract for render is that user must provide a function that:
// - Receives a task as an input.
// - Returns a React Node or something that can be rendered by JSX.
// - Does not depend on any outer context.
render: {=js (task) => task.isDone ? <s>{task.description}</s> : task.description js=}
}
}
}