mirror of
https://github.com/microsoft/frontend-bootcamp.git
synced 2026-01-26 14:56:42 +08:00
checking in docs for github.io page
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -4,4 +4,3 @@ lib
|
|||||||
*.log
|
*.log
|
||||||
.DS_Store
|
.DS_Store
|
||||||
tmp.json
|
tmp.json
|
||||||
/build
|
|
||||||
85
docs/assets/shared.css
Normal file
85
docs/assets/shared.css
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
html {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
*,
|
||||||
|
*:before,
|
||||||
|
*:after {
|
||||||
|
box-sizing: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background-color: #f3f2f1;
|
||||||
|
background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBzdGFuZGFsb25lPSJubyI/PjxzdmcgIHdpZHRoPSI2MDAiICBoZWlnaHQ9IjYwMCIgIHZpZXdCb3g9IjAgMCA2MDAgNjAwIiAgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4gIDxnIHRyYW5zZm9ybT0idHJhbnNsYXRlKDMwMCwzMDApIj4gICAgPHBhdGggZD0iTTEyMi42LC0xMTQuOEMxNzAuMywtNzUsMjI4LjEsLTM3LjUsMjM0LjQsNi4yQzI0MC42LDUwLDE5NS4zLDk5LjksMTQ3LjYsMTI5LjZDOTkuOSwxNTkuMyw1MCwxNjguNiw0LjcsMTYzLjlDLTQwLjUsMTU5LjIsLTgxLjEsMTQwLjQsLTExNS43LDExMC43Qy0xNTAuNCw4MS4xLC0xNzkuMiw0MC41LC0xOTMuOCwtMTQuNkMtMjA4LjQsLTY5LjgsLTIwOC45LC0xMzkuNSwtMTc0LjIsLTE3OS40Qy0xMzkuNSwtMjE5LjIsLTY5LjgsLTIyOS4xLC0xNi4xLC0yMTNDMzcuNSwtMTk2LjgsNzUsLTE1NC42LDEyMi42LC0xMTQuOFoiIGZpbGw9IiMzZDk3ZTMiIC8+ICA8L2c+PC9zdmc+);
|
||||||
|
background-attachment: fixed;
|
||||||
|
background-position: 50% 50%;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: 900px 740px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Container {
|
||||||
|
justify-content: center;
|
||||||
|
padding: 20px 0;
|
||||||
|
max-width: 1040px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Tiles {
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
||||||
|
grid-gap: 20px;
|
||||||
|
display: grid;
|
||||||
|
list-style-type: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Tile {
|
||||||
|
background-color: white;
|
||||||
|
border-radius: 2px;
|
||||||
|
box-shadow: 0 3.2px 7.2px 0 rgba(0, 0, 0, 0.132), 0 0.6px 1.8px 0 rgba(0, 0, 0, 0.108);
|
||||||
|
opacity: 0.96;
|
||||||
|
transition: all 0.15s linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Tile:not(.Tile--intro):hover {
|
||||||
|
box-shadow: 0 6.4px 14.4px 0 rgba(0, 0, 0, 0.132), 0 1.2px 3.6px 0 rgba(0, 0, 0, 0.108);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Tile-link {
|
||||||
|
align-items: center;
|
||||||
|
color: #323130;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 148px;
|
||||||
|
justify-content: center;
|
||||||
|
text-align: center;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Tile-link i {
|
||||||
|
font-size: 32px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
color: #605e5c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Tile--intro {
|
||||||
|
grid-column: span 2;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Tile--intro h1 {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 300;
|
||||||
|
margin: 8px 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Tile--intro p {
|
||||||
|
font-size: 14px;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Tile--intro a,
|
||||||
|
.Tile--intro a:visited {
|
||||||
|
color: #0078d4;
|
||||||
|
}
|
||||||
147
docs/index.html
Normal file
147
docs/index.html
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="stylesheet" href="https://static2.sharepointonline.com/files/fabric/office-ui-fabric-core/9.6.1/css/fabric.min.css" />
|
||||||
|
<link rel="stylesheet" href="./assets/shared.css" />
|
||||||
|
</head>
|
||||||
|
<body class="ms-Fabric">
|
||||||
|
<div class="Container">
|
||||||
|
<h1>Frontend Bootcamp</h1>
|
||||||
|
<a href="https://github.com/kenotron/bootcamp">github sources</a>
|
||||||
|
</div>
|
||||||
|
<div class="Container">
|
||||||
|
<ul class="Tiles">
|
||||||
|
<li class="Tile Tile--intro">
|
||||||
|
<h2>Day 1</h2>
|
||||||
|
Setup, HTML, CSS, Javascript and Intro to React
|
||||||
|
</li>
|
||||||
|
<li class="Tile">
|
||||||
|
<a target="_blank" href="/step1-00/" class="Tile-link">
|
||||||
|
Step 0 <br />
|
||||||
|
HTML/CSS/JS
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="Tile">
|
||||||
|
<a target="_blank" href="/step1-01/" class="Tile-link">
|
||||||
|
Step 1 <br />
|
||||||
|
Todo HTML
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="Tile">
|
||||||
|
<a target="_blank" href="/step1-02/" class="Tile-link">
|
||||||
|
Step 2 <br />
|
||||||
|
Todo CSS
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="Tile">
|
||||||
|
<a target="_blank" href="/step1-03/" class="Tile-link">
|
||||||
|
Step 3 <br />
|
||||||
|
Todo JS
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="Tile">
|
||||||
|
<a target="_blank" href="/step1-04/" class="Tile-link">
|
||||||
|
Step 4 <br />
|
||||||
|
React Intro
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="Tile">
|
||||||
|
<a target="_blank" href="/step1-05/" class="Tile-link">
|
||||||
|
Step 5 <br />
|
||||||
|
React Components
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="Tile">
|
||||||
|
<a target="_blank" href="/step1-06/" class="Tile-link">
|
||||||
|
Step 6 <br />
|
||||||
|
State Driven UI
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="Tile">
|
||||||
|
<a target="_blank" href="/step1-07/" class="Tile-link">
|
||||||
|
Step 7 <br />
|
||||||
|
UI Driven State
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="Container">
|
||||||
|
<ul class="Tiles">
|
||||||
|
<li class="Tile Tile--intro">
|
||||||
|
<h2>Day 2</h2>
|
||||||
|
UI Fabric and Redux Deep Dive
|
||||||
|
</li>
|
||||||
|
<li class="Tile">
|
||||||
|
<div class="Tile-link">
|
||||||
|
Step 1<br />
|
||||||
|
Typescript Basics
|
||||||
|
<div><a target="_blank" href="/step2-01/demo/">demo</a> | <a target="_blank" href="/step2-01/exercise/">exercise</a></div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="Tile">
|
||||||
|
<div class="Tile-link">
|
||||||
|
Step 2<br />
|
||||||
|
UI Fabric
|
||||||
|
<div><a target="_blank" href="/step2-02/demo/">demo</a> | <a target="_blank" href="/step2-02/exercise/">exercise</a></div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="Tile">
|
||||||
|
<div class="Tile-link">
|
||||||
|
Step 3<br />
|
||||||
|
Theming and Styling
|
||||||
|
<div><a target="_blank" href="/step2-03/demo/">demo</a> | <a target="_blank" href="/step2-03/exercise/">exercise</a></div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="Tile">
|
||||||
|
<div class="Tile-link">
|
||||||
|
Step 4<br />
|
||||||
|
Testing with jest
|
||||||
|
<div><a target="_blank" href="/step2-04/demo/">demo</a> | <a target="_blank" href="/step2-04/exercise/">exercise</a></div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="Tile">
|
||||||
|
<div class="Tile-link">
|
||||||
|
Step 5<br />
|
||||||
|
Redux: Reducers
|
||||||
|
<div><a target="_blank" href="/step2-05/demo/">demo</a> | <a target="_blank" href="/step2-05/exercise/">exercise</a></div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="Tile">
|
||||||
|
<div class="Tile-link">
|
||||||
|
Step 6<br />
|
||||||
|
Redux: Dispatch Actions
|
||||||
|
<div><a target="_blank" href="/step2-06/demo/">demo</a> | <a target="_blank" href="/step2-06/exercise/">exercise</a></div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="Tile">
|
||||||
|
<div class="Tile-link">
|
||||||
|
Step 7<br />
|
||||||
|
Redux: Connect to UI
|
||||||
|
<div><a target="_blank" href="/step2-07/demo/">demo</a> | <a target="_blank" href="/step2-07/exercise/">exercise</a></div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="Tile">
|
||||||
|
<a target="_blank" href="/step2-08/" class="Tile-link">
|
||||||
|
Step 8<br />
|
||||||
|
Redux: Combine Reducers
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="Tile">
|
||||||
|
<a target="_blank" href="/step2-09/" class="Tile-link">
|
||||||
|
Step 9<br />
|
||||||
|
Redux: Service Calls
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="Container">
|
||||||
|
<ul class="Tiles">
|
||||||
|
<li class="Tile Tile--intro">
|
||||||
|
<h2>Playground</h2>
|
||||||
|
</li>
|
||||||
|
<li class="Tile"><a target="_blank" href="/playground/" class="Tile-link">Playground</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
6
docs/playground/index.html
Normal file
6
docs/playground/index.html
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="text/javascript" src="../playground/playground.js"></script></body>
|
||||||
|
</html>
|
||||||
10786
docs/playground/playground.js
Normal file
10786
docs/playground/playground.js
Normal file
File diff suppressed because one or more lines are too long
58
docs/playground/src/actions/index.ts
Normal file
58
docs/playground/src/actions/index.ts
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
import { action, GenericActionTypes, GenericAction, GenericActionLookup } from '../redux-utils/action';
|
||||||
|
import { Dispatch } from 'redux';
|
||||||
|
import { Store } from '../store';
|
||||||
|
import uuid from 'uuid/v4';
|
||||||
|
import * as todosService from '../service/todosService';
|
||||||
|
|
||||||
|
export const actions = {
|
||||||
|
add: (label: string) => action('add', { id: uuid(), label }),
|
||||||
|
remove: (id: string) => action('remove', { id }),
|
||||||
|
edit: (id: string, label: string) => action('edit', { id, label }),
|
||||||
|
complete: (id: string) => action('complete', { id }),
|
||||||
|
clear: () => action('clear'),
|
||||||
|
setFilter: (filter: string) => action('setFilter', { filter })
|
||||||
|
};
|
||||||
|
|
||||||
|
export const actionsWithService = {
|
||||||
|
add: (label: string) => {
|
||||||
|
return async (dispatch: Dispatch<TodoAction>, getState: () => Store) => {
|
||||||
|
const addAction = actions.add(label);
|
||||||
|
const id = addAction.id;
|
||||||
|
dispatch(addAction);
|
||||||
|
await todosService.add(id, getState().todos[id]);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
edit: (id: string, label: string) => {
|
||||||
|
return async (dispatch: Dispatch<TodoAction>, getState: () => Store) => {
|
||||||
|
dispatch(actions.edit(id, label));
|
||||||
|
await todosService.edit(id, getState().todos[id]);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
remove: (id: string) => {
|
||||||
|
return async (dispatch: Dispatch<TodoAction>, getState: () => Store) => {
|
||||||
|
dispatch(actions.remove(id));
|
||||||
|
await todosService.remove(id);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
complete: (id: string) => {
|
||||||
|
return async (dispatch: Dispatch<TodoAction>, getState: () => Store) => {
|
||||||
|
dispatch(actions.complete(id));
|
||||||
|
await todosService.edit(id, getState().todos[id]);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
clear: () => {
|
||||||
|
return async (dispatch: Dispatch<TodoAction>, getState: () => Store) => {
|
||||||
|
dispatch(actions.clear());
|
||||||
|
await todosService.editBulk(getState().todos);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ActionTypes = GenericActionTypes<typeof actions>;
|
||||||
|
export type TodoAction = GenericAction<typeof actions>;
|
||||||
|
export type TodoActionWithService = GenericAction<typeof actionsWithService>;
|
||||||
|
export type TodoActionLookup = GenericActionLookup<typeof actions>;
|
||||||
15
docs/playground/src/components/TodoApp.tsx
Normal file
15
docs/playground/src/components/TodoApp.tsx
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Stack } from 'office-ui-fabric-react';
|
||||||
|
import { TodoFooter } from './TodoFooter';
|
||||||
|
import { TodoHeader } from './TodoHeader';
|
||||||
|
import { TodoList } from './TodoList';
|
||||||
|
|
||||||
|
export const TodoApp = (props: {}) => (
|
||||||
|
<Stack horizontalAlign="center">
|
||||||
|
<Stack style={{ width: 400 }} gap={25}>
|
||||||
|
<TodoHeader />
|
||||||
|
<TodoList />
|
||||||
|
<TodoFooter />
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
39
docs/playground/src/components/TodoFooter.tsx
Normal file
39
docs/playground/src/components/TodoFooter.tsx
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Text } from '@uifabric/experiments';
|
||||||
|
import { Stack } from 'office-ui-fabric-react';
|
||||||
|
import { Store } from '../store';
|
||||||
|
import { DefaultButton } from 'office-ui-fabric-react';
|
||||||
|
import { actionsWithService } from '../actions';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
|
// Redux Container
|
||||||
|
export function mapStateToProps({ todos, filter }: Store) {
|
||||||
|
return {
|
||||||
|
todos,
|
||||||
|
filter
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function mapDispatchToProps(dispatch: any) {
|
||||||
|
return {
|
||||||
|
clear: () => dispatch(actionsWithService.clear())
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
type TodoFooterProps = ReturnType<typeof mapStateToProps> & ReturnType<typeof mapDispatchToProps>;
|
||||||
|
|
||||||
|
export const TodoFooter = connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)((props: TodoFooterProps) => {
|
||||||
|
const itemCount = Object.keys(props.todos).filter(id => !props.todos[id].completed).length;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack horizontal horizontalAlign="space-between">
|
||||||
|
<Text>
|
||||||
|
{itemCount} item{itemCount > 1 ? 's' : ''} left
|
||||||
|
</Text>
|
||||||
|
<DefaultButton onClick={() => props.clear()}>Clear Completed</DefaultButton>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
});
|
||||||
80
docs/playground/src/components/TodoHeader.tsx
Normal file
80
docs/playground/src/components/TodoHeader.tsx
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Text } from '@uifabric/experiments';
|
||||||
|
import { Pivot, PivotItem, TextField, Stack } from 'office-ui-fabric-react';
|
||||||
|
import { FilterTypes, Store } from '../store';
|
||||||
|
import { actionsWithService, actions } from '../actions';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
|
function mapStateToProps({ todos, filter }: Store) {
|
||||||
|
return {
|
||||||
|
todos,
|
||||||
|
filter
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapDispatchToProps(dispatch: any) {
|
||||||
|
return {
|
||||||
|
add: (label: string) => dispatch(actionsWithService.add(label)),
|
||||||
|
remove: (id: string) => dispatch(actionsWithService.remove(id)),
|
||||||
|
setFilter: (filter: FilterTypes) => dispatch(actions.setFilter(filter))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
type TodoHeaderProps = ReturnType<typeof mapStateToProps> & ReturnType<typeof mapDispatchToProps>;
|
||||||
|
|
||||||
|
interface TodoHeaderState {
|
||||||
|
labelInput: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
class TodoHeader extends React.Component<TodoHeaderProps, TodoHeaderState> {
|
||||||
|
constructor(props: TodoHeaderProps) {
|
||||||
|
super(props);
|
||||||
|
this.state = { labelInput: undefined };
|
||||||
|
}
|
||||||
|
|
||||||
|
onKeyPress = (evt: React.KeyboardEvent) => {
|
||||||
|
if (evt.charCode === 13) {
|
||||||
|
this.props.add(this.state.labelInput);
|
||||||
|
this.setState({ labelInput: undefined });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onChange = (evt: React.FormEvent<HTMLInputElement>, newValue: string) => {
|
||||||
|
this.setState({ labelInput: newValue });
|
||||||
|
};
|
||||||
|
|
||||||
|
onFilter = (item: PivotItem) => {
|
||||||
|
this.props.setFilter(item.props.headerText as FilterTypes);
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<Stack>
|
||||||
|
<Stack horizontal horizontalAlign="center">
|
||||||
|
<Text variant="xxLarge">todos</Text>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
placeholder="What needs to be done?"
|
||||||
|
value={this.state.labelInput}
|
||||||
|
onChange={this.onChange}
|
||||||
|
onKeyPress={this.onKeyPress}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Pivot onLinkClick={this.onFilter}>
|
||||||
|
<PivotItem headerText="all" />
|
||||||
|
<PivotItem headerText="active" />
|
||||||
|
<PivotItem headerText="completed" />
|
||||||
|
</Pivot>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hook up the Redux state and dispatches
|
||||||
|
const component = connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(TodoHeader);
|
||||||
|
|
||||||
|
export { component as TodoHeader };
|
||||||
40
docs/playground/src/components/TodoList.tsx
Normal file
40
docs/playground/src/components/TodoList.tsx
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Stack } from 'office-ui-fabric-react';
|
||||||
|
import { TodoListItem } from './TodoListItem';
|
||||||
|
import { Store } from '../store';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
|
function mapStateToProps({ todos, filter }: Store) {
|
||||||
|
return {
|
||||||
|
todos,
|
||||||
|
filter
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapDispatchToProps(dispatch: any) {}
|
||||||
|
|
||||||
|
type TodoListProps = ReturnType<typeof mapStateToProps> & ReturnType<typeof mapDispatchToProps>;
|
||||||
|
|
||||||
|
class TodoList extends React.Component<TodoListProps> {
|
||||||
|
render() {
|
||||||
|
const { filter, todos } = this.props;
|
||||||
|
const filteredTodos = Object.keys(todos).filter(id => {
|
||||||
|
return filter === 'all' || (filter === 'completed' && todos[id].completed) || (filter === 'active' && !todos[id].completed);
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack gap={10}>
|
||||||
|
{filteredTodos.map(id => (
|
||||||
|
<TodoListItem key={id} id={id} />
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const component = connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(TodoList);
|
||||||
|
|
||||||
|
export { component as TodoList };
|
||||||
104
docs/playground/src/components/TodoListItem.tsx
Normal file
104
docs/playground/src/components/TodoListItem.tsx
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Stack, Checkbox, IconButton, TextField } from 'office-ui-fabric-react';
|
||||||
|
import { mergeStyles } from '@uifabric/styling';
|
||||||
|
import { Store } from '../store';
|
||||||
|
import { actionsWithService } from '../actions';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
|
function mapStateToProps({ todos }: Store) {
|
||||||
|
return {
|
||||||
|
todos
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapDispatchToProps(dispatch: any) {
|
||||||
|
return {
|
||||||
|
remove: (id: string) => dispatch(actionsWithService.remove(id)),
|
||||||
|
complete: (id: string) => dispatch(actionsWithService.complete(id)),
|
||||||
|
edit: (id: string, label: string) => dispatch(actionsWithService.edit(id, label))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
type TodoListItemProps = { id: string } & ReturnType<typeof mapDispatchToProps> & ReturnType<typeof mapStateToProps>;
|
||||||
|
|
||||||
|
interface TodoListItemState {
|
||||||
|
editing: boolean;
|
||||||
|
editLabel: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const className = mergeStyles({
|
||||||
|
selectors: {
|
||||||
|
'.clearButton': {
|
||||||
|
visibility: 'hidden'
|
||||||
|
},
|
||||||
|
'&:hover .clearButton': {
|
||||||
|
visibility: 'visible'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
class TodoListItem extends React.Component<TodoListItemProps, TodoListItemState> {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
constructor(props: TodoListItemProps) {
|
||||||
|
super(props);
|
||||||
|
this.state = { editing: false, editLabel: undefined };
|
||||||
|
}
|
||||||
|
|
||||||
|
onEdit = () => {
|
||||||
|
const { todos, id } = this.props;
|
||||||
|
const { label } = todos[id];
|
||||||
|
|
||||||
|
this.setState(prevState => ({
|
||||||
|
editing: true,
|
||||||
|
editLabel: prevState.editLabel || label
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
onDoneEdit = () => {
|
||||||
|
this.props.edit(this.props.id, this.state.editLabel);
|
||||||
|
this.setState(prevState => ({
|
||||||
|
editing: false,
|
||||||
|
editLabel: undefined
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
onKeyDown = (evt: React.KeyboardEvent) => {
|
||||||
|
if (evt.which === 13) {
|
||||||
|
this.onDoneEdit();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onChange = (evt: React.FormEvent<HTMLInputElement>, newValue: string) => {
|
||||||
|
this.setState({ editLabel: newValue });
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { todos, id, complete, remove } = this.props;
|
||||||
|
const item = todos[id];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack horizontal className={className} verticalAlign="center" horizontalAlign="space-between">
|
||||||
|
{!this.state.editing && (
|
||||||
|
<>
|
||||||
|
<Checkbox label={item.label} checked={item.completed} onChange={() => complete(id)} />
|
||||||
|
<div>
|
||||||
|
<IconButton iconProps={{ iconName: 'Edit' }} className="clearButton" onClick={this.onEdit} />
|
||||||
|
<IconButton iconProps={{ iconName: 'Cancel' }} className="clearButton" onClick={() => remove(id)} />
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{this.state.editing && <TextField value={this.state.editLabel} onChange={this.onChange} onKeyPress={this.onKeyDown} />}
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const component = connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(TodoListItem);
|
||||||
|
|
||||||
|
export { component as TodoListItem };
|
||||||
36
docs/playground/src/index.tsx
Normal file
36
docs/playground/src/index.tsx
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
|
import { createStore, applyMiddleware, compose } from 'redux';
|
||||||
|
import { Provider } from 'react-redux';
|
||||||
|
import { reducer } from './reducers';
|
||||||
|
import { TodoApp } from './components/TodoApp';
|
||||||
|
import { initializeIcons } from '@uifabric/icons';
|
||||||
|
import thunk from 'redux-thunk';
|
||||||
|
import * as todosService from './service/todosService';
|
||||||
|
import { FilterTypes } from './store';
|
||||||
|
|
||||||
|
declare var window: any;
|
||||||
|
|
||||||
|
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
|
||||||
|
|
||||||
|
// For preloading store
|
||||||
|
initializeIcons();
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
const preloadStore = {
|
||||||
|
todos: await todosService.getAll(),
|
||||||
|
filter: 'all' as FilterTypes
|
||||||
|
};
|
||||||
|
|
||||||
|
const store = createStore(reducer, preloadStore, composeEnhancers(applyMiddleware(thunk)));
|
||||||
|
|
||||||
|
ReactDOM.render(
|
||||||
|
<Provider store={store}>
|
||||||
|
<TodoApp />
|
||||||
|
</Provider>,
|
||||||
|
document.getElementById('app')
|
||||||
|
);
|
||||||
|
})();
|
||||||
|
|
||||||
|
// For Synchronous Case
|
||||||
|
// const store = createStore(reducer, { todos: {}, filter: 'all' }, composeEnhancers(applyMiddleware(thunk)));
|
||||||
10
docs/playground/src/reducers/__tests__/reducers.spec.ts
Normal file
10
docs/playground/src/reducers/__tests__/reducers.spec.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { reducer } from '../index';
|
||||||
|
|
||||||
|
describe('reducers', () => {
|
||||||
|
it('should add item to the list', () => {
|
||||||
|
const newStore = reducer({ todos: {}, filter: 'all' }, { type: 'add', label: 'hello' });
|
||||||
|
const keys = Object.keys(newStore.todos);
|
||||||
|
expect(keys.length).toBe(1);
|
||||||
|
expect(newStore.todos[keys[0]].label).toBe('hello');
|
||||||
|
});
|
||||||
|
});
|
||||||
10
docs/playground/src/reducers/createReducer.ts
Normal file
10
docs/playground/src/reducers/createReducer.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { ActionTypes, TodoActionLookup, actions } from '../actions';
|
||||||
|
import { createGenericReducer, HandlerMap, ImmerReducer } from '../redux-utils/reducer';
|
||||||
|
import { Reducer } from 'redux';
|
||||||
|
|
||||||
|
export function createReducer<T, AM extends ActionTypes | never = never>(
|
||||||
|
initialState: T,
|
||||||
|
handlerOrMap: HandlerMap<T, typeof actions> | ImmerReducer<T, TodoActionLookup[AM]>
|
||||||
|
): Reducer<T> {
|
||||||
|
return createGenericReducer<T, typeof actions, AM>(initialState, handlerOrMap);
|
||||||
|
}
|
||||||
42
docs/playground/src/reducers/index.ts
Normal file
42
docs/playground/src/reducers/index.ts
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import { createReducer } from './createReducer';
|
||||||
|
import { Store, FilterTypes } from '../store';
|
||||||
|
import { combineReducers } from 'redux';
|
||||||
|
|
||||||
|
export const reducer = combineReducers<Store>({
|
||||||
|
todos: createReducer<Store['todos']>(
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
add(draft, action) {
|
||||||
|
draft[action.id] = { label: action.label, completed: false };
|
||||||
|
return draft;
|
||||||
|
},
|
||||||
|
|
||||||
|
remove(draft, action) {
|
||||||
|
delete draft[action.id];
|
||||||
|
return draft;
|
||||||
|
},
|
||||||
|
|
||||||
|
complete(draft, action) {
|
||||||
|
draft[action.id].completed = !draft[action.id].completed;
|
||||||
|
return draft;
|
||||||
|
},
|
||||||
|
|
||||||
|
clear(draft) {
|
||||||
|
Object.keys(draft).forEach(id => {
|
||||||
|
if (draft[id].completed) {
|
||||||
|
delete draft[id];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return draft;
|
||||||
|
},
|
||||||
|
|
||||||
|
edit(draft, action) {
|
||||||
|
draft[action.id].label = action.label;
|
||||||
|
return draft;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
),
|
||||||
|
filter: createReducer<Store['filter'], 'setFilter'>('all', (draft, action) => {
|
||||||
|
return action.filter as FilterTypes;
|
||||||
|
})
|
||||||
|
});
|
||||||
14
docs/playground/src/redux-utils/action.ts
Normal file
14
docs/playground/src/redux-utils/action.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { Action } from 'redux';
|
||||||
|
|
||||||
|
type ActionWithPayload<T, P> = Action<T> & P;
|
||||||
|
|
||||||
|
export function action<T extends string>(type: T): Action<T>;
|
||||||
|
export function action<T extends string, P>(type: T, payload: P): ActionWithPayload<T, P>;
|
||||||
|
export function action<T extends string, P>(type: T, payload?: P) {
|
||||||
|
return { type, ...payload };
|
||||||
|
}
|
||||||
|
|
||||||
|
export type GenericActionMapping<A> = { [somekey in keyof A]: (...args: any) => Action<any> | ActionWithPayload<any, any> };
|
||||||
|
export type GenericActionTypes<A extends GenericActionMapping<A>> = ReturnType<A[keyof A]>['type'];
|
||||||
|
export type GenericAction<A extends GenericActionMapping<A>> = ReturnType<A[GenericActionTypes<A>]>;
|
||||||
|
export type GenericActionLookup<A extends GenericActionMapping<A>> = { [a in GenericActionTypes<A>]: ReturnType<A[a]> };
|
||||||
34
docs/playground/src/redux-utils/reducer.ts
Normal file
34
docs/playground/src/redux-utils/reducer.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import { Reducer } from 'redux';
|
||||||
|
import { Draft, produce } from 'immer';
|
||||||
|
import { GenericActionLookup, GenericActionMapping } from './action';
|
||||||
|
|
||||||
|
export type ImmerReducer<T, A> = (state: Draft<T>, action?: A) => T;
|
||||||
|
export type HandlerMap<T, A extends GenericActionMapping<A>> = {
|
||||||
|
[actionType in keyof A]?: ImmerReducer<T, GenericActionLookup<A>[actionType]>
|
||||||
|
};
|
||||||
|
|
||||||
|
function isHandlerFunction<T, A extends GenericActionMapping<A>>(
|
||||||
|
handlerOrMap: HandlerMap<T, A> | ImmerReducer<T, A>
|
||||||
|
): handlerOrMap is ImmerReducer<T, A> {
|
||||||
|
if (typeof handlerOrMap === 'function') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createGenericReducer<T, A extends GenericActionMapping<A>, AM = keyof GenericActionMapping<A>>(
|
||||||
|
initialState: T,
|
||||||
|
handlerOrMap: HandlerMap<T, A> | ImmerReducer<T, GenericActionLookup<A>[AM]>
|
||||||
|
): Reducer<T> {
|
||||||
|
return function reducer(state = initialState, action: GenericActionLookup<A>[AM]): T {
|
||||||
|
if (isHandlerFunction(handlerOrMap)) {
|
||||||
|
return produce(state, draft => handlerOrMap(draft, action as GenericActionLookup<A>[AM]));
|
||||||
|
} else if (handlerOrMap.hasOwnProperty(action.type)) {
|
||||||
|
const handler = (handlerOrMap as any)[action.type] as ImmerReducer<T, GenericActionLookup<A>[AM]>;
|
||||||
|
return produce(state, draft => handler(draft, action));
|
||||||
|
} else {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
48
docs/playground/src/service/todosService.ts
Normal file
48
docs/playground/src/service/todosService.ts
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import { TodoItem, Store } from '../store';
|
||||||
|
const HOST = 'http://localhost:3000';
|
||||||
|
|
||||||
|
export async function add(id: string, todo: TodoItem) {
|
||||||
|
const response = await fetch(`${HOST}/todos/${id}`, {
|
||||||
|
method: 'post',
|
||||||
|
headers: { 'content-type': 'application/json' },
|
||||||
|
body: JSON.stringify(todo)
|
||||||
|
});
|
||||||
|
|
||||||
|
return await response.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function edit(id: string, todo: TodoItem) {
|
||||||
|
const response = await fetch(`${HOST}/todos/${id}`, {
|
||||||
|
method: 'put',
|
||||||
|
headers: { 'content-type': 'application/json' },
|
||||||
|
body: JSON.stringify(todo)
|
||||||
|
});
|
||||||
|
|
||||||
|
return await response.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function remove(id: string) {
|
||||||
|
const response = await fetch(`${HOST}/todos/${id}`, {
|
||||||
|
method: 'delete'
|
||||||
|
});
|
||||||
|
|
||||||
|
return await response.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getAll() {
|
||||||
|
const response = await fetch(`${HOST}/todos`, {
|
||||||
|
method: 'get'
|
||||||
|
});
|
||||||
|
|
||||||
|
return await response.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function editBulk(todos: Store['todos']) {
|
||||||
|
const response = await fetch(`${HOST}/todos`, {
|
||||||
|
method: 'post',
|
||||||
|
headers: { 'content-type': 'application/json' },
|
||||||
|
body: JSON.stringify(todos)
|
||||||
|
});
|
||||||
|
|
||||||
|
return await response.json();
|
||||||
|
}
|
||||||
14
docs/playground/src/store/index.ts
Normal file
14
docs/playground/src/store/index.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
export type FilterTypes = 'all' | 'active' | 'completed';
|
||||||
|
|
||||||
|
export interface TodoItem {
|
||||||
|
label: string;
|
||||||
|
completed: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Store {
|
||||||
|
todos: {
|
||||||
|
[id: string]: TodoItem;
|
||||||
|
};
|
||||||
|
|
||||||
|
filter: FilterTypes;
|
||||||
|
}
|
||||||
8
docs/step1-04/index.html
Normal file
8
docs/step1-04/index.html
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="text/javascript" src="../step1-04/step1-04.js"></script></body>
|
||||||
|
</html>
|
||||||
|
|
||||||
|
|
||||||
13
docs/step1-04/src/App.tsx
Normal file
13
docs/step1-04/src/App.tsx
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Counter } from './components/Counter';
|
||||||
|
|
||||||
|
export class App extends React.Component {
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h2>My App</h2>
|
||||||
|
<Counter start={2} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
14
docs/step1-04/src/components/Button.css
Normal file
14
docs/step1-04/src/components/Button.css
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
.Button {
|
||||||
|
background: #0078d4;
|
||||||
|
color: white;
|
||||||
|
padding: 5px 10px;
|
||||||
|
outline: none;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
.Button:hover {
|
||||||
|
background: #005a9e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Button:active {
|
||||||
|
background: #004578;
|
||||||
|
}
|
||||||
10
docs/step1-04/src/components/Button.tsx
Normal file
10
docs/step1-04/src/components/Button.tsx
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import './Button.css';
|
||||||
|
|
||||||
|
export const Button = props => {
|
||||||
|
return (
|
||||||
|
<button className="Button" onClick={props.onClick}>
|
||||||
|
{props.children}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
};
|
||||||
25
docs/step1-04/src/components/Counter.tsx
Normal file
25
docs/step1-04/src/components/Counter.tsx
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Button } from './Button';
|
||||||
|
|
||||||
|
export class Counter extends React.Component<any, any> {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
counter: props.start
|
||||||
|
};
|
||||||
|
}
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<span> {this.state.counter} </span>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
this.setState({ counter: this.state.counter + 1 });
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Click Me
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
4
docs/step1-04/src/index.tsx
Normal file
4
docs/step1-04/src/index.tsx
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
import React from "react";
|
||||||
|
import ReactDOM from "react-dom";
|
||||||
|
import { App } from "./App";
|
||||||
|
ReactDOM.render(<App />, document.getElementById("app"));
|
||||||
336
docs/step1-04/step1-04.js
Normal file
336
docs/step1-04/step1-04.js
Normal file
File diff suppressed because one or more lines are too long
9
docs/step1-05/index.html
Normal file
9
docs/step1-05/index.html
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<link rel="stylesheet" href="./src/style.css" />
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="text/javascript" src="../step1-05/step1-05.js"></script></body>
|
||||||
|
</html>
|
||||||
|
|
||||||
|
|
||||||
18
docs/step1-05/src/App.tsx
Normal file
18
docs/step1-05/src/App.tsx
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { TodoFooter } from './components/TodoFooter';
|
||||||
|
import { TodoHeader } from './components/TodoHeader';
|
||||||
|
import { TodoList } from './components/TodoList';
|
||||||
|
|
||||||
|
|
||||||
|
export class TodoApp extends React.Component {
|
||||||
|
render() {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<TodoHeader />
|
||||||
|
<TodoList />
|
||||||
|
<TodoFooter />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
13
docs/step1-05/src/components/TodoFooter.tsx
Normal file
13
docs/step1-05/src/components/TodoFooter.tsx
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
export const TodoFooter = (props: any) => {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<footer>
|
||||||
|
<span>
|
||||||
|
<span className="remaining">4</span> items left
|
||||||
|
</span>
|
||||||
|
<button className="button">Clear Completed</button>
|
||||||
|
</footer>
|
||||||
|
);
|
||||||
|
};
|
||||||
18
docs/step1-05/src/components/TodoHeader.tsx
Normal file
18
docs/step1-05/src/components/TodoHeader.tsx
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export class TodoHeader extends React.Component {
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h1>todos</h1>
|
||||||
|
<input className="textfield" placeholder="add todo" />
|
||||||
|
<button className="button add">Add</button>
|
||||||
|
<div className="filter">
|
||||||
|
<button className="active">all</button>
|
||||||
|
<button>active</button>
|
||||||
|
<button>completed</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
17
docs/step1-05/src/components/TodoList.tsx
Normal file
17
docs/step1-05/src/components/TodoList.tsx
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { TodoListItem } from './TodoListItem';
|
||||||
|
|
||||||
|
export class TodoList extends React.Component<any, any> {
|
||||||
|
render() {
|
||||||
|
const { filter, todos } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ul className="todos">
|
||||||
|
<TodoListItem/>
|
||||||
|
<TodoListItem/>
|
||||||
|
<TodoListItem/>
|
||||||
|
<TodoListItem/>
|
||||||
|
</ul>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
13
docs/step1-05/src/components/TodoListItem.tsx
Normal file
13
docs/step1-05/src/components/TodoListItem.tsx
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
export class TodoListItem extends React.Component {
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<li className="todo">
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" /> Todo 1
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
4
docs/step1-05/src/index.tsx
Normal file
4
docs/step1-05/src/index.tsx
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
import React from "react";
|
||||||
|
import ReactDOM from "react-dom";
|
||||||
|
import { TodoApp } from "./App";
|
||||||
|
ReactDOM.render(<TodoApp />, document.getElementById("app"));
|
||||||
50
docs/step1-05/src/style.css
Normal file
50
docs/step1-05/src/style.css
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
body {
|
||||||
|
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
|
||||||
|
width: 400px;
|
||||||
|
margin: 20px auto;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.textfield {
|
||||||
|
width: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add {
|
||||||
|
margin-left: 5%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
border: none;
|
||||||
|
padding: 5px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter {
|
||||||
|
margin: 10px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter button {
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
.filter .active {
|
||||||
|
border-bottom: 2px solid blue;
|
||||||
|
}
|
||||||
|
|
||||||
|
.todos {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer span {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
304
docs/step1-05/step1-05.js
Normal file
304
docs/step1-05/step1-05.js
Normal file
File diff suppressed because one or more lines are too long
9
docs/step1-06/index.html
Normal file
9
docs/step1-06/index.html
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<link rel="stylesheet" href="./src/style.css" />
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="text/javascript" src="../step1-06/step1-06.js"></script></body>
|
||||||
|
</html>
|
||||||
|
|
||||||
|
|
||||||
41
docs/step1-06/src/TodoApp.tsx
Normal file
41
docs/step1-06/src/TodoApp.tsx
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { TodoFooter } from './components/TodoFooter';
|
||||||
|
import { TodoHeader } from './components/TodoHeader';
|
||||||
|
import { TodoList } from './components/TodoList';
|
||||||
|
|
||||||
|
export class TodoApp extends React.Component<any, any> {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
todos: {
|
||||||
|
'04': {
|
||||||
|
label: 'Todo 4',
|
||||||
|
completed: true
|
||||||
|
},
|
||||||
|
'03': {
|
||||||
|
label: 'Todo 3',
|
||||||
|
completed: false
|
||||||
|
},
|
||||||
|
'02': {
|
||||||
|
label: 'Todo 2',
|
||||||
|
completed: false
|
||||||
|
},
|
||||||
|
'01': {
|
||||||
|
label: 'Todo 1',
|
||||||
|
completed: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
filter: 'all'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
render() {
|
||||||
|
const { filter, todos } = this.state;
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<TodoHeader filter={filter} />
|
||||||
|
<TodoList todos={todos} filter={filter} />
|
||||||
|
<TodoFooter todos={todos} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
13
docs/step1-06/src/components/TodoFooter.tsx
Normal file
13
docs/step1-06/src/components/TodoFooter.tsx
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export const TodoFooter = (props: any) => {
|
||||||
|
const itemCount = Object.keys(props.todos).filter(id => !props.todos[id].completed).length;
|
||||||
|
return (
|
||||||
|
<footer>
|
||||||
|
<span>
|
||||||
|
{itemCount} item{itemCount > 1 ? 's' : ''} left
|
||||||
|
</span>
|
||||||
|
<button className="button">Clear Completed</button>
|
||||||
|
</footer>
|
||||||
|
);
|
||||||
|
};
|
||||||
19
docs/step1-06/src/components/TodoHeader.tsx
Normal file
19
docs/step1-06/src/components/TodoHeader.tsx
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export class TodoHeader extends React.Component<any, any> {
|
||||||
|
render() {
|
||||||
|
const { filter } = this.props;
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h1>todos</h1>
|
||||||
|
<input className="textfield" placeholder="add todo" />
|
||||||
|
<button className="button add">Add</button>
|
||||||
|
<div className="filter">
|
||||||
|
<button className={filter == 'all' ? 'active' : ''}>all</button>
|
||||||
|
<button className={filter == 'active' ? 'active' : ''}>active</button>
|
||||||
|
<button className={filter == 'completed' ? 'active' : ''}>completed</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
19
docs/step1-06/src/components/TodoList.tsx
Normal file
19
docs/step1-06/src/components/TodoList.tsx
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { TodoListItem } from './TodoListItem';
|
||||||
|
|
||||||
|
export class TodoList extends React.Component<any, any> {
|
||||||
|
render() {
|
||||||
|
const { filter, todos } = this.props;
|
||||||
|
|
||||||
|
const filteredTodos = Object.keys(todos).filter(id => {
|
||||||
|
return filter === 'all' || (filter === 'completed' && todos[id].completed) || (filter === 'active' && !todos[id].completed);
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<ul className="todos">
|
||||||
|
{filteredTodos.map(id => (
|
||||||
|
<TodoListItem key={id} {...todos[id]} />
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
14
docs/step1-06/src/components/TodoListItem.tsx
Normal file
14
docs/step1-06/src/components/TodoListItem.tsx
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export class TodoListItem extends React.Component<any, any> {
|
||||||
|
render() {
|
||||||
|
const { label, completed } = this.props;
|
||||||
|
return (
|
||||||
|
<li className="todo">
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" checked={completed} /> {label}
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
4
docs/step1-06/src/index.tsx
Normal file
4
docs/step1-06/src/index.tsx
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
|
import { TodoApp } from './TodoApp';
|
||||||
|
ReactDOM.render(<TodoApp />, document.getElementById('app'));
|
||||||
50
docs/step1-06/src/style.css
Normal file
50
docs/step1-06/src/style.css
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
body {
|
||||||
|
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
|
||||||
|
width: 400px;
|
||||||
|
margin: 20px auto;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.textfield {
|
||||||
|
width: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add {
|
||||||
|
margin-left: 5%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
border: none;
|
||||||
|
padding: 5px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter {
|
||||||
|
margin: 10px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter button {
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
.filter .active {
|
||||||
|
border-bottom: 2px solid blue;
|
||||||
|
}
|
||||||
|
|
||||||
|
.todos {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer span {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
304
docs/step1-06/step1-06.js
Normal file
304
docs/step1-06/step1-06.js
Normal file
File diff suppressed because one or more lines are too long
9
docs/step1-07/index.html
Normal file
9
docs/step1-07/index.html
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<link rel="stylesheet" href="./src/style.css" />
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="text/javascript" src="../step1-07/step1-07.js"></script></body>
|
||||||
|
</html>
|
||||||
|
|
||||||
|
|
||||||
84
docs/step1-07/src/TodoApp.tsx
Normal file
84
docs/step1-07/src/TodoApp.tsx
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { TodoFooter } from './components/TodoFooter';
|
||||||
|
import { TodoHeader } from './components/TodoHeader';
|
||||||
|
import { TodoList } from './components/TodoList';
|
||||||
|
|
||||||
|
let index = 0;
|
||||||
|
|
||||||
|
export class TodoApp extends React.Component<any, any> {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
todos: {},
|
||||||
|
filter: 'all'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { filter, todos } = this.state;
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<TodoHeader addTodo={this._addTodo} setFilter={this._setFilter} filter={filter} />
|
||||||
|
<TodoList complete={this._complete} todos={todos} filter={filter} />
|
||||||
|
<TodoFooter clear={this._clear} todos={todos} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _addTodo = label => {
|
||||||
|
const { todos } = this.state;
|
||||||
|
const id = index++;
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
todos: { ...todos, [id]: { label, completed: false } }
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
private _remove = id => {
|
||||||
|
const newTodos = { ...this.state.todos };
|
||||||
|
delete newTodos[id];
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
todos: newTodos
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
private _complete = id => {
|
||||||
|
const newTodos = { ...this.state.todos };
|
||||||
|
newTodos[id].completed = !newTodos[id].completed;
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
todos: newTodos
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
private _edit = (id, label) => {
|
||||||
|
const newTodos = { ...this.state.todos };
|
||||||
|
newTodos[id] = { ...newTodos[id], label };
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
todos: newTodos
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
private _clear = () => {
|
||||||
|
const { todos } = this.state;
|
||||||
|
const newTodos = {};
|
||||||
|
|
||||||
|
Object.keys(this.state.todos).forEach(id => {
|
||||||
|
if (!todos[id].completed) {
|
||||||
|
newTodos[id] = todos[id];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
todos: newTodos
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
private _setFilter = filter => {
|
||||||
|
this.setState({
|
||||||
|
filter: filter
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
10
docs/step1-07/src/TodoApp.types.ts
Normal file
10
docs/step1-07/src/TodoApp.types.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
export type FilterTypes = 'all' | 'active' | 'completed';
|
||||||
|
|
||||||
|
export interface TodoItem {
|
||||||
|
label: string;
|
||||||
|
completed: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Todos {
|
||||||
|
[id: string]: TodoItem;
|
||||||
|
}
|
||||||
20
docs/step1-07/src/components/TodoFooter.tsx
Normal file
20
docs/step1-07/src/components/TodoFooter.tsx
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Todos } from '../TodoApp.types';
|
||||||
|
interface TodoFooterProps {
|
||||||
|
clear: () => void;
|
||||||
|
todos: Todos;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TodoFooter = (props: TodoFooterProps) => {
|
||||||
|
const itemCount = Object.keys(props.todos).filter(id => !props.todos[id].completed).length;
|
||||||
|
return (
|
||||||
|
<footer>
|
||||||
|
<span>
|
||||||
|
{itemCount} item{itemCount > 1 ? 's' : ''} left
|
||||||
|
</span>
|
||||||
|
<button onClick={() => props.clear()} className="button">
|
||||||
|
Clear Completed
|
||||||
|
</button>
|
||||||
|
</footer>
|
||||||
|
);
|
||||||
|
};
|
||||||
48
docs/step1-07/src/components/TodoHeader.tsx
Normal file
48
docs/step1-07/src/components/TodoHeader.tsx
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { FilterTypes } from '../TodoApp.types';
|
||||||
|
|
||||||
|
interface TodoHeaderProps {
|
||||||
|
addTodo: (label: string) => void;
|
||||||
|
setFilter: (filter: FilterTypes) => void;
|
||||||
|
filter: FilterTypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TodoHeader extends React.Component<TodoHeaderProps, any> {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = { labelInput: '' };
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { filter, setFilter } = this.props;
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h1>todos</h1>
|
||||||
|
<input value={this.state.labelInput} onChange={this._onChange} className="textfield" placeholder="add todo" />
|
||||||
|
<button onClick={this._onAdd} className="button add">
|
||||||
|
Add
|
||||||
|
</button>
|
||||||
|
<div className="filter">
|
||||||
|
<button onClick={() => setFilter('all')} className={filter == 'all' ? 'active' : ''}>
|
||||||
|
all
|
||||||
|
</button>
|
||||||
|
<button onClick={() => setFilter('active')} className={filter == 'active' ? 'active' : ''}>
|
||||||
|
active
|
||||||
|
</button>
|
||||||
|
<button onClick={() => setFilter('completed')} className={filter == 'completed' ? 'active' : ''}>
|
||||||
|
completed
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
_onChange = evt => {
|
||||||
|
this.setState({ labelInput: evt.target.value });
|
||||||
|
};
|
||||||
|
|
||||||
|
_onAdd = () => {
|
||||||
|
this.props.addTodo(this.state.labelInput);
|
||||||
|
this.setState({ labelInput: '' });
|
||||||
|
};
|
||||||
|
}
|
||||||
27
docs/step1-07/src/components/TodoList.tsx
Normal file
27
docs/step1-07/src/components/TodoList.tsx
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { TodoListItem } from './TodoListItem';
|
||||||
|
import { FilterTypes, Todos } from '../TodoApp.types';
|
||||||
|
|
||||||
|
interface TodoListProps {
|
||||||
|
complete: (id: string) => void;
|
||||||
|
todos: Todos;
|
||||||
|
filter: FilterTypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TodoList extends React.Component<TodoListProps, any> {
|
||||||
|
render() {
|
||||||
|
const { filter, todos, complete } = this.props;
|
||||||
|
|
||||||
|
const filteredTodos = Object.keys(todos).filter(id => {
|
||||||
|
return filter === 'all' || (filter === 'completed' && todos[id].completed) || (filter === 'active' && !todos[id].completed);
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ul className="todos">
|
||||||
|
{filteredTodos.map(id => (
|
||||||
|
<TodoListItem key={id} id={id} complete={complete} {...todos[id]} />
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
21
docs/step1-07/src/components/TodoListItem.tsx
Normal file
21
docs/step1-07/src/components/TodoListItem.tsx
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { TodoItem } from '../TodoApp.types';
|
||||||
|
|
||||||
|
interface TodoListItemProps extends TodoItem {
|
||||||
|
id: string;
|
||||||
|
complete: (id: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TodoListItem extends React.Component<TodoListItemProps, any> {
|
||||||
|
render() {
|
||||||
|
const { label, completed, complete, id } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<li className="todo">
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" checked={completed} onChange={() => complete(id)} /> {label}
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
4
docs/step1-07/src/index.tsx
Normal file
4
docs/step1-07/src/index.tsx
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
|
import { TodoApp } from './TodoApp';
|
||||||
|
ReactDOM.render(<TodoApp />, document.getElementById('app'));
|
||||||
50
docs/step1-07/src/style.css
Normal file
50
docs/step1-07/src/style.css
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
body {
|
||||||
|
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
|
||||||
|
width: 400px;
|
||||||
|
margin: 20px auto;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.textfield {
|
||||||
|
width: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add {
|
||||||
|
margin-left: 5%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
border: none;
|
||||||
|
padding: 5px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter {
|
||||||
|
margin: 10px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter button {
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
.filter .active {
|
||||||
|
border-bottom: 2px solid blue;
|
||||||
|
}
|
||||||
|
|
||||||
|
.todos {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer span {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
304
docs/step1-07/step1-07.js
Normal file
304
docs/step1-07/step1-07.js
Normal file
File diff suppressed because one or more lines are too long
6
docs/step2-01/demo/index.html
Normal file
6
docs/step2-01/demo/index.html
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="text/javascript" src="../../step2-01/demo/step2-01/demo.js"></script></body>
|
||||||
|
</html>
|
||||||
12
docs/step2-01/demo/src/async/index.ts
Normal file
12
docs/step2-01/demo/src/async/index.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
async function fetchSomething() {
|
||||||
|
const response = await fetch('http://localhost:3000/hello');
|
||||||
|
return await response.text();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Async functions always returns Promise
|
||||||
|
fetchSomething().then(text => {
|
||||||
|
console.log('hello ' + text);
|
||||||
|
});
|
||||||
|
|
||||||
|
// adding an export to turn this into a "module"
|
||||||
|
export default {};
|
||||||
23
docs/step2-01/demo/src/generics/index.ts
Normal file
23
docs/step2-01/demo/src/generics/index.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
// Generics for classes
|
||||||
|
class Stack<T = number> {
|
||||||
|
private data: T[] = [];
|
||||||
|
|
||||||
|
push(item: T) {
|
||||||
|
this.data.push(item);
|
||||||
|
}
|
||||||
|
pop(): T {
|
||||||
|
return this.data.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const numberStack = new Stack();
|
||||||
|
const stringStack = new Stack<string>();
|
||||||
|
|
||||||
|
// Generics for functions
|
||||||
|
function reverse<T>(arg: T[]): T[] {
|
||||||
|
// TODO: implement the logic to reverse the array
|
||||||
|
return arg;
|
||||||
|
}
|
||||||
|
|
||||||
|
// adding an export to turn this into a "module"
|
||||||
|
export default {};
|
||||||
19
docs/step2-01/demo/src/index.tsx
Normal file
19
docs/step2-01/demo/src/index.tsx
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
// Interesting Typescript Topics
|
||||||
|
|
||||||
|
// types
|
||||||
|
import './types';
|
||||||
|
|
||||||
|
// interface
|
||||||
|
import './interfaces';
|
||||||
|
|
||||||
|
// modularity
|
||||||
|
import './modules';
|
||||||
|
|
||||||
|
// generics
|
||||||
|
import './generics';
|
||||||
|
|
||||||
|
// await / async
|
||||||
|
import './async';
|
||||||
|
|
||||||
|
// spread syntax
|
||||||
|
import './spread';
|
||||||
22
docs/step2-01/demo/src/interfaces/index.ts
Normal file
22
docs/step2-01/demo/src/interfaces/index.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
interface Car {
|
||||||
|
make: string;
|
||||||
|
model: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
class MyCar implements Car {
|
||||||
|
make: 'Honda';
|
||||||
|
model: 'Accord';
|
||||||
|
}
|
||||||
|
|
||||||
|
const myCar: Car = {
|
||||||
|
make: 'Honda',
|
||||||
|
model: 'Accord'
|
||||||
|
};
|
||||||
|
|
||||||
|
// Interface as Functions
|
||||||
|
interface InterestingFn {
|
||||||
|
(someArgs: string): number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// adding an export to turn this into a "module"
|
||||||
|
export default {};
|
||||||
3
docs/step2-01/demo/src/modules/default.ts
Normal file
3
docs/step2-01/demo/src/modules/default.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export default class DefaultClass {
|
||||||
|
hello = 'world';
|
||||||
|
}
|
||||||
19
docs/step2-01/demo/src/modules/index.ts
Normal file
19
docs/step2-01/demo/src/modules/index.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { namedConst, namedFn, namedObj, namedConstBracket, namedConst as c } from './named';
|
||||||
|
import * as named from './named';
|
||||||
|
|
||||||
|
// Print out the exports
|
||||||
|
console.log(namedConst);
|
||||||
|
console.log(c);
|
||||||
|
console.log(namedFn());
|
||||||
|
console.log(namedObj);
|
||||||
|
console.log(namedConstBracket);
|
||||||
|
|
||||||
|
// Print out exports through module level import
|
||||||
|
console.log(named.namedConst);
|
||||||
|
console.log(named.namedFn());
|
||||||
|
console.log(named.namedObj);
|
||||||
|
console.log(named.namedConstBracket);
|
||||||
|
|
||||||
|
import DefaultClass from './default';
|
||||||
|
|
||||||
|
console.log(new DefaultClass().hello);
|
||||||
12
docs/step2-01/demo/src/modules/named.ts
Normal file
12
docs/step2-01/demo/src/modules/named.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
export const namedConst = 5;
|
||||||
|
|
||||||
|
export function namedFn() {
|
||||||
|
return 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const namedObj = {
|
||||||
|
hello: 'world'
|
||||||
|
};
|
||||||
|
|
||||||
|
const namedConstBracket = 10;
|
||||||
|
export { namedConstBracket };
|
||||||
24
docs/step2-01/demo/src/spread/index.ts
Normal file
24
docs/step2-01/demo/src/spread/index.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
// Destructuring
|
||||||
|
var [a, b, ...rest] = [1, 2, 3, 4];
|
||||||
|
console.log(a, b, rest); // 1,2,[3,4]
|
||||||
|
|
||||||
|
// Array assignment
|
||||||
|
var list = [1, 2];
|
||||||
|
list = [...list, 3, 4];
|
||||||
|
console.log(list); // [1,2,3,4]
|
||||||
|
|
||||||
|
// Object assignment
|
||||||
|
const point2D = { x: 1, y: 2 };
|
||||||
|
const point3D = { ...point2D, z: 3 };
|
||||||
|
|
||||||
|
// Concat two objects
|
||||||
|
const obj1 = { x: 1 };
|
||||||
|
const obj2 = { y: 2 };
|
||||||
|
|
||||||
|
const obj3 = { ...obj1, ...obj2 };
|
||||||
|
|
||||||
|
// Destructuring object
|
||||||
|
const { x } = obj3;
|
||||||
|
|
||||||
|
// adding an export to turn this into a "module"
|
||||||
|
export default {};
|
||||||
59
docs/step2-01/demo/src/types/index.ts
Normal file
59
docs/step2-01/demo/src/types/index.ts
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
// Basic Types
|
||||||
|
let isDone: boolean = false;
|
||||||
|
let decimal: number = 6;
|
||||||
|
let color: string = 'blue';
|
||||||
|
let sky: string = `the sky is ${color}`;
|
||||||
|
|
||||||
|
// Function Types
|
||||||
|
type FibFn = (n: number) => number;
|
||||||
|
|
||||||
|
// Object Types
|
||||||
|
type Obj = {
|
||||||
|
[key: string]: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Object with Specified Keys
|
||||||
|
type Specific1 = {
|
||||||
|
foo: string;
|
||||||
|
bar: number;
|
||||||
|
common: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Specific2 = {
|
||||||
|
alice: string;
|
||||||
|
bob: number;
|
||||||
|
common: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
// composition
|
||||||
|
type TypeOfObj = {
|
||||||
|
foo: string;
|
||||||
|
bar: number;
|
||||||
|
obj1: Specific1;
|
||||||
|
obj2: Specific2;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get types by property
|
||||||
|
type Obj1Type = TypeOfObj['obj1'];
|
||||||
|
|
||||||
|
// union, intersection
|
||||||
|
type Union = Specific1 | Specific2;
|
||||||
|
type Intersection = Specific1 & Specific2;
|
||||||
|
|
||||||
|
// casting
|
||||||
|
let choose1 = <Specific1>{ common: '5' };
|
||||||
|
|
||||||
|
// string literal union
|
||||||
|
type CatStatus = 'alive' | 'dead' | 'both';
|
||||||
|
|
||||||
|
// Classes
|
||||||
|
class Animal {}
|
||||||
|
|
||||||
|
// Illustration purposes only
|
||||||
|
// In real apps, avoid inheritance if possible
|
||||||
|
// noted exception: React.Component with react@<16.8.0
|
||||||
|
class Cat extends Animal {}
|
||||||
|
class Dog extends Animal {}
|
||||||
|
|
||||||
|
// adding an export to turn this into a "module"
|
||||||
|
export default {};
|
||||||
197
docs/step2-01/demo/step2-01/demo.js
Normal file
197
docs/step2-01/demo/step2-01/demo.js
Normal file
@@ -0,0 +1,197 @@
|
|||||||
|
/******/ (function(modules) { // webpackBootstrap
|
||||||
|
/******/ // The module cache
|
||||||
|
/******/ var installedModules = {};
|
||||||
|
/******/
|
||||||
|
/******/ // The require function
|
||||||
|
/******/ function __webpack_require__(moduleId) {
|
||||||
|
/******/
|
||||||
|
/******/ // Check if module is in cache
|
||||||
|
/******/ if(installedModules[moduleId]) {
|
||||||
|
/******/ return installedModules[moduleId].exports;
|
||||||
|
/******/ }
|
||||||
|
/******/ // Create a new module (and put it into the cache)
|
||||||
|
/******/ var module = installedModules[moduleId] = {
|
||||||
|
/******/ i: moduleId,
|
||||||
|
/******/ l: false,
|
||||||
|
/******/ exports: {}
|
||||||
|
/******/ };
|
||||||
|
/******/
|
||||||
|
/******/ // Execute the module function
|
||||||
|
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
|
||||||
|
/******/
|
||||||
|
/******/ // Flag the module as loaded
|
||||||
|
/******/ module.l = true;
|
||||||
|
/******/
|
||||||
|
/******/ // Return the exports of the module
|
||||||
|
/******/ return module.exports;
|
||||||
|
/******/ }
|
||||||
|
/******/
|
||||||
|
/******/
|
||||||
|
/******/ // expose the modules object (__webpack_modules__)
|
||||||
|
/******/ __webpack_require__.m = modules;
|
||||||
|
/******/
|
||||||
|
/******/ // expose the module cache
|
||||||
|
/******/ __webpack_require__.c = installedModules;
|
||||||
|
/******/
|
||||||
|
/******/ // define getter function for harmony exports
|
||||||
|
/******/ __webpack_require__.d = function(exports, name, getter) {
|
||||||
|
/******/ if(!__webpack_require__.o(exports, name)) {
|
||||||
|
/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter });
|
||||||
|
/******/ }
|
||||||
|
/******/ };
|
||||||
|
/******/
|
||||||
|
/******/ // define __esModule on exports
|
||||||
|
/******/ __webpack_require__.r = function(exports) {
|
||||||
|
/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
|
||||||
|
/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
||||||
|
/******/ }
|
||||||
|
/******/ Object.defineProperty(exports, '__esModule', { value: true });
|
||||||
|
/******/ };
|
||||||
|
/******/
|
||||||
|
/******/ // create a fake namespace object
|
||||||
|
/******/ // mode & 1: value is a module id, require it
|
||||||
|
/******/ // mode & 2: merge all properties of value into the ns
|
||||||
|
/******/ // mode & 4: return value when already ns object
|
||||||
|
/******/ // mode & 8|1: behave like require
|
||||||
|
/******/ __webpack_require__.t = function(value, mode) {
|
||||||
|
/******/ if(mode & 1) value = __webpack_require__(value);
|
||||||
|
/******/ if(mode & 8) return value;
|
||||||
|
/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
|
||||||
|
/******/ var ns = Object.create(null);
|
||||||
|
/******/ __webpack_require__.r(ns);
|
||||||
|
/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value });
|
||||||
|
/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
|
||||||
|
/******/ return ns;
|
||||||
|
/******/ };
|
||||||
|
/******/
|
||||||
|
/******/ // getDefaultExport function for compatibility with non-harmony modules
|
||||||
|
/******/ __webpack_require__.n = function(module) {
|
||||||
|
/******/ var getter = module && module.__esModule ?
|
||||||
|
/******/ function getDefault() { return module['default']; } :
|
||||||
|
/******/ function getModuleExports() { return module; };
|
||||||
|
/******/ __webpack_require__.d(getter, 'a', getter);
|
||||||
|
/******/ return getter;
|
||||||
|
/******/ };
|
||||||
|
/******/
|
||||||
|
/******/ // Object.prototype.hasOwnProperty.call
|
||||||
|
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
|
||||||
|
/******/
|
||||||
|
/******/ // __webpack_public_path__
|
||||||
|
/******/ __webpack_require__.p = "";
|
||||||
|
/******/
|
||||||
|
/******/
|
||||||
|
/******/ // Load entry module and return exports
|
||||||
|
/******/ return __webpack_require__(__webpack_require__.s = "./step2-01/demo/src/index.tsx");
|
||||||
|
/******/ })
|
||||||
|
/************************************************************************/
|
||||||
|
/******/ ({
|
||||||
|
|
||||||
|
/***/ "./step2-01/demo/src/async/index.ts":
|
||||||
|
/*!******************************************!*\
|
||||||
|
!*** ./step2-01/demo/src/async/index.ts ***!
|
||||||
|
\******************************************/
|
||||||
|
/*! exports provided: default */
|
||||||
|
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
eval("__webpack_require__.r(__webpack_exports__);\nvar __awaiter = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {\n return new (P || (P = Promise))(function (resolve, reject) {\n function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }\n function rejected(value) { try { step(generator[\"throw\"](value)); } catch (e) { reject(e); } }\n function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }\n step((generator = generator.apply(thisArg, _arguments || [])).next());\n });\n};\nvar __generator = (undefined && undefined.__generator) || function (thisArg, body) {\n var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;\n return g = { next: verb(0), \"throw\": verb(1), \"return\": verb(2) }, typeof Symbol === \"function\" && (g[Symbol.iterator] = function() { return this; }), g;\n function verb(n) { return function (v) { return step([n, v]); }; }\n function step(op) {\n if (f) throw new TypeError(\"Generator is already executing.\");\n while (_) try {\n if (f = 1, y && (t = op[0] & 2 ? y[\"return\"] : op[0] ? y[\"throw\"] || ((t = y[\"return\"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;\n if (y = 0, t) op = [op[0] & 2, t.value];\n switch (op[0]) {\n case 0: case 1: t = op; break;\n case 4: _.label++; return { value: op[1], done: false };\n case 5: _.label++; y = op[1]; op = [0]; continue;\n case 7: op = _.ops.pop(); _.trys.pop(); continue;\n default:\n if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }\n if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }\n if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }\n if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }\n if (t[2]) _.ops.pop();\n _.trys.pop(); continue;\n }\n op = body.call(thisArg, _);\n } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }\n if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };\n }\n};\nfunction fetchSomething() {\n return __awaiter(this, void 0, void 0, function () {\n var response;\n return __generator(this, function (_a) {\n switch (_a.label) {\n case 0: return [4 /*yield*/, fetch('http://localhost:3000/hello')];\n case 1:\n response = _a.sent();\n return [4 /*yield*/, response.text()];\n case 2: return [2 /*return*/, _a.sent()];\n }\n });\n });\n}\n// Async functions always returns Promise\nfetchSomething().then(function (text) {\n console.log('hello ' + text);\n});\n// adding an export to turn this into a \"module\"\n/* harmony default export */ __webpack_exports__[\"default\"] = ({});\n\n\n//# sourceURL=webpack:///./step2-01/demo/src/async/index.ts?");
|
||||||
|
|
||||||
|
/***/ }),
|
||||||
|
|
||||||
|
/***/ "./step2-01/demo/src/generics/index.ts":
|
||||||
|
/*!*********************************************!*\
|
||||||
|
!*** ./step2-01/demo/src/generics/index.ts ***!
|
||||||
|
\*********************************************/
|
||||||
|
/*! exports provided: default */
|
||||||
|
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
eval("__webpack_require__.r(__webpack_exports__);\n// Generics for classes\nvar Stack = /** @class */ (function () {\n function Stack() {\n this.data = [];\n }\n Stack.prototype.push = function (item) {\n this.data.push(item);\n };\n Stack.prototype.pop = function () {\n return this.data.pop();\n };\n return Stack;\n}());\nvar numberStack = new Stack();\nvar stringStack = new Stack();\n// Generics for functions\nfunction reverse(arg) {\n // TODO: implement the logic to reverse the array\n return arg;\n}\n// adding an export to turn this into a \"module\"\n/* harmony default export */ __webpack_exports__[\"default\"] = ({});\n\n\n//# sourceURL=webpack:///./step2-01/demo/src/generics/index.ts?");
|
||||||
|
|
||||||
|
/***/ }),
|
||||||
|
|
||||||
|
/***/ "./step2-01/demo/src/index.tsx":
|
||||||
|
/*!*************************************!*\
|
||||||
|
!*** ./step2-01/demo/src/index.tsx ***!
|
||||||
|
\*************************************/
|
||||||
|
/*! no exports provided */
|
||||||
|
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _types__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./types */ \"./step2-01/demo/src/types/index.ts\");\n/* harmony import */ var _interfaces__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./interfaces */ \"./step2-01/demo/src/interfaces/index.ts\");\n/* harmony import */ var _modules__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./modules */ \"./step2-01/demo/src/modules/index.ts\");\n/* harmony import */ var _generics__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./generics */ \"./step2-01/demo/src/generics/index.ts\");\n/* harmony import */ var _async__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./async */ \"./step2-01/demo/src/async/index.ts\");\n/* harmony import */ var _spread__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./spread */ \"./step2-01/demo/src/spread/index.ts\");\n// Interesting Typescript Topics\n// types\n\n// interface\n\n// modularity\n\n// generics\n\n// await / async\n\n// spread syntax\n\n\n\n//# sourceURL=webpack:///./step2-01/demo/src/index.tsx?");
|
||||||
|
|
||||||
|
/***/ }),
|
||||||
|
|
||||||
|
/***/ "./step2-01/demo/src/interfaces/index.ts":
|
||||||
|
/*!***********************************************!*\
|
||||||
|
!*** ./step2-01/demo/src/interfaces/index.ts ***!
|
||||||
|
\***********************************************/
|
||||||
|
/*! exports provided: default */
|
||||||
|
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
eval("__webpack_require__.r(__webpack_exports__);\nvar MyCar = /** @class */ (function () {\n function MyCar() {\n }\n return MyCar;\n}());\nvar myCar = {\n make: 'Honda',\n model: 'Accord'\n};\n// adding an export to turn this into a \"module\"\n/* harmony default export */ __webpack_exports__[\"default\"] = ({});\n\n\n//# sourceURL=webpack:///./step2-01/demo/src/interfaces/index.ts?");
|
||||||
|
|
||||||
|
/***/ }),
|
||||||
|
|
||||||
|
/***/ "./step2-01/demo/src/modules/default.ts":
|
||||||
|
/*!**********************************************!*\
|
||||||
|
!*** ./step2-01/demo/src/modules/default.ts ***!
|
||||||
|
\**********************************************/
|
||||||
|
/*! exports provided: default */
|
||||||
|
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
eval("__webpack_require__.r(__webpack_exports__);\nvar DefaultClass = /** @class */ (function () {\n function DefaultClass() {\n this.hello = 'world';\n }\n return DefaultClass;\n}());\n/* harmony default export */ __webpack_exports__[\"default\"] = (DefaultClass);\n\n\n//# sourceURL=webpack:///./step2-01/demo/src/modules/default.ts?");
|
||||||
|
|
||||||
|
/***/ }),
|
||||||
|
|
||||||
|
/***/ "./step2-01/demo/src/modules/index.ts":
|
||||||
|
/*!********************************************!*\
|
||||||
|
!*** ./step2-01/demo/src/modules/index.ts ***!
|
||||||
|
\********************************************/
|
||||||
|
/*! no exports provided */
|
||||||
|
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _named__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./named */ \"./step2-01/demo/src/modules/named.ts\");\n/* harmony import */ var _default__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./default */ \"./step2-01/demo/src/modules/default.ts\");\n\n\n// Print out the exports\nconsole.log(_named__WEBPACK_IMPORTED_MODULE_0__[\"namedConst\"]);\nconsole.log(_named__WEBPACK_IMPORTED_MODULE_0__[\"namedConst\"]);\nconsole.log(Object(_named__WEBPACK_IMPORTED_MODULE_0__[\"namedFn\"])());\nconsole.log(_named__WEBPACK_IMPORTED_MODULE_0__[\"namedObj\"]);\nconsole.log(_named__WEBPACK_IMPORTED_MODULE_0__[\"namedConstBracket\"]);\n// Print out exports through module level import\nconsole.log(_named__WEBPACK_IMPORTED_MODULE_0__[\"namedConst\"]);\nconsole.log(_named__WEBPACK_IMPORTED_MODULE_0__[\"namedFn\"]());\nconsole.log(_named__WEBPACK_IMPORTED_MODULE_0__[\"namedObj\"]);\nconsole.log(_named__WEBPACK_IMPORTED_MODULE_0__[\"namedConstBracket\"]);\n\nconsole.log(new _default__WEBPACK_IMPORTED_MODULE_1__[\"default\"]().hello);\n\n\n//# sourceURL=webpack:///./step2-01/demo/src/modules/index.ts?");
|
||||||
|
|
||||||
|
/***/ }),
|
||||||
|
|
||||||
|
/***/ "./step2-01/demo/src/modules/named.ts":
|
||||||
|
/*!********************************************!*\
|
||||||
|
!*** ./step2-01/demo/src/modules/named.ts ***!
|
||||||
|
\********************************************/
|
||||||
|
/*! exports provided: namedConst, namedFn, namedObj, namedConstBracket */
|
||||||
|
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"namedConst\", function() { return namedConst; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"namedFn\", function() { return namedFn; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"namedObj\", function() { return namedObj; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"namedConstBracket\", function() { return namedConstBracket; });\nvar namedConst = 5;\nfunction namedFn() {\n return 5;\n}\nvar namedObj = {\n hello: 'world'\n};\nvar namedConstBracket = 10;\n\n\n\n//# sourceURL=webpack:///./step2-01/demo/src/modules/named.ts?");
|
||||||
|
|
||||||
|
/***/ }),
|
||||||
|
|
||||||
|
/***/ "./step2-01/demo/src/spread/index.ts":
|
||||||
|
/*!*******************************************!*\
|
||||||
|
!*** ./step2-01/demo/src/spread/index.ts ***!
|
||||||
|
\*******************************************/
|
||||||
|
/*! exports provided: default */
|
||||||
|
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
eval("__webpack_require__.r(__webpack_exports__);\nvar __assign = (undefined && undefined.__assign) || function () {\n __assign = Object.assign || function(t) {\n for (var s, i = 1, n = arguments.length; i < n; i++) {\n s = arguments[i];\n for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))\n t[p] = s[p];\n }\n return t;\n };\n return __assign.apply(this, arguments);\n};\n// Destructuring\nvar _a = [1, 2, 3, 4], a = _a[0], b = _a[1], rest = _a.slice(2);\nconsole.log(a, b, rest); // 1,2,[3,4]\n// Array assignment\nvar list = [1, 2];\nlist = list.concat([3, 4]);\nconsole.log(list); // [1,2,3,4]\n// Object assignment\nvar point2D = { x: 1, y: 2 };\nvar point3D = __assign({}, point2D, { z: 3 });\n// Concat two objects\nvar obj1 = { x: 1 };\nvar obj2 = { y: 2 };\nvar obj3 = __assign({}, obj1, obj2);\n// Destructuring object\nvar x = obj3.x;\n// adding an export to turn this into a \"module\"\n/* harmony default export */ __webpack_exports__[\"default\"] = ({});\n\n\n//# sourceURL=webpack:///./step2-01/demo/src/spread/index.ts?");
|
||||||
|
|
||||||
|
/***/ }),
|
||||||
|
|
||||||
|
/***/ "./step2-01/demo/src/types/index.ts":
|
||||||
|
/*!******************************************!*\
|
||||||
|
!*** ./step2-01/demo/src/types/index.ts ***!
|
||||||
|
\******************************************/
|
||||||
|
/*! exports provided: default */
|
||||||
|
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
eval("__webpack_require__.r(__webpack_exports__);\nvar __extends = (undefined && undefined.__extends) || (function () {\n var extendStatics = function (d, b) {\n extendStatics = Object.setPrototypeOf ||\n ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||\n function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };\n return extendStatics(d, b);\n };\n return function (d, b) {\n extendStatics(d, b);\n function __() { this.constructor = d; }\n d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());\n };\n})();\n// Basic Types\nvar isDone = false;\nvar decimal = 6;\nvar color = 'blue';\nvar sky = \"the sky is \" + color;\n// casting\nvar choose1 = { common: '5' };\n// Classes\nvar Animal = /** @class */ (function () {\n function Animal() {\n }\n return Animal;\n}());\n// Illustration purposes only\n// In real apps, avoid inheritance if possible\n// noted exception: React.Component with react@<16.8.0\nvar Cat = /** @class */ (function (_super) {\n __extends(Cat, _super);\n function Cat() {\n return _super !== null && _super.apply(this, arguments) || this;\n }\n return Cat;\n}(Animal));\nvar Dog = /** @class */ (function (_super) {\n __extends(Dog, _super);\n function Dog() {\n return _super !== null && _super.apply(this, arguments) || this;\n }\n return Dog;\n}(Animal));\n// adding an export to turn this into a \"module\"\n/* harmony default export */ __webpack_exports__[\"default\"] = ({});\n\n\n//# sourceURL=webpack:///./step2-01/demo/src/types/index.ts?");
|
||||||
|
|
||||||
|
/***/ })
|
||||||
|
|
||||||
|
/******/ });
|
||||||
20
docs/step2-01/exercise/index.html
Normal file
20
docs/step2-01/exercise/index.html
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
outline: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
width: 100vh;
|
||||||
|
height: 100vh;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="text/javascript" src="../../step2-01/exercise/step2-01/exercise.js"></script></body>
|
||||||
|
</html>
|
||||||
34
docs/step2-01/exercise/src/index.ts
Normal file
34
docs/step2-01/exercise/src/index.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
const app = document.getElementById('app');
|
||||||
|
const textarea = document.createElement('textarea');
|
||||||
|
textarea.setAttribute('readonly', 'true');
|
||||||
|
app.appendChild(textarea);
|
||||||
|
function log(results: string) {
|
||||||
|
textarea.innerText += results;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Some setup code for exercises
|
||||||
|
const obj1 = {
|
||||||
|
first: 'who',
|
||||||
|
second: 'what',
|
||||||
|
third: 'dunno',
|
||||||
|
left: 'why'
|
||||||
|
};
|
||||||
|
|
||||||
|
const obj2 = {
|
||||||
|
center: 'because',
|
||||||
|
pitcher: 'tomorrow',
|
||||||
|
catcher: 'today'
|
||||||
|
};
|
||||||
|
|
||||||
|
function makePromise() {
|
||||||
|
return Promise.resolve(5);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do the exercises here, output your results with "log()" function
|
||||||
|
// ...
|
||||||
|
log('hello world');
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
// Place your code for the async / await exercise here
|
||||||
|
// ...
|
||||||
|
})();
|
||||||
100
docs/step2-01/exercise/step2-01/exercise.js
Normal file
100
docs/step2-01/exercise/step2-01/exercise.js
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
/******/ (function(modules) { // webpackBootstrap
|
||||||
|
/******/ // The module cache
|
||||||
|
/******/ var installedModules = {};
|
||||||
|
/******/
|
||||||
|
/******/ // The require function
|
||||||
|
/******/ function __webpack_require__(moduleId) {
|
||||||
|
/******/
|
||||||
|
/******/ // Check if module is in cache
|
||||||
|
/******/ if(installedModules[moduleId]) {
|
||||||
|
/******/ return installedModules[moduleId].exports;
|
||||||
|
/******/ }
|
||||||
|
/******/ // Create a new module (and put it into the cache)
|
||||||
|
/******/ var module = installedModules[moduleId] = {
|
||||||
|
/******/ i: moduleId,
|
||||||
|
/******/ l: false,
|
||||||
|
/******/ exports: {}
|
||||||
|
/******/ };
|
||||||
|
/******/
|
||||||
|
/******/ // Execute the module function
|
||||||
|
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
|
||||||
|
/******/
|
||||||
|
/******/ // Flag the module as loaded
|
||||||
|
/******/ module.l = true;
|
||||||
|
/******/
|
||||||
|
/******/ // Return the exports of the module
|
||||||
|
/******/ return module.exports;
|
||||||
|
/******/ }
|
||||||
|
/******/
|
||||||
|
/******/
|
||||||
|
/******/ // expose the modules object (__webpack_modules__)
|
||||||
|
/******/ __webpack_require__.m = modules;
|
||||||
|
/******/
|
||||||
|
/******/ // expose the module cache
|
||||||
|
/******/ __webpack_require__.c = installedModules;
|
||||||
|
/******/
|
||||||
|
/******/ // define getter function for harmony exports
|
||||||
|
/******/ __webpack_require__.d = function(exports, name, getter) {
|
||||||
|
/******/ if(!__webpack_require__.o(exports, name)) {
|
||||||
|
/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter });
|
||||||
|
/******/ }
|
||||||
|
/******/ };
|
||||||
|
/******/
|
||||||
|
/******/ // define __esModule on exports
|
||||||
|
/******/ __webpack_require__.r = function(exports) {
|
||||||
|
/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
|
||||||
|
/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
||||||
|
/******/ }
|
||||||
|
/******/ Object.defineProperty(exports, '__esModule', { value: true });
|
||||||
|
/******/ };
|
||||||
|
/******/
|
||||||
|
/******/ // create a fake namespace object
|
||||||
|
/******/ // mode & 1: value is a module id, require it
|
||||||
|
/******/ // mode & 2: merge all properties of value into the ns
|
||||||
|
/******/ // mode & 4: return value when already ns object
|
||||||
|
/******/ // mode & 8|1: behave like require
|
||||||
|
/******/ __webpack_require__.t = function(value, mode) {
|
||||||
|
/******/ if(mode & 1) value = __webpack_require__(value);
|
||||||
|
/******/ if(mode & 8) return value;
|
||||||
|
/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
|
||||||
|
/******/ var ns = Object.create(null);
|
||||||
|
/******/ __webpack_require__.r(ns);
|
||||||
|
/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value });
|
||||||
|
/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
|
||||||
|
/******/ return ns;
|
||||||
|
/******/ };
|
||||||
|
/******/
|
||||||
|
/******/ // getDefaultExport function for compatibility with non-harmony modules
|
||||||
|
/******/ __webpack_require__.n = function(module) {
|
||||||
|
/******/ var getter = module && module.__esModule ?
|
||||||
|
/******/ function getDefault() { return module['default']; } :
|
||||||
|
/******/ function getModuleExports() { return module; };
|
||||||
|
/******/ __webpack_require__.d(getter, 'a', getter);
|
||||||
|
/******/ return getter;
|
||||||
|
/******/ };
|
||||||
|
/******/
|
||||||
|
/******/ // Object.prototype.hasOwnProperty.call
|
||||||
|
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
|
||||||
|
/******/
|
||||||
|
/******/ // __webpack_public_path__
|
||||||
|
/******/ __webpack_require__.p = "";
|
||||||
|
/******/
|
||||||
|
/******/
|
||||||
|
/******/ // Load entry module and return exports
|
||||||
|
/******/ return __webpack_require__(__webpack_require__.s = "./step2-01/exercise/src/index.ts");
|
||||||
|
/******/ })
|
||||||
|
/************************************************************************/
|
||||||
|
/******/ ({
|
||||||
|
|
||||||
|
/***/ "./step2-01/exercise/src/index.ts":
|
||||||
|
/*!****************************************!*\
|
||||||
|
!*** ./step2-01/exercise/src/index.ts ***!
|
||||||
|
\****************************************/
|
||||||
|
/*! no static exports found */
|
||||||
|
/***/ (function(module, exports) {
|
||||||
|
|
||||||
|
eval("var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {\n return new (P || (P = Promise))(function (resolve, reject) {\n function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }\n function rejected(value) { try { step(generator[\"throw\"](value)); } catch (e) { reject(e); } }\n function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }\n step((generator = generator.apply(thisArg, _arguments || [])).next());\n });\n};\nvar __generator = (this && this.__generator) || function (thisArg, body) {\n var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;\n return g = { next: verb(0), \"throw\": verb(1), \"return\": verb(2) }, typeof Symbol === \"function\" && (g[Symbol.iterator] = function() { return this; }), g;\n function verb(n) { return function (v) { return step([n, v]); }; }\n function step(op) {\n if (f) throw new TypeError(\"Generator is already executing.\");\n while (_) try {\n if (f = 1, y && (t = op[0] & 2 ? y[\"return\"] : op[0] ? y[\"throw\"] || ((t = y[\"return\"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;\n if (y = 0, t) op = [op[0] & 2, t.value];\n switch (op[0]) {\n case 0: case 1: t = op; break;\n case 4: _.label++; return { value: op[1], done: false };\n case 5: _.label++; y = op[1]; op = [0]; continue;\n case 7: op = _.ops.pop(); _.trys.pop(); continue;\n default:\n if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }\n if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }\n if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }\n if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }\n if (t[2]) _.ops.pop();\n _.trys.pop(); continue;\n }\n op = body.call(thisArg, _);\n } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }\n if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };\n }\n};\nvar _this = this;\nvar app = document.getElementById('app');\nvar textarea = document.createElement('textarea');\ntextarea.setAttribute('readonly', 'true');\napp.appendChild(textarea);\nfunction log(results) {\n textarea.innerText += results;\n}\n// Some setup code for exercises\nvar obj1 = {\n first: 'who',\n second: 'what',\n third: 'dunno',\n left: 'why'\n};\nvar obj2 = {\n center: 'because',\n pitcher: 'tomorrow',\n catcher: 'today'\n};\nfunction makePromise() {\n return Promise.resolve(5);\n}\n// Do the exercises here, output your results with \"log()\" function\n// ...\nlog('hello world');\n(function () { return __awaiter(_this, void 0, void 0, function () {\n return __generator(this, function (_a) {\n return [2 /*return*/];\n });\n}); })();\n\n\n//# sourceURL=webpack:///./step2-01/exercise/src/index.ts?");
|
||||||
|
|
||||||
|
/***/ })
|
||||||
|
|
||||||
|
/******/ });
|
||||||
6
docs/step2-02/demo/index.html
Normal file
6
docs/step2-02/demo/index.html
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="text/javascript" src="../../step2-02/demo/step2-02/demo.js"></script></body>
|
||||||
|
</html>
|
||||||
87
docs/step2-02/demo/src/components/TodoApp.tsx
Normal file
87
docs/step2-02/demo/src/components/TodoApp.tsx
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Stack } from 'office-ui-fabric-react';
|
||||||
|
import { TodoFooter } from './TodoFooter';
|
||||||
|
import { TodoHeader } from './TodoHeader';
|
||||||
|
import { TodoList } from './TodoList';
|
||||||
|
import { Store } from '../store';
|
||||||
|
|
||||||
|
let index = 0;
|
||||||
|
|
||||||
|
export class TodoApp extends React.Component<any, Store> {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
todos: {},
|
||||||
|
filter: 'all'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
render() {
|
||||||
|
const { filter, todos } = this.state;
|
||||||
|
return (
|
||||||
|
<Stack horizontalAlign="center">
|
||||||
|
<Stack style={{ width: 400 }} gap={25}>
|
||||||
|
<TodoHeader addTodo={this._addTodo} setFilter={this._setFilter} filter={filter} />
|
||||||
|
<TodoList complete={this._complete} todos={todos} filter={filter} remove={this._remove} edit={this._edit} />
|
||||||
|
<TodoFooter clear={this._clear} todos={todos} />
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _addTodo = label => {
|
||||||
|
const { todos } = this.state;
|
||||||
|
const id = index++;
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
todos: { ...todos, [id]: { label } }
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
private _remove = id => {
|
||||||
|
const newTodos = { ...this.state.todos };
|
||||||
|
delete newTodos[id];
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
todos: newTodos
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
private _complete = id => {
|
||||||
|
const newTodos = { ...this.state.todos };
|
||||||
|
newTodos[id].completed = !newTodos[id].completed;
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
todos: newTodos
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
private _edit = (id, label) => {
|
||||||
|
const newTodos = { ...this.state.todos };
|
||||||
|
newTodos[id] = { ...newTodos[id], label };
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
todos: newTodos
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
private _clear = () => {
|
||||||
|
const { todos } = this.state;
|
||||||
|
const newTodos = {};
|
||||||
|
|
||||||
|
Object.keys(this.state.todos).forEach(id => {
|
||||||
|
if (!todos[id].completed) {
|
||||||
|
newTodos[id] = todos[id];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
todos: newTodos
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
private _setFilter = filter => {
|
||||||
|
this.setState({
|
||||||
|
filter: filter
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
23
docs/step2-02/demo/src/components/TodoFooter.tsx
Normal file
23
docs/step2-02/demo/src/components/TodoFooter.tsx
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Text } from '@uifabric/experiments';
|
||||||
|
import { Stack } from 'office-ui-fabric-react';
|
||||||
|
import { Store } from '../store';
|
||||||
|
import { DefaultButton } from 'office-ui-fabric-react';
|
||||||
|
|
||||||
|
interface TodoFooterProps {
|
||||||
|
clear: () => void;
|
||||||
|
todos: Store['todos'];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TodoFooter = (props: TodoFooterProps) => {
|
||||||
|
const itemCount = Object.keys(props.todos).filter(id => !props.todos[id].completed).length;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack horizontal horizontalAlign="space-between">
|
||||||
|
<Text>
|
||||||
|
{itemCount} item{itemCount > 1 ? 's' : ''} left
|
||||||
|
</Text>
|
||||||
|
<DefaultButton onClick={() => props.clear()}>Clear Completed</DefaultButton>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
58
docs/step2-02/demo/src/components/TodoHeader.tsx
Normal file
58
docs/step2-02/demo/src/components/TodoHeader.tsx
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Text } from '@uifabric/experiments';
|
||||||
|
import { Stack } from 'office-ui-fabric-react';
|
||||||
|
import { Pivot, PivotItem, TextField, PrimaryButton } from 'office-ui-fabric-react';
|
||||||
|
import { FilterTypes } from '../store';
|
||||||
|
|
||||||
|
interface TodoHeaderProps {
|
||||||
|
addTodo: (label: string) => void;
|
||||||
|
setFilter: (filter: FilterTypes) => void;
|
||||||
|
filter: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TodoHeaderState {
|
||||||
|
labelInput: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TodoHeader extends React.Component<TodoHeaderProps, TodoHeaderState> {
|
||||||
|
constructor(props: TodoHeaderProps) {
|
||||||
|
super(props);
|
||||||
|
this.state = { labelInput: undefined };
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<Stack>
|
||||||
|
<Stack horizontal horizontalAlign="center">
|
||||||
|
<Text variant="xxLarge">todos</Text>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
<Stack horizontal>
|
||||||
|
<Stack.Item grow>
|
||||||
|
<TextField placeholder="What needs to be done?" value={this.state.labelInput} onChange={this.onChange} />
|
||||||
|
</Stack.Item>
|
||||||
|
<PrimaryButton onClick={this.onAdd}>Add</PrimaryButton>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
<Pivot onLinkClick={this.onFilter}>
|
||||||
|
<PivotItem headerText="all" />
|
||||||
|
<PivotItem headerText="active" />
|
||||||
|
<PivotItem headerText="completed" />
|
||||||
|
</Pivot>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private onAdd = () => {
|
||||||
|
this.props.addTodo(this.state.labelInput);
|
||||||
|
this.setState({ labelInput: undefined });
|
||||||
|
};
|
||||||
|
|
||||||
|
private onChange = (evt: React.FormEvent<HTMLInputElement>, newValue: string) => {
|
||||||
|
this.setState({ labelInput: newValue });
|
||||||
|
};
|
||||||
|
|
||||||
|
private onFilter = (item: PivotItem) => {
|
||||||
|
this.props.setFilter(item.props.headerText as FilterTypes);
|
||||||
|
};
|
||||||
|
}
|
||||||
27
docs/step2-02/demo/src/components/TodoList.tsx
Normal file
27
docs/step2-02/demo/src/components/TodoList.tsx
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Stack } from 'office-ui-fabric-react';
|
||||||
|
import { TodoListItem } from './TodoListItem';
|
||||||
|
import { Store, FilterTypes } from '../store';
|
||||||
|
|
||||||
|
interface TodoListProps {
|
||||||
|
complete: (id: string) => void;
|
||||||
|
remove: (id: string) => void;
|
||||||
|
todos: Store['todos'];
|
||||||
|
filter: FilterTypes;
|
||||||
|
edit: (id: string, label: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TodoList = (props: TodoListProps) => {
|
||||||
|
const { filter, todos, complete, remove, edit } = props;
|
||||||
|
const filteredTodos = Object.keys(todos).filter(id => {
|
||||||
|
return filter === 'all' || (filter === 'completed' && todos[id].completed) || (filter === 'active' && !todos[id].completed);
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack gap={10}>
|
||||||
|
{filteredTodos.map(id => (
|
||||||
|
<TodoListItem key={id} id={id} todos={todos} complete={complete} remove={remove} edit={edit} />
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
75
docs/step2-02/demo/src/components/TodoListItem.tsx
Normal file
75
docs/step2-02/demo/src/components/TodoListItem.tsx
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Stack, Checkbox, IconButton, TextField, DefaultButton } from 'office-ui-fabric-react';
|
||||||
|
import { Store } from '../store';
|
||||||
|
|
||||||
|
interface TodoListItemProps {
|
||||||
|
id: string;
|
||||||
|
todos: Store['todos'];
|
||||||
|
remove: (id: string) => void;
|
||||||
|
complete: (id: string) => void;
|
||||||
|
edit: (id: string, label: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TodoListItemState {
|
||||||
|
editing: boolean;
|
||||||
|
editLabel: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TodoListItem extends React.Component<TodoListItemProps, TodoListItemState> {
|
||||||
|
constructor(props: TodoListItemProps) {
|
||||||
|
super(props);
|
||||||
|
this.state = { editing: false, editLabel: undefined };
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { todos, id, complete, remove } = this.props;
|
||||||
|
const item = todos[id];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack horizontal verticalAlign="center" horizontalAlign="space-between">
|
||||||
|
{!this.state.editing && (
|
||||||
|
<>
|
||||||
|
<Checkbox label={item.label} checked={item.completed} onChange={() => complete(id)} />
|
||||||
|
<div>
|
||||||
|
<IconButton iconProps={{ iconName: 'Edit' }} className="clearButton" onClick={this.onEdit} />
|
||||||
|
<IconButton iconProps={{ iconName: 'Cancel' }} className="clearButton" onClick={() => remove(id)} />
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{this.state.editing && (
|
||||||
|
<Stack.Item grow>
|
||||||
|
<Stack horizontal>
|
||||||
|
<Stack.Item grow>
|
||||||
|
<TextField value={this.state.editLabel} onChange={this.onChange} />
|
||||||
|
</Stack.Item>
|
||||||
|
<DefaultButton onClick={this.onDoneEdit}>Save</DefaultButton>
|
||||||
|
</Stack>
|
||||||
|
</Stack.Item>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private onEdit = () => {
|
||||||
|
const { todos, id } = this.props;
|
||||||
|
const { label } = todos[id];
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
editing: true,
|
||||||
|
editLabel: this.state.editLabel || label
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
private onDoneEdit = () => {
|
||||||
|
this.props.edit(this.props.id, this.state.editLabel);
|
||||||
|
this.setState({
|
||||||
|
editing: false,
|
||||||
|
editLabel: undefined
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
private onChange = (evt: React.FormEvent<HTMLInputElement>, newValue: string) => {
|
||||||
|
this.setState({ editLabel: newValue });
|
||||||
|
};
|
||||||
|
}
|
||||||
10
docs/step2-02/demo/src/index.tsx
Normal file
10
docs/step2-02/demo/src/index.tsx
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
|
import { TodoApp } from './components/TodoApp';
|
||||||
|
import { initializeIcons } from '@uifabric/icons';
|
||||||
|
|
||||||
|
// Initializes the UI Fabric icons that we can use
|
||||||
|
// Choose one from this list: https://developer.microsoft.com/en-us/fabric#/styles/icons
|
||||||
|
initializeIcons();
|
||||||
|
|
||||||
|
ReactDOM.render(<TodoApp />, document.getElementById('app'));
|
||||||
14
docs/step2-02/demo/src/store/index.ts
Normal file
14
docs/step2-02/demo/src/store/index.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
export type FilterTypes = 'all' | 'active' | 'completed';
|
||||||
|
|
||||||
|
export interface TodoItem {
|
||||||
|
label: string;
|
||||||
|
completed: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Store {
|
||||||
|
todos: {
|
||||||
|
[id: string]: TodoItem;
|
||||||
|
};
|
||||||
|
|
||||||
|
filter: FilterTypes;
|
||||||
|
}
|
||||||
10334
docs/step2-02/demo/step2-02/demo.js
Normal file
10334
docs/step2-02/demo/step2-02/demo.js
Normal file
File diff suppressed because one or more lines are too long
6
docs/step2-02/exercise/index.html
Normal file
6
docs/step2-02/exercise/index.html
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="text/javascript" src="../../step2-02/exercise/step2-02/exercise.js"></script></body>
|
||||||
|
</html>
|
||||||
20
docs/step2-02/exercise/src/components/TodoApp.tsx
Normal file
20
docs/step2-02/exercise/src/components/TodoApp.tsx
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Stack } from 'office-ui-fabric-react';
|
||||||
|
import { TodoFooter } from './TodoFooter';
|
||||||
|
import { TodoHeader } from './TodoHeader';
|
||||||
|
import { TodoList } from './TodoList';
|
||||||
|
import { Store } from '../store';
|
||||||
|
|
||||||
|
export class TodoApp extends React.Component<any, Store> {
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<Stack horizontalAlign="center">
|
||||||
|
<Stack style={{ width: 400 }} gap={25}>
|
||||||
|
<TodoHeader />
|
||||||
|
<TodoList />
|
||||||
|
<TodoFooter />
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
16
docs/step2-02/exercise/src/components/TodoFooter.tsx
Normal file
16
docs/step2-02/exercise/src/components/TodoFooter.tsx
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export const TodoFooter = (props: any) => {
|
||||||
|
const itemCount = 3;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<footer>
|
||||||
|
<span>
|
||||||
|
{itemCount} item{itemCount > 1 ? 's' : ''} left
|
||||||
|
</span>
|
||||||
|
<button onClick={() => props.clear()} className="button">
|
||||||
|
Clear Completed
|
||||||
|
</button>
|
||||||
|
</footer>
|
||||||
|
);
|
||||||
|
};
|
||||||
19
docs/step2-02/exercise/src/components/TodoHeader.tsx
Normal file
19
docs/step2-02/exercise/src/components/TodoHeader.tsx
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { FilterTypes } from '../store';
|
||||||
|
|
||||||
|
export class TodoHeader extends React.Component<{}, {}> {
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h1>todos</h1>
|
||||||
|
<input className="textfield" placeholder="add todo" />
|
||||||
|
<button className="button add">Add</button>
|
||||||
|
<div className="filter">
|
||||||
|
<button>all</button>
|
||||||
|
<button>active</button>
|
||||||
|
<button>completed</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
13
docs/step2-02/exercise/src/components/TodoList.tsx
Normal file
13
docs/step2-02/exercise/src/components/TodoList.tsx
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Stack } from 'office-ui-fabric-react';
|
||||||
|
import { TodoListItem } from './TodoListItem';
|
||||||
|
|
||||||
|
export const TodoList = (props: any) => {
|
||||||
|
return (
|
||||||
|
<ul className="todos">
|
||||||
|
<TodoListItem />
|
||||||
|
<TodoListItem />
|
||||||
|
<TodoListItem />
|
||||||
|
</ul>
|
||||||
|
);
|
||||||
|
};
|
||||||
13
docs/step2-02/exercise/src/components/TodoListItem.tsx
Normal file
13
docs/step2-02/exercise/src/components/TodoListItem.tsx
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export class TodoListItem extends React.Component<any, any> {
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<li className="todo">
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" defaultChecked={false} /> test item
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
10
docs/step2-02/exercise/src/index.tsx
Normal file
10
docs/step2-02/exercise/src/index.tsx
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
|
import { TodoApp } from './components/TodoApp';
|
||||||
|
import { initializeIcons } from '@uifabric/icons';
|
||||||
|
|
||||||
|
// Initializes the UI Fabric icons that we can use
|
||||||
|
// Choose one from this list: https://developer.microsoft.com/en-us/fabric#/styles/icons
|
||||||
|
initializeIcons();
|
||||||
|
|
||||||
|
ReactDOM.render(<TodoApp />, document.getElementById('app'));
|
||||||
14
docs/step2-02/exercise/src/store/index.ts
Normal file
14
docs/step2-02/exercise/src/store/index.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
export type FilterTypes = 'all' | 'active' | 'completed';
|
||||||
|
|
||||||
|
export interface TodoItem {
|
||||||
|
label: string;
|
||||||
|
completed: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Store {
|
||||||
|
todos: {
|
||||||
|
[id: string]: TodoItem;
|
||||||
|
};
|
||||||
|
|
||||||
|
filter: FilterTypes;
|
||||||
|
}
|
||||||
8726
docs/step2-02/exercise/step2-02/exercise.js
Normal file
8726
docs/step2-02/exercise/step2-02/exercise.js
Normal file
File diff suppressed because one or more lines are too long
6
docs/step2-03/demo/index.html
Normal file
6
docs/step2-03/demo/index.html
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="text/javascript" src="../../step2-03/demo/step2-03/demo.js"></script></body>
|
||||||
|
</html>
|
||||||
95
docs/step2-03/demo/src/components/TodoApp.tsx
Normal file
95
docs/step2-03/demo/src/components/TodoApp.tsx
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Stack, Customizer, mergeStyles, getTheme } from 'office-ui-fabric-react';
|
||||||
|
import { TodoFooter } from './TodoFooter';
|
||||||
|
import { TodoHeader } from './TodoHeader';
|
||||||
|
import { TodoList } from './TodoList';
|
||||||
|
import { Store } from '../store';
|
||||||
|
import { FluentCustomizations } from '@uifabric/fluent-theme';
|
||||||
|
|
||||||
|
let index = 0;
|
||||||
|
|
||||||
|
const className = mergeStyles({
|
||||||
|
padding: 25,
|
||||||
|
...getTheme().effects.elevation4
|
||||||
|
});
|
||||||
|
|
||||||
|
export class TodoApp extends React.Component<any, Store> {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
todos: {},
|
||||||
|
filter: 'all'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
render() {
|
||||||
|
const { filter, todos } = this.state;
|
||||||
|
return (
|
||||||
|
<Customizer {...FluentCustomizations}>
|
||||||
|
<Stack horizontalAlign="center">
|
||||||
|
<Stack style={{ width: 400 }} gap={25} className={className}>
|
||||||
|
<TodoHeader addTodo={this._addTodo} setFilter={this._setFilter} filter={filter} />
|
||||||
|
<TodoList complete={this._complete} todos={todos} filter={filter} remove={this._remove} edit={this._edit} />
|
||||||
|
<TodoFooter clear={this._clear} todos={todos} />
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
</Customizer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _addTodo = label => {
|
||||||
|
const { todos } = this.state;
|
||||||
|
const id = index++;
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
todos: { ...todos, [id]: { label } }
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
private _remove = id => {
|
||||||
|
const newTodos = { ...this.state.todos };
|
||||||
|
delete newTodos[id];
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
todos: newTodos
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
private _complete = id => {
|
||||||
|
const newTodos = { ...this.state.todos };
|
||||||
|
newTodos[id].completed = !newTodos[id].completed;
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
todos: newTodos
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
private _edit = (id, label) => {
|
||||||
|
const newTodos = { ...this.state.todos };
|
||||||
|
newTodos[id] = { ...newTodos[id], label };
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
todos: newTodos
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
private _clear = () => {
|
||||||
|
const { todos } = this.state;
|
||||||
|
const newTodos = {};
|
||||||
|
|
||||||
|
Object.keys(this.state.todos).forEach(id => {
|
||||||
|
if (!todos[id].completed) {
|
||||||
|
newTodos[id] = todos[id];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
todos: newTodos
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
private _setFilter = filter => {
|
||||||
|
this.setState({
|
||||||
|
filter: filter
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
23
docs/step2-03/demo/src/components/TodoFooter.tsx
Normal file
23
docs/step2-03/demo/src/components/TodoFooter.tsx
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Text } from '@uifabric/experiments';
|
||||||
|
import { Stack } from 'office-ui-fabric-react';
|
||||||
|
import { Store } from '../store';
|
||||||
|
import { DefaultButton } from 'office-ui-fabric-react';
|
||||||
|
|
||||||
|
interface TodoFooterProps {
|
||||||
|
clear: () => void;
|
||||||
|
todos: Store['todos'];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TodoFooter = (props: TodoFooterProps) => {
|
||||||
|
const itemCount = Object.keys(props.todos).filter(id => !props.todos[id].completed).length;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack horizontal horizontalAlign="space-between">
|
||||||
|
<Text>
|
||||||
|
{itemCount} item{itemCount > 1 ? 's' : ''} left
|
||||||
|
</Text>
|
||||||
|
<DefaultButton onClick={() => props.clear()}>Clear Completed</DefaultButton>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
58
docs/step2-03/demo/src/components/TodoHeader.tsx
Normal file
58
docs/step2-03/demo/src/components/TodoHeader.tsx
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Text } from '@uifabric/experiments';
|
||||||
|
import { Stack } from 'office-ui-fabric-react';
|
||||||
|
import { Pivot, PivotItem, TextField, PrimaryButton } from 'office-ui-fabric-react';
|
||||||
|
import { FilterTypes } from '../store';
|
||||||
|
|
||||||
|
interface TodoHeaderProps {
|
||||||
|
addTodo: (label: string) => void;
|
||||||
|
setFilter: (filter: FilterTypes) => void;
|
||||||
|
filter: FilterTypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TodoHeaderState {
|
||||||
|
labelInput: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TodoHeader extends React.Component<TodoHeaderProps, TodoHeaderState> {
|
||||||
|
constructor(props: TodoHeaderProps) {
|
||||||
|
super(props);
|
||||||
|
this.state = { labelInput: undefined };
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<Stack gap={10}>
|
||||||
|
<Stack horizontal horizontalAlign="center">
|
||||||
|
<Text variant="xxLarge">todos</Text>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
<Stack horizontal gap={10}>
|
||||||
|
<Stack.Item grow>
|
||||||
|
<TextField placeholder="What needs to be done?" value={this.state.labelInput} onChange={this.onChange} />
|
||||||
|
</Stack.Item>
|
||||||
|
<PrimaryButton onClick={this.onAdd}>Add</PrimaryButton>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
<Pivot onLinkClick={this.onFilter}>
|
||||||
|
<PivotItem headerText="all" />
|
||||||
|
<PivotItem headerText="active" />
|
||||||
|
<PivotItem headerText="completed" />
|
||||||
|
</Pivot>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private onAdd = () => {
|
||||||
|
this.props.addTodo(this.state.labelInput);
|
||||||
|
this.setState({ labelInput: undefined });
|
||||||
|
};
|
||||||
|
|
||||||
|
private onChange = (evt: React.FormEvent<HTMLInputElement>, newValue: string) => {
|
||||||
|
this.setState({ labelInput: newValue });
|
||||||
|
};
|
||||||
|
|
||||||
|
private onFilter = (item: PivotItem) => {
|
||||||
|
this.props.setFilter(item.props.headerText as FilterTypes);
|
||||||
|
};
|
||||||
|
}
|
||||||
27
docs/step2-03/demo/src/components/TodoList.tsx
Normal file
27
docs/step2-03/demo/src/components/TodoList.tsx
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Stack } from 'office-ui-fabric-react';
|
||||||
|
import { TodoListItem } from './TodoListItem';
|
||||||
|
import { Store, FilterTypes } from '../store';
|
||||||
|
|
||||||
|
interface TodoListProps {
|
||||||
|
complete: (id: string) => void;
|
||||||
|
remove: (id: string) => void;
|
||||||
|
todos: Store['todos'];
|
||||||
|
filter: FilterTypes;
|
||||||
|
edit: (id: string, label: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TodoList = (props: TodoListProps) => {
|
||||||
|
const { filter, todos, complete, remove, edit } = props;
|
||||||
|
const filteredTodos = Object.keys(todos).filter(id => {
|
||||||
|
return filter === 'all' || (filter === 'completed' && todos[id].completed) || (filter === 'active' && !todos[id].completed);
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack gap={10}>
|
||||||
|
{filteredTodos.map(id => (
|
||||||
|
<TodoListItem key={id} id={id} todos={todos} complete={complete} remove={remove} edit={edit} />
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
75
docs/step2-03/demo/src/components/TodoListItem.tsx
Normal file
75
docs/step2-03/demo/src/components/TodoListItem.tsx
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Stack, Checkbox, IconButton, TextField, DefaultButton } from 'office-ui-fabric-react';
|
||||||
|
import { Store } from '../store';
|
||||||
|
|
||||||
|
interface TodoListItemProps {
|
||||||
|
id: string;
|
||||||
|
todos: Store['todos'];
|
||||||
|
remove: (id: string) => void;
|
||||||
|
complete: (id: string) => void;
|
||||||
|
edit: (id: string, label: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TodoListItemState {
|
||||||
|
editing: boolean;
|
||||||
|
editLabel: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TodoListItem extends React.Component<TodoListItemProps, TodoListItemState> {
|
||||||
|
constructor(props: TodoListItemProps) {
|
||||||
|
super(props);
|
||||||
|
this.state = { editing: false, editLabel: undefined };
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { todos, id, complete, remove } = this.props;
|
||||||
|
const item = todos[id];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack horizontal verticalAlign="center" horizontalAlign="space-between">
|
||||||
|
{!this.state.editing && (
|
||||||
|
<>
|
||||||
|
<Checkbox label={item.label} checked={item.completed} onChange={() => complete(id)} />
|
||||||
|
<div>
|
||||||
|
<IconButton iconProps={{ iconName: 'Edit' }} onClick={this.onEdit} />
|
||||||
|
<IconButton iconProps={{ iconName: 'Cancel' }} onClick={() => remove(id)} />
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{this.state.editing && (
|
||||||
|
<Stack.Item grow>
|
||||||
|
<Stack horizontal gap={10}>
|
||||||
|
<Stack.Item grow>
|
||||||
|
<TextField value={this.state.editLabel} onChange={this.onChange} />
|
||||||
|
</Stack.Item>
|
||||||
|
<DefaultButton onClick={this.onDoneEdit}>Save</DefaultButton>
|
||||||
|
</Stack>
|
||||||
|
</Stack.Item>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private onEdit = () => {
|
||||||
|
const { todos, id } = this.props;
|
||||||
|
const { label } = todos[id];
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
editing: true,
|
||||||
|
editLabel: this.state.editLabel || label
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
private onDoneEdit = () => {
|
||||||
|
this.props.edit(this.props.id, this.state.editLabel);
|
||||||
|
this.setState({
|
||||||
|
editing: false,
|
||||||
|
editLabel: undefined
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
private onChange = (evt: React.FormEvent<HTMLInputElement>, newValue: string) => {
|
||||||
|
this.setState({ editLabel: newValue });
|
||||||
|
};
|
||||||
|
}
|
||||||
10
docs/step2-03/demo/src/index.tsx
Normal file
10
docs/step2-03/demo/src/index.tsx
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
|
import { TodoApp } from './components/TodoApp';
|
||||||
|
import { initializeIcons } from '@uifabric/icons';
|
||||||
|
|
||||||
|
// Initializes the UI Fabric icons that we can use
|
||||||
|
// Choose one from this list: https://developer.microsoft.com/en-us/fabric#/styles/icons
|
||||||
|
initializeIcons();
|
||||||
|
|
||||||
|
ReactDOM.render(<TodoApp />, document.getElementById('app'));
|
||||||
14
docs/step2-03/demo/src/store/index.ts
Normal file
14
docs/step2-03/demo/src/store/index.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
export type FilterTypes = 'all' | 'active' | 'completed';
|
||||||
|
|
||||||
|
export interface TodoItem {
|
||||||
|
label: string;
|
||||||
|
completed: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Store {
|
||||||
|
todos: {
|
||||||
|
[id: string]: TodoItem;
|
||||||
|
};
|
||||||
|
|
||||||
|
filter: FilterTypes;
|
||||||
|
}
|
||||||
10934
docs/step2-03/demo/step2-03/demo.js
Normal file
10934
docs/step2-03/demo/step2-03/demo.js
Normal file
File diff suppressed because one or more lines are too long
6
docs/step2-03/exercise/index.html
Normal file
6
docs/step2-03/exercise/index.html
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="text/javascript" src="../../step2-03/exercise/step2-03/exercise.js"></script></body>
|
||||||
|
</html>
|
||||||
97
docs/step2-03/exercise/src/components/TodoApp.tsx
Normal file
97
docs/step2-03/exercise/src/components/TodoApp.tsx
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Stack, Customizer, mergeStyles, getTheme } from 'office-ui-fabric-react';
|
||||||
|
import { TodoFooter } from './TodoFooter';
|
||||||
|
import { TodoHeader } from './TodoHeader';
|
||||||
|
import { TodoList } from './TodoList';
|
||||||
|
import { Store } from '../store';
|
||||||
|
|
||||||
|
// TODO: Change me to another theme!
|
||||||
|
import { TeamsCustomizations } from '@uifabric/theme-samples';
|
||||||
|
|
||||||
|
let index = 0;
|
||||||
|
|
||||||
|
const className = mergeStyles({
|
||||||
|
padding: 25,
|
||||||
|
...getTheme().effects.elevation4
|
||||||
|
});
|
||||||
|
|
||||||
|
export class TodoApp extends React.Component<any, Store> {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
todos: {},
|
||||||
|
filter: 'all'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
render() {
|
||||||
|
const { filter, todos } = this.state;
|
||||||
|
return (
|
||||||
|
<Customizer {...TeamsCustomizations}>
|
||||||
|
<Stack horizontalAlign="center">
|
||||||
|
<Stack style={{ width: 400 }} gap={25} className={className}>
|
||||||
|
<TodoHeader addTodo={this._addTodo} setFilter={this._setFilter} filter={filter} />
|
||||||
|
<TodoList complete={this._complete} todos={todos} filter={filter} remove={this._remove} edit={this._edit} />
|
||||||
|
<TodoFooter clear={this._clear} todos={todos} />
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
</Customizer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _addTodo = label => {
|
||||||
|
const { todos } = this.state;
|
||||||
|
const id = index++;
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
todos: { ...todos, [id]: { label } }
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
private _remove = id => {
|
||||||
|
const newTodos = { ...this.state.todos };
|
||||||
|
delete newTodos[id];
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
todos: newTodos
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
private _complete = id => {
|
||||||
|
const newTodos = { ...this.state.todos };
|
||||||
|
newTodos[id].completed = !newTodos[id].completed;
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
todos: newTodos
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
private _edit = (id, label) => {
|
||||||
|
const newTodos = { ...this.state.todos };
|
||||||
|
newTodos[id] = { ...newTodos[id], label };
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
todos: newTodos
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
private _clear = () => {
|
||||||
|
const { todos } = this.state;
|
||||||
|
const newTodos = {};
|
||||||
|
|
||||||
|
Object.keys(this.state.todos).forEach(id => {
|
||||||
|
if (!todos[id].completed) {
|
||||||
|
newTodos[id] = todos[id];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
todos: newTodos
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
private _setFilter = filter => {
|
||||||
|
this.setState({
|
||||||
|
filter: filter
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
23
docs/step2-03/exercise/src/components/TodoFooter.tsx
Normal file
23
docs/step2-03/exercise/src/components/TodoFooter.tsx
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Text } from '@uifabric/experiments';
|
||||||
|
import { Stack } from 'office-ui-fabric-react';
|
||||||
|
import { Store } from '../store';
|
||||||
|
import { DefaultButton } from 'office-ui-fabric-react';
|
||||||
|
|
||||||
|
interface TodoFooterProps {
|
||||||
|
clear: () => void;
|
||||||
|
todos: Store['todos'];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TodoFooter = (props: TodoFooterProps) => {
|
||||||
|
const itemCount = Object.keys(props.todos).filter(id => !props.todos[id].completed).length;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack horizontal horizontalAlign="space-between">
|
||||||
|
<Text>
|
||||||
|
{itemCount} item{itemCount > 1 ? 's' : ''} left
|
||||||
|
</Text>
|
||||||
|
<DefaultButton onClick={() => props.clear()}>Clear Completed</DefaultButton>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
58
docs/step2-03/exercise/src/components/TodoHeader.tsx
Normal file
58
docs/step2-03/exercise/src/components/TodoHeader.tsx
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Text } from '@uifabric/experiments';
|
||||||
|
import { Stack } from 'office-ui-fabric-react';
|
||||||
|
import { Pivot, PivotItem, TextField, PrimaryButton } from 'office-ui-fabric-react';
|
||||||
|
import { FilterTypes } from '../store';
|
||||||
|
|
||||||
|
interface TodoHeaderProps {
|
||||||
|
addTodo: (label: string) => void;
|
||||||
|
setFilter: (filter: FilterTypes) => void;
|
||||||
|
filter: FilterTypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TodoHeaderState {
|
||||||
|
labelInput: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TodoHeader extends React.Component<TodoHeaderProps, TodoHeaderState> {
|
||||||
|
constructor(props: TodoHeaderProps) {
|
||||||
|
super(props);
|
||||||
|
this.state = { labelInput: undefined };
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<Stack gap={10}>
|
||||||
|
<Stack horizontal horizontalAlign="center">
|
||||||
|
<Text variant="xxLarge">todos</Text>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
<Stack horizontal gap={10}>
|
||||||
|
<Stack.Item grow>
|
||||||
|
<TextField placeholder="What needs to be done?" value={this.state.labelInput} onChange={this.onChange} />
|
||||||
|
</Stack.Item>
|
||||||
|
<PrimaryButton onClick={this.onAdd}>Add</PrimaryButton>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
<Pivot onLinkClick={this.onFilter}>
|
||||||
|
<PivotItem headerText="all" />
|
||||||
|
<PivotItem headerText="active" />
|
||||||
|
<PivotItem headerText="completed" />
|
||||||
|
</Pivot>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private onAdd = () => {
|
||||||
|
this.props.addTodo(this.state.labelInput);
|
||||||
|
this.setState({ labelInput: undefined });
|
||||||
|
};
|
||||||
|
|
||||||
|
private onChange = (evt: React.FormEvent<HTMLInputElement>, newValue: string) => {
|
||||||
|
this.setState({ labelInput: newValue });
|
||||||
|
};
|
||||||
|
|
||||||
|
private onFilter = (item: PivotItem) => {
|
||||||
|
this.props.setFilter(item.props.headerText as FilterTypes);
|
||||||
|
};
|
||||||
|
}
|
||||||
27
docs/step2-03/exercise/src/components/TodoList.tsx
Normal file
27
docs/step2-03/exercise/src/components/TodoList.tsx
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Stack } from 'office-ui-fabric-react';
|
||||||
|
import { TodoListItem } from './TodoListItem';
|
||||||
|
import { Store, FilterTypes } from '../store';
|
||||||
|
|
||||||
|
interface TodoListProps {
|
||||||
|
complete: (id: string) => void;
|
||||||
|
remove: (id: string) => void;
|
||||||
|
todos: Store['todos'];
|
||||||
|
filter: FilterTypes;
|
||||||
|
edit: (id: string, label: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TodoList = (props: TodoListProps) => {
|
||||||
|
const { filter, todos, complete, remove, edit } = props;
|
||||||
|
const filteredTodos = Object.keys(todos).filter(id => {
|
||||||
|
return filter === 'all' || (filter === 'completed' && todos[id].completed) || (filter === 'active' && !todos[id].completed);
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack gap={10}>
|
||||||
|
{filteredTodos.map(id => (
|
||||||
|
<TodoListItem key={id} id={id} todos={todos} complete={complete} remove={remove} edit={edit} />
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user