From a0ebaf080340b0d83c624e59def9e885326b2e50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Hauszknecht?= Date: Wed, 12 Jul 2017 16:37:57 +0200 Subject: [PATCH] init commit --- .babelrc | 8 +++++ .gitignore | 3 ++ README.md | 15 +++++++++ index.html | 17 ++++++++++ package.json | 51 ++++++++++++++++++++++++++++ src/actions.js | 59 ++++++++++++++++++++++++++++++++ src/api.js | 38 +++++++++++++++++++++ src/components/App.jsx | 44 ++++++++++++++++++++++++ src/components/Item.jsx | 74 +++++++++++++++++++++++++++++++++++++++++ src/index.jsx | 12 +++++++ src/main.js | 57 +++++++++++++++++++++++++++++++ src/store.js | 9 +++++ style.css | 4 +++ 13 files changed, 391 insertions(+) create mode 100644 .babelrc create mode 100644 .gitignore create mode 100644 README.md create mode 100644 index.html create mode 100644 package.json create mode 100644 src/actions.js create mode 100644 src/api.js create mode 100644 src/components/App.jsx create mode 100644 src/components/Item.jsx create mode 100644 src/index.jsx create mode 100644 src/main.js create mode 100644 src/store.js create mode 100644 style.css diff --git a/.babelrc b/.babelrc new file mode 100644 index 0000000..b8af819 --- /dev/null +++ b/.babelrc @@ -0,0 +1,8 @@ +{ + "plugins": [ + "transform-react-jsx", + "transform-object-rest-spread", + "transform-es2015-modules-commonjs", + "transform-class-properties" + ] +} \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..40ef805 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +package-lock.json +node_modules +lib \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..3379929 --- /dev/null +++ b/README.md @@ -0,0 +1,15 @@ +# repatch-example-electron-app + +## Running the example + +``` +npm install +npm run build +npm start +``` + +## Autobuild + +``` +npm run build:w +``` diff --git a/index.html b/index.html new file mode 100644 index 0000000..158dfd0 --- /dev/null +++ b/index.html @@ -0,0 +1,17 @@ + + + + + + Hello World! + + + + +
+ + + + \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..5b8d9c6 --- /dev/null +++ b/package.json @@ -0,0 +1,51 @@ +{ + "name": "repatch-example-electron-app", + "version": "0.0.1", + "description": "Todo electron app to representing repatch", + "main": "lib/main", + "scripts": { + "babel": "babel src -d lib", + "babel:w": "babel src -d lib -w", + "build": "rm -rf lib && npm run babel", + "build:w": "npm run babel:w", + "start": "electron lib/main", + "start-prod": "NODE_ENV=production npm start" + }, + "authors": [ + "Péter Hauszknecht (https://github.com/jayhasyee)" + ], + "keywords": [ + "dispatch", + "redux", + "reducer", + "state", + "predictable", + "functional", + "immutable", + "flux" + ], + "repository": { + "type": "git", + "url": "https://github.com/jaystack/repatch-example-electron-app.git" + }, + "license": "MIT", + "bugs": { + "url": "https://github.com/jaystack/repatch-example-electron-app/issues" + }, + "dependencies": { + "electron": "^1.7.4", + "prop-types": "^15.5.10", + "react": "^15.6.1", + "react-dom": "^15.6.1", + "react-redux": "^5.0.5", + "redux": "^3.7.1", + "repatch": "0.0.11" + }, + "devDependencies": { + "babel-cli": "^6.24.1", + "babel-plugin-transform-class-properties": "^6.24.1", + "babel-plugin-transform-es2015-modules-commonjs": "^6.24.1", + "babel-plugin-transform-object-rest-spread": "^6.23.0", + "babel-plugin-transform-react-jsx": "^6.24.1" + } +} diff --git a/src/actions.js b/src/actions.js new file mode 100644 index 0000000..63ad809 --- /dev/null +++ b/src/actions.js @@ -0,0 +1,59 @@ +function call(method, path, query, body) { + return () => async (dispatch, getState, api) => { + try { + return await api[path][method.toLowerCase()](query, body) + } catch (error) { + dispatch(state => ({ ...state, error: error.message })) + } finally { + dispatch(state => ({ ...state, isFetching: false })) + } + } +} + +export function fetchTodos() { + return () => async dispatch => { + dispatch(cancelRemovingTodo()) + const todos = await dispatch(call('GET', '/todos')) + dispatch(state => ({ ...state, todos })) + } +} + +export function addTodo() { + return () => async dispatch => { + dispatch(cancelRemovingTodo()) + await dispatch(call('POST', '/todos')) + await dispatch(fetchTodos()) + } +} + +export function updateTodo(id, message) { + return () => async dispatch => { + dispatch(cancelRemovingTodo()) + await dispatch(call('PUT', '/todos', { id }, { message })) + await dispatch(fetchTodos()) + } +} + +export function checkTodo(id) { + return () => async dispatch => { + dispatch(cancelRemovingTodo()) + await dispatch(call('PATCH', '/todos', { id })) + await dispatch(fetchTodos()) + } +} + +export function removeTodo(removingTodoId) { + return state => ({ ...state, removingTodoId }) +} + +export function cancelRemovingTodo() { + return state => ({ ...state, removingTodoId: null }) +} + +export function confirmRemovingTodo() { + return () => async (dispatch, getState) => { + const id = getState().removingTodoId + await dispatch(call('DELETE', '/todos', { id })) + await dispatch(fetchTodos()) + } +} diff --git a/src/api.js b/src/api.js new file mode 100644 index 0000000..5ad4631 --- /dev/null +++ b/src/api.js @@ -0,0 +1,38 @@ +const todos = [ + { + id: Math.random().toString(), + message: 'shopping', + checked: false + }, + { + id: Math.random().toString(), + message: 'carwash', + checked: false + }, + { + id: Math.random().toString(), + message: 'haircut', + checked: true + } +] + +export default { + '/todos': { + get: async () => JSON.parse(JSON.stringify(todos)), + post: async () => { + todos.push({ id: Math.random().toString(), message: '', checked: false }) + }, + put: async ({ id }, { message }) => { + const todo = todos.find(todo => todo.id === id) + todo.message = message + }, + patch: async ({ id }) => { + const todo = todos.find(todo => todo.id === id) + todo.checked = !todo.checked + }, + delete: async ({ id }) => { + const todoIndex = todos.findIndex(todo => todo.id === id) + todos.splice(todoIndex, 1) + } + } +} diff --git a/src/components/App.jsx b/src/components/App.jsx new file mode 100644 index 0000000..c19e03b --- /dev/null +++ b/src/components/App.jsx @@ -0,0 +1,44 @@ +import * as React from 'react' +import PropTypes from 'prop-types' +import { connect } from 'react-redux' +import { fetchTodos, addTodo } from '../actions' +import Item from './Item' + +class App extends React.PureComponent { + static propTypes = { + todos: PropTypes.arrayOf( + PropTypes.shape({ + id: PropTypes.string, + message: PropTypes.string, + checked: PropTypes.bool + }) + ), + fetchTodos: PropTypes.func, + addTodo: PropTypes.func + } + + componentDidMount() { + this.props.fetchTodos() + } + + render() { + const { todos, addTodo } = this.props + return ( +
+ {todos.map((todo, index) => )} + +
+ ) + } +} + +const mapStateToProps = state => ({ + todos: state.todos +}) + +const mapDispatchToProps = { + fetchTodos, + addTodo +} + +export default connect(mapStateToProps, mapDispatchToProps)(App) diff --git a/src/components/Item.jsx b/src/components/Item.jsx new file mode 100644 index 0000000..77f4ce2 --- /dev/null +++ b/src/components/Item.jsx @@ -0,0 +1,74 @@ +import * as React from 'react' +import PropTypes from 'prop-types' +import { connect } from 'react-redux' +import { + checkTodo, + updateTodo, + removeTodo, + cancelRemovingTodo, + confirmRemovingTodo +} from '../actions' + +class Item extends React.PureComponent { + static propTypes = { + todoIndex: PropTypes.number, + todo: PropTypes.shape({ + id: PropTypes.string, + message: PropTypes.string, + checked: PropTypes.bool + }), + isRemovingTodo: PropTypes.bool, + checkTodo: PropTypes.func, + updateTodo: PropTypes.func, + removeTodo: PropTypes.func, + cancelRemovingTodo: PropTypes.func, + confirmRemovingTodo: PropTypes.func + } + + render() { + const { + todo: { id, message, checked }, + isRemovingTodo, + checkTodo, + updateTodo, + removeTodo, + cancelRemovingTodo, + confirmRemovingTodo + } = this.props + return ( +
+ checkTodo(id)} + /> + updateTodo(id, evt.target.value)} + /> + {isRemovingTodo + ? + + + + : } +
+ ) + } +} + +const mapStateToProps = (state, { todoIndex }) => ({ + todo: state.todos[todoIndex], + isRemovingTodo: state.removingTodoId === state.todos[todoIndex].id +}) + +const mapDispatchToProps = { + checkTodo, + updateTodo, + removeTodo, + cancelRemovingTodo, + confirmRemovingTodo +} + +export default connect(mapStateToProps, mapDispatchToProps)(Item) diff --git a/src/index.jsx b/src/index.jsx new file mode 100644 index 0000000..8caa205 --- /dev/null +++ b/src/index.jsx @@ -0,0 +1,12 @@ +import * as React from 'react' +import * as ReactDOM from 'react-dom' +import { Provider } from 'react-redux' +import store from './store' +import App from './components/App' + +ReactDOM.render( + + + , + document.getElementById('app') +) diff --git a/src/main.js b/src/main.js new file mode 100644 index 0000000..972da13 --- /dev/null +++ b/src/main.js @@ -0,0 +1,57 @@ +import { app, BrowserWindow } from 'electron' +import * as path from 'path' +import * as url from 'url' + +// Keep a global reference of the window object, if you don't, the window will +// be closed automatically when the JavaScript object is garbage collected. +let win + +function createWindow() { + // Create the browser window. + win = new BrowserWindow({ width: 800, height: 600 }) + + // and load the index.html of the app. + win.loadURL( + url.format({ + pathname: path.join(__dirname, '../index.html'), + protocol: 'file:', + slashes: true + }) + ) + + // Open the DevTools. + //win.webContents.openDevTools(); + + // Emitted when the window is closed. + win.on('closed', () => { + // Dereference the window object, usually you would store windows + // in an array if your app supports multi windows, this is the time + // when you should delete the corresponding element. + win = null + }) +} + +// This method will be called when Electron has finished +// initialization and is ready to create browser windows. +// Some APIs can only be used after this event occurs. +app.on('ready', createWindow) + +// Quit when all windows are closed. +app.on('window-all-closed', () => { + // On macOS it is common for applications and their menu bar + // to stay active until the user quits explicitly with Cmd + Q + if (process.platform !== 'darwin') { + app.quit() + } +}) + +app.on('activate', () => { + // On macOS it's common to re-create a window in the app when the + // dock icon is clicked and there are no other windows open. + if (win === null) { + createWindow() + } +}) + +// In this file you can include the rest of your app's specific main process +// code. You can also put them in separate files and require them here. diff --git a/src/store.js b/src/store.js new file mode 100644 index 0000000..a044c4b --- /dev/null +++ b/src/store.js @@ -0,0 +1,9 @@ +import Store, { thunk, Reducer, Thunk } from 'repatch' +import api from './api' + +export default new Store({ + todos: [], + isFetching: false, + error: null, + removingTodoId: null +}).addMiddleware(thunk.withExtraArgument(api)) diff --git a/style.css b/style.css new file mode 100644 index 0000000..b554bfa --- /dev/null +++ b/style.css @@ -0,0 +1,4 @@ +.item > input[type=text] { + border: none; + outline: none; +}