diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..60d2cae --- /dev/null +++ b/.editorconfig @@ -0,0 +1,29 @@ +# This file is for unifying the coding style for different editors and IDEs. +# More information at http://EditorConfig.org + +[*] +indent_style = tab +indent_size = 4 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[.{babel,eslint,jslint,jshint,jscs}rc] +indent_style = tab +indent_size = 4 + +# Use 1 tab for public files (per uie.paypal.com) +[*.{es6,js,json,less,dust}] +indent_style = tab +indent_size = 4 + +# Use 4 spaces for indentation in Markdown files +[*.md] +trim_trailing_whitespace = false +indent_style = space +indent_size = 4 + +[package.json] +indent_style = space +indent_size = 2 diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..b1fd2df --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,32 @@ +module.exports = { + parser: '@typescript-eslint/parser', // Specifies the ESLint parser + extends: [ + 'plugin:@typescript-eslint/recommended', // Uses the recommended rules from the @typescript-eslint/eslint-plugin + 'plugin:react/recommended', // basic rules for React + 'plugin:prettier/recommended', // Enables eslint-plugin-prettier and displays prettier errors as ESLint errors. Make sure this is always the last configuration in the extends array. + 'prettier/@typescript-eslint', // Uses eslint-config-prettier to disable ESLint rules from @typescript-eslint/eslint-plugin that would conflict with prettier + ], + parserOptions: { + 'ecmaVersion': 2018, // Allows for the parsing of modern ECMAScript features + 'sourceType': 'module', + 'ecmaFeatures': { + 'jsx': true, // Allows for the parsing of JSX + } + }, + plugins: [ + '@typescript-eslint', + 'react' + ], + rules: { + // Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs + // e.g. "@typescript-eslint/explicit-function-return-type": "off" + '@typescript-eslint/indent': ['error', 'tab' ], + '@typescript-eslint/no-explicit-any': 0, + 'useTabs': 0 + }, + settings: { + 'react': { + 'version': 'detect' // Tells eslint-plugin-react to automatically detect the version of React to use + } + } +}; diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8cf25d6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +.idea +.vscode + +*.iml +out +gen +.DS_Store +node_modules/ +users/ +system +npm-debug.log + +*.js +*.js.map +!public/*.js +!react-test-setup.js +!webpack.config.js +!.editorconfig +!.eslintrc.js +!.prettierrc.js +!jest.config.js diff --git a/.prettierrc.js b/.prettierrc.js new file mode 100644 index 0000000..d50e0b7 --- /dev/null +++ b/.prettierrc.js @@ -0,0 +1,9 @@ +module.exports = { + semi: true, + trailingComma: 'none', + singleQuote: true, + printWidth: 120, + useTabs: true, + jsxSingleQuote: true, + arrowParens: 'always' +}; diff --git a/README.md b/README.md new file mode 100644 index 0000000..d747c99 --- /dev/null +++ b/README.md @@ -0,0 +1,100 @@ +# "Offline-First" TodoMVC Example + +## Up and Running + +1. Install the dependencies found in the `package.json` +2. Compile the TypeScript to JavaScript +3. Run the server + + ``` + npm install + npm run build + ``` + +4. Open `public/index.html` in your browser. + +## What's an "offline-first" application? + +Coming soon ... + +Here's the full tech-stack that we've chosen to execute this idea: + +- **TypeScript**: used for transpiling (JSX and ES6) and module bundling for universal module design +- **React**: the V in MVC +- **Express**: our lightweight, un-opinionated, server framework +- **Page.js**: our client side routing technology +- **PouchDB**: for data storage and future "offline" capabilities + +#### A bit on the top 4: + +> TypeScript is a language for application-scale JavaScript development. TypeScript is a typed superset of JavaScript that compiles to plain JavaScript. Any browser. Any host. Any OS. Open Source. +> +> _[TypeScript - typescriptlang.org](http://typescriptlang.org)_ + +> React is a JavaScript library for creating user interfaces. Its core principles are declarative code, efficiency, and flexibility. Simply specify what your component looks like and React will keep it up-to-date when the underlying data changes. +> +> _[React - facebook.github.io/react](http://facebook.github.io/react)_ + +## Learning TypeScript + +The [TypeScript website](http://typescriptlang.org) is a great resource for getting started. + +Here are some links you may find helpful: + +- [Tutorial](http://www.typescriptlang.org/Tutorial) +- [Code Playground](http://www.typescriptlang.org/Playground) +- [Documentation](https://github.com/Microsoft/TypeScript/wiki) +- [Applications built with TypeScript](http://www.typescriptlang.org/Samples) +- [Blog](http://blogs.msdn.com/b/typescript) +- [Source Code](https://github.com/Microsoft/TypeScript) + +Articles and guides from the community: + +- [Thoughts on TypeScript](http://www.nczonline.net/blog/2012/10/04/thoughts-on-typescript) +- [ScreenCast - Why I Like TypeScript](http://www.leebrimelow.com/why-i-like-typescripts) + +Get help from other TypeScript users: + +- [TypeScript on StackOverflow](http://stackoverflow.com/questions/tagged/typescript) +- [Forums](https://github.com/Microsoft/TypeScript/issues) +- [TypeScript on Twitter](http://twitter.com/typescriptlang) + +_If you have other helpful links to share, or find any of the links above no longer work, please [let us know](https://github.com/tastejs/todomvc/issues)._ + +## Learning Express + +Express has been around for a very long time, so documentation is ubiquitous. But, if you need a reference, the framework's main site is the place to go: http://expressjs.com/. + +## Learning React + +The [React getting started documentation](http://facebook.github.io/react/docs/getting-started.html) is a great way to get started. + +Here are some links you may find helpful: + +- [Documentation](http://facebook.github.io/react/docs/getting-started.html) +- [API Reference](http://facebook.github.io/react/docs/reference.html) +- [Blog](http://facebook.github.io/react/blog/) +- [React on GitHub](https://github.com/facebook/react) +- [Support](http://facebook.github.io/react/support.html) + +Articles and guides from the community: + +- [How is Facebook's React JavaScript library](http://www.quora.com/React-JS-Library/How-is-Facebooks-React-JavaScript-library) +- [React: Under the hood](http://www.quora.com/Pete-Hunt/Posts/React-Under-the-Hood) + +Get help from other React users: + +* [React on StackOverflow](http://stackoverflow.com/questions/tagged/reactjs) +* [Discussion Forum](https://discuss.reactjs.org/) + +_If you have other helpful links to share, or find any of the links above no longer work, please [let us know](https://github.com/tastejs/todomvc/issues)._ + +## How it works + +Coming soon ... + +## TODO + +- [ ] Get the app to rerender when the PouchDB is updated +- [x] Get the TodoMVC app to render todos stored within IndexedDB using PouchDB +- [x] Initialize the application from [this previous TodoMVC application](https://github.com/cerebralideas/todomvc-universal-react-pouchdb) diff --git a/client/actions/index.ts b/client/actions/index.ts new file mode 100644 index 0000000..644f7ab --- /dev/null +++ b/client/actions/index.ts @@ -0,0 +1,13 @@ +import { createAction } from 'redux-actions'; + +export const addTodo = createAction('ADD_TODO', (title): { title: string } => ({ title })); +export const deleteTodo = createAction('DELETE_TODO', (id): { id: number } => ({ id })); +export const editTodo = createAction('EDIT_TODO', (id, title): { id: number; title: string } => ({ id, title })); +export const completeTodo = createAction('COMPLETE_TODO', (id): { id: number } => ({ id })); +export const completeAll = createAction('COMPLETE_ALL', (): {} => ({})); +export const clearCompleted = createAction('CLEAR_COMPLETED', (): {} => ({})); +export const showAll = createAction('SHOW_ALL', (): { filter: string } => ({ filter: 'show_all' })); +export const showActive = createAction('SHOW_ACTIVE', (): { filter: string } => ({ filter: 'show_active' })); +export const showCompleted = createAction('SHOW_COMPLETED', (): { filter: string } => ({ + filter: 'show_completed' +})); diff --git a/client/components/footer.tsx b/client/components/footer.tsx new file mode 100644 index 0000000..0ecdb7c --- /dev/null +++ b/client/components/footer.tsx @@ -0,0 +1,101 @@ +import React from 'react'; +import { AppContext } from '../store/state-mgmt'; +import { completeAll, clearCompleted } from '../events/client-events'; +import { SHOW_ALL, SHOW_COMPLETED, SHOW_ACTIVE } from '../constants/todo-filters'; + +import { State } from '../interfaces'; + +let FILTER_TITLES = { + [SHOW_ALL]: 'All', + [SHOW_ACTIVE]: 'Active', + [SHOW_COMPLETED]: 'Completed' +}; + +interface Props { + completed?: number; + count?: number; + filter?: string; + active?: number; +} + +function Footer({ filter, count, completed, active }: Props) { + + if (count) { + function RenderTodoCount() { + let itemWord = active === 1 ? 'item' : 'items'; + + return ( + + { active || 'No' } { itemWord } left + + ); + } + + function RenderFilterLink({ filterType }) { + let title = FILTER_TITLES[filterType]; + let isSelected = filter === filterType ? 'selected' : ''; + + return ( + + { title } + + ); + } + + function RenderClearButton() { + if (completed > 0) { + return ( + + ); + } else { + return null; + } + } + + return ( + + ); + } else { + return null; + } +} + +export default function () { + return ( + + { ({ todos, filter }) => { + let count = todos.length, + completed = todos.reduce((count, todo) => ( + todo.completed ? count + 1 : count + ), + 0 + ), + active = count - completed; + + return ( +