diff --git a/playground/index.html b/playground/index.html index 454cef5..8b4a22c 100644 --- a/playground/index.html +++ b/playground/index.html @@ -1,6 +1,19 @@ + + + -
+ diff --git a/playground/src/actions/index.ts b/playground/src/actions/index.ts deleted file mode 100644 index b99f899..0000000 --- a/playground/src/actions/index.ts +++ /dev/null @@ -1,58 +0,0 @@ -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, 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, getState: () => Store) => { - dispatch(actions.edit(id, label)); - await todosService.edit(id, getState().todos[id]); - }; - }, - - remove: (id: string) => { - return async (dispatch: Dispatch, getState: () => Store) => { - dispatch(actions.remove(id)); - await todosService.remove(id); - }; - }, - - complete: (id: string) => { - return async (dispatch: Dispatch, getState: () => Store) => { - dispatch(actions.complete(id)); - await todosService.edit(id, getState().todos[id]); - }; - }, - - clear: () => { - return async (dispatch: Dispatch, getState: () => Store) => { - dispatch(actions.clear()); - await todosService.editBulk(getState().todos); - }; - } -}; - -export type ActionTypes = GenericActionTypes; -export type TodoAction = GenericAction; -export type TodoActionWithService = GenericAction; -export type TodoActionLookup = GenericActionLookup; diff --git a/playground/src/components/TodoApp.tsx b/playground/src/components/TodoApp.tsx deleted file mode 100644 index 13fb83f..0000000 --- a/playground/src/components/TodoApp.tsx +++ /dev/null @@ -1,15 +0,0 @@ -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: {}) => ( - - - - - - - -); diff --git a/playground/src/components/TodoFooter.tsx b/playground/src/components/TodoFooter.tsx deleted file mode 100644 index 66f0c47..0000000 --- a/playground/src/components/TodoFooter.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import React from 'react'; - -import { Store } from '../store'; -import { DefaultButton, Stack, Text } 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 & ReturnType; - -export const TodoFooter = connect( - mapStateToProps, - mapDispatchToProps -)((props: TodoFooterProps) => { - const itemCount = Object.keys(props.todos).filter(id => !props.todos[id].completed).length; - - return ( - - - {itemCount} item{itemCount === 1 ? '' : 's'} left - - props.clear()}>Clear Completed - - ); -}); diff --git a/playground/src/components/TodoHeader.tsx b/playground/src/components/TodoHeader.tsx deleted file mode 100644 index 8fe1c8c..0000000 --- a/playground/src/components/TodoHeader.tsx +++ /dev/null @@ -1,79 +0,0 @@ -import React from 'react'; -import { Pivot, PivotItem, TextField, Stack, Text } 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 & ReturnType; - -interface TodoHeaderState { - labelInput: string; -} - -class TodoHeader extends React.Component { - 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, newValue: string) => { - this.setState({ labelInput: newValue }); - }; - - onFilter = (item: PivotItem) => { - this.props.setFilter(item.props.headerText as FilterTypes); - }; - - render() { - return ( - - - todos - - - - - - - - - - - ); - } -} - -// Hook up the Redux state and dispatches -const component = connect( - mapStateToProps, - mapDispatchToProps -)(TodoHeader); - -export { component as TodoHeader }; diff --git a/playground/src/components/TodoList.tsx b/playground/src/components/TodoList.tsx deleted file mode 100644 index 3e4b05c..0000000 --- a/playground/src/components/TodoList.tsx +++ /dev/null @@ -1,40 +0,0 @@ -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 & ReturnType; - -class TodoList extends React.Component { - 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 ( - - {filteredTodos.map(id => ( - - ))} - - ); - } -} - -const component = connect( - mapStateToProps, - mapDispatchToProps -)(TodoList); - -export { component as TodoList }; diff --git a/playground/src/components/TodoListItem.tsx b/playground/src/components/TodoListItem.tsx deleted file mode 100644 index c8132cf..0000000 --- a/playground/src/components/TodoListItem.tsx +++ /dev/null @@ -1,104 +0,0 @@ -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 & ReturnType; - -interface TodoListItemState { - editing: boolean; - editLabel: string; -} - -const className = mergeStyles({ - selectors: { - '.clearButton': { - visibility: 'hidden' - }, - '&:hover .clearButton': { - visibility: 'visible' - } - } -}); - -class TodoListItem extends React.Component { - /** - * - */ - 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, newValue: string) => { - this.setState({ editLabel: newValue }); - }; - - render() { - const { todos, id, complete, remove } = this.props; - const item = todos[id]; - - return ( - - {!this.state.editing && ( - <> - complete(id)} /> -
- - remove(id)} /> -
- - )} - - {this.state.editing && } -
- ); - } -} - -const component = connect( - mapStateToProps, - mapDispatchToProps -)(TodoListItem); - -export { component as TodoListItem }; diff --git a/playground/src/index.tsx b/playground/src/index.tsx deleted file mode 100644 index 72bfb40..0000000 --- a/playground/src/index.tsx +++ /dev/null @@ -1,36 +0,0 @@ -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( - - - , - document.getElementById('app') - ); -})(); - -// For Synchronous Case -// const store = createStore(reducer, { todos: {}, filter: 'all' }, composeEnhancers(applyMiddleware(thunk))); diff --git a/playground/src/reducers/__tests__/reducers.spec.ts b/playground/src/reducers/__tests__/reducers.spec.ts deleted file mode 100644 index b666d8a..0000000 --- a/playground/src/reducers/__tests__/reducers.spec.ts +++ /dev/null @@ -1,10 +0,0 @@ -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'); - }); -}); diff --git a/playground/src/reducers/createReducer.ts b/playground/src/reducers/createReducer.ts deleted file mode 100644 index eb195ab..0000000 --- a/playground/src/reducers/createReducer.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { ActionTypes, TodoActionLookup, actions } from '../actions'; -import { createGenericReducer, HandlerMap, ImmerReducer } from '../redux-utils/reducer'; -import { Reducer } from 'redux'; - -export function createReducer( - initialState: T, - handlerOrMap: HandlerMap | ImmerReducer -): Reducer { - return createGenericReducer(initialState, handlerOrMap); -} diff --git a/playground/src/reducers/index.ts b/playground/src/reducers/index.ts deleted file mode 100644 index df6ccf7..0000000 --- a/playground/src/reducers/index.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { createReducer } from './createReducer'; -import { Store, FilterTypes } from '../store'; -import { combineReducers } from 'redux'; - -export const reducer = combineReducers({ - todos: createReducer( - {}, - { - 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('all', (draft, action) => { - return action.filter as FilterTypes; - }) -}); diff --git a/playground/src/redux-utils/action.ts b/playground/src/redux-utils/action.ts deleted file mode 100644 index 32c96dc..0000000 --- a/playground/src/redux-utils/action.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Action } from 'redux'; - -type ActionWithPayload = Action & P; - -export function action(type: T): Action; -export function action(type: T, payload: P): ActionWithPayload; -export function action(type: T, payload?: P) { - return { type, ...payload }; -} - -export type GenericActionMapping = { [somekey in keyof A]: (...args: any) => Action | ActionWithPayload }; -export type GenericActionTypes> = ReturnType['type']; -export type GenericAction> = ReturnType]>; -export type GenericActionLookup> = { [a in GenericActionTypes]: ReturnType }; diff --git a/playground/src/redux-utils/reducer.ts b/playground/src/redux-utils/reducer.ts deleted file mode 100644 index fa6c4a3..0000000 --- a/playground/src/redux-utils/reducer.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { Reducer } from 'redux'; -import { Draft, produce } from 'immer'; -import { GenericActionLookup, GenericActionMapping } from './action'; - -export type ImmerReducer = (state: Draft, action?: A) => T; -export type HandlerMap> = { - [actionType in keyof A]?: ImmerReducer[actionType]> -}; - -function isHandlerFunction>( - handlerOrMap: HandlerMap | ImmerReducer -): handlerOrMap is ImmerReducer { - if (typeof handlerOrMap === 'function') { - return true; - } - - return false; -} - -export function createGenericReducer, AM = keyof GenericActionMapping>( - initialState: T, - handlerOrMap: HandlerMap | ImmerReducer[AM]> -): Reducer { - return function reducer(state = initialState, action: GenericActionLookup[AM]): T { - if (isHandlerFunction(handlerOrMap)) { - return produce(state, draft => handlerOrMap(draft, action as GenericActionLookup[AM])); - } else if (handlerOrMap.hasOwnProperty(action.type)) { - const handler = (handlerOrMap as any)[action.type] as ImmerReducer[AM]>; - return produce(state, draft => handler(draft, action)); - } else { - return state; - } - }; -} diff --git a/playground/src/service/todosService.ts b/playground/src/service/todosService.ts deleted file mode 100644 index b2c2481..0000000 --- a/playground/src/service/todosService.ts +++ /dev/null @@ -1,48 +0,0 @@ -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(); -} diff --git a/playground/src/store/index.ts b/playground/src/store/index.ts deleted file mode 100644 index 221b5f4..0000000 --- a/playground/src/store/index.ts +++ /dev/null @@ -1,14 +0,0 @@ -export type FilterTypes = 'all' | 'active' | 'completed'; - -export interface TodoItem { - label: string; - completed: boolean; -} - -export interface Store { - todos: { - [id: string]: TodoItem; - }; - - filter: FilterTypes; -} diff --git a/webpack.config.js b/webpack.config.js index ac13d60..b8e1733 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -34,7 +34,7 @@ fs.readdirSync('./').filter(step => { } } - if (!isEntryPoint && step.includes('step')) { + if (!isEntryPoint && (step.includes('step') || step.includes('playground'))) { nonWebpackedEntries.push(step); } });