From ecd9a2237a1bdee3c0179af63b3e3e71d9839240 Mon Sep 17 00:00:00 2001 From: Yasith Jayawardana Date: Sat, 16 Jun 2018 15:54:47 +0530 Subject: [PATCH 01/11] create app --- .gitignore | 7 ++++ LICENSE | 21 +++++++++++ index.html | 17 +++++++++ package.json | 30 +++++++++++++++ src/components/App.tsx | 22 +++++++++++ src/components/CarContainer.tsx | 67 +++++++++++++++++++++++++++++++++ src/components/CarListItem.tsx | 66 ++++++++++++++++++++++++++++++++ src/data/actions.tsx | 26 +++++++++++++ src/data/constants.tsx | 3 ++ src/data/models.tsx | 19 ++++++++++ src/data/store.tsx | 38 +++++++++++++++++++ src/index.tsx | 12 ++++++ tsconfig.json | 13 +++++++ webpack.config.js | 34 +++++++++++++++++ 14 files changed, 375 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 index.html create mode 100644 package.json create mode 100644 src/components/App.tsx create mode 100644 src/components/CarContainer.tsx create mode 100644 src/components/CarListItem.tsx create mode 100644 src/data/actions.tsx create mode 100644 src/data/constants.tsx create mode 100644 src/data/models.tsx create mode 100644 src/data/store.tsx create mode 100644 src/index.tsx create mode 100644 tsconfig.json create mode 100644 webpack.config.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f10dfc8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +# Node.JS Specific +dist/ +node_modules/ +package-lock.json + +# VSCode +.vscode/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..edacd8e --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Valentino Gagliardi + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..9943521 --- /dev/null +++ b/index.html @@ -0,0 +1,17 @@ + + + + + Hello React! + + +
+ + + + + + + + + \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..8bf196d --- /dev/null +++ b/package.json @@ -0,0 +1,30 @@ +{ + "name": "reactreduxapp", + "version": "1.0.0", + "description": "React Redux App", + "repository": "git://github.com", + "main": "./src/index.js", + "scripts": { + "start:dev": "webpack-dev-server --mode development" + }, + "author": "YJ", + "license": "MIT", + "dependencies": { + "@types/react": "^16.3.17", + "@types/react-dom": "^16.0.6", + "@types/react-redux": "^6.0.2", + "react": "^16.4.1", + "react-dom": "^16.4.1", + "react-redux": "^5.0.7", + "redux": "^4.0.0" + }, + "devDependencies": { + "@types/uuid": "^3.4.3", + "awesome-typescript-loader": "^5.0.0", + "source-map-loader": "^0.2.3", + "typescript": "^2.9.2", + "webpack": "^4.12.0", + "webpack-cli": "^3.0.7", + "webpack-dev-server": "^3.1.4" + } +} diff --git a/src/components/App.tsx b/src/components/App.tsx new file mode 100644 index 0000000..213bf0a --- /dev/null +++ b/src/components/App.tsx @@ -0,0 +1,22 @@ +import * as React from "react"; +import CarContainer from "./CarContainer"; + +export interface AppProps { + compiler: string; + framework: string; +} + +export class App extends React.Component { + render() { + return ( +
+

+ Hello from {this.props.compiler} and {this.props.framework}! +

+
+ +
+
+ ); + } +} diff --git a/src/components/CarContainer.tsx b/src/components/CarContainer.tsx new file mode 100644 index 0000000..090b8d1 --- /dev/null +++ b/src/components/CarContainer.tsx @@ -0,0 +1,67 @@ +import * as React from "react"; +import { connect } from "react-redux"; +import * as Redux from "redux"; +import * as UUID from "uuid"; +import { Car, ApplicationState } from "../data/models"; +import { addCar } from "../data/actions"; +import CarListItem from "./CarListItem"; + +// Properties to get from Redux Store +interface StoreProps { + cars: Car[]; +} + +// Properties to get for Redux Dispactch +interface DispatchProps { + insertCar: (car: Car) => void; +} + +// Properties external to Redux Store +export interface OwnProps { + // No properties +} + +type CombinedProps = StoreProps & DispatchProps & OwnProps; + +class CarContainer extends React.Component { + constructor(props: CombinedProps) { + super(props); + } + + render(): JSX.Element { + return ( + + + + + + + + + + + + + {this.props.cars.forEach(car => )} + {this.props.children} + +
ManufacturerMakeModelYearEditDelete
+ ); + } +} + +function mapStateToProps( state: ApplicationState, ownProps: OwnProps ): StoreProps { + return { + cars: state.cars + }; +} + +function mapDispatchToProps( dispatch: Redux.Dispatch, ownProps: OwnProps ): DispatchProps { + return { + insertCar: car => dispatch(addCar(car)) + }; +} + +const wrapper = connect( mapStateToProps, mapDispatchToProps ); + +export default wrapper(CarContainer); diff --git a/src/components/CarListItem.tsx b/src/components/CarListItem.tsx new file mode 100644 index 0000000..da346d6 --- /dev/null +++ b/src/components/CarListItem.tsx @@ -0,0 +1,66 @@ +import * as React from "react"; +import { connect } from "react-redux"; +import * as Redux from "redux"; +import * as UUID from "uuid"; +import { updateCar, deleteCar } from "../data/actions"; +import { Car, ApplicationState } from "../data/models"; + +// Properties to get from Redux Store +interface StoreProps { + // No properties +} + +// Properties to get for Redux Dispatch +interface DispatchProps { + updateCar: (car: Car) => void; + deleteCar: (car: Car) => void; +} + +// Properties external to Redux Store +export interface OwnProps { + car: Car; +} + +type CombinedProps = StoreProps & DispatchProps & OwnProps; + +class CarListItem extends React.Component { + constructor(props: CombinedProps) { + super(props); + } + + handleDelete(event) { + const { manufacturer, make, model, year } = this.props.car; + const id = UUID.v4(); + } + + render(): JSX.Element { + const { manufacturer, make, model, year } = this.props.car; + return ( + + {manufacturer} + {make} + {model} + {year} + + + + ); + } +} + +function mapStateToProps(state: ApplicationState, ownProps: OwnProps): StoreProps { + return { + // Nothing to map + }; +} + +function mapDispatchToProps(dispatch: Redux.Dispatch, ownProps: OwnProps ): DispatchProps { + return { + updateCar: car => dispatch(updateCar(car)), + deleteCar: car => dispatch(deleteCar(car)) + }; +} + +const wrapper = connect( mapStateToProps, mapDispatchToProps ) + +export default wrapper(CarListItem); \ No newline at end of file diff --git a/src/data/actions.tsx b/src/data/actions.tsx new file mode 100644 index 0000000..4783267 --- /dev/null +++ b/src/data/actions.tsx @@ -0,0 +1,26 @@ +import { ADD_CAR, UPDATE_CAR, DELETE_CAR } from "./constants"; +import { Car, StoreAction } from "./models"; + +// Template Add New Car to Redux Store +export const addCar = function(car: Car): StoreAction { + return { + type: ADD_CAR, + payload: car + }; +}; + +// Template Update Existing Car in Redux Store +export const updateCar = function(car: Car): StoreAction { + return { + type: UPDATE_CAR, + payload: car + }; +}; + +// Template Delete Existing Car in Redux Store +export const deleteCar = function(car: Car): StoreAction { + return { + type: DELETE_CAR, + payload: car + }; +}; diff --git a/src/data/constants.tsx b/src/data/constants.tsx new file mode 100644 index 0000000..10539af --- /dev/null +++ b/src/data/constants.tsx @@ -0,0 +1,3 @@ +export const ADD_CAR = "ADD_CAR"; +export const UPDATE_CAR = "UPDATE_CAR"; +export const DELETE_CAR = "DELETE_CAR"; diff --git a/src/data/models.tsx b/src/data/models.tsx new file mode 100644 index 0000000..be6e433 --- /dev/null +++ b/src/data/models.tsx @@ -0,0 +1,19 @@ +import { Action } from "redux"; + +// Structure of Car +export interface Car { + manufacturer: String; + make: String; + model: String; + year: Number; +} + +// Structure of Application State +export interface ApplicationState { + cars: Car[]; +}; + +// Structure of Store Action +export interface StoreAction extends Action { + payload: V; +} diff --git a/src/data/store.tsx b/src/data/store.tsx new file mode 100644 index 0000000..866ac2f --- /dev/null +++ b/src/data/store.tsx @@ -0,0 +1,38 @@ +import { createStore, Reducer, Store } from "redux"; + +import { ADD_CAR, UPDATE_CAR, DELETE_CAR } from "./constants"; +import { ApplicationState, StoreAction } from "./models"; + +const initialState: ApplicationState = { + cars: [] +}; + +// Root Reducer for all actions in the application +function reduce (state: ApplicationState = initialState, action: StoreAction) : ApplicationState { + switch (action.type) { + case ADD_CAR: + // Return all cars plus the new one + return { + ...state, + cars: [...state.cars, action.payload] + }; + case UPDATE_CAR: + // Return all cars along with the updated (merged) car + return { + ...state, + cars: [...state.cars, action.payload] + }; + case DELETE_CAR: + // Return all cars except the deleted one + return { + ...state, + cars: [...state.cars, action.payload] + }; + default: + return state; + } +} + +const store : Store> = createStore(reduce); + +export default store; diff --git a/src/index.tsx b/src/index.tsx new file mode 100644 index 0000000..98c082d --- /dev/null +++ b/src/index.tsx @@ -0,0 +1,12 @@ +import * as React from "react"; +import * as ReactDOM from "react-dom"; +import { Provider } from "react-redux"; +import { App } from "./components/App"; +import Store from "./data/store"; + +ReactDOM.render( + + , + , + document.getElementById("example") +); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..15543b3 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "outDir": "./dist/", + "sourceMap": true, + "noImplicitAny": false, + "module": "commonjs", + "target": "es5", + "jsx": "react" + }, + "include": [ + "./src/**/*" + ], +} \ No newline at end of file diff --git a/webpack.config.js b/webpack.config.js new file mode 100644 index 0000000..1d7610d --- /dev/null +++ b/webpack.config.js @@ -0,0 +1,34 @@ +module.exports = { + entry: "./src/index.tsx", + output: { + filename: "bundle.js", + path: __dirname + "/dist" + }, + + // Enable sourcemaps for debugging webpack's output. + devtool: "source-map", + + resolve: { + // Add '.ts' and '.tsx' as resolvable extensions. + extensions: [".ts", ".tsx", ".js", ".json"] + }, + + module: { + rules: [ + // All files with a '.ts' or '.tsx' extension will be handled by 'awesome-typescript-loader'. + { test: /\.tsx?$/, loader: "awesome-typescript-loader" }, + + // All output '.js' files will have any sourcemaps re-processed by 'source-map-loader'. + { enforce: "pre", test: /\.js$/, loader: "source-map-loader" } + ] + }, + + // When importing a module whose path matches one of the following, just + // assume a corresponding global variable exists and use that instead. + // This is important because it allows us to avoid bundling all of our + // dependencies, which allows browsers to cache those libraries between builds. + externals: { + "react": "React", + "react-dom": "ReactDOM" + } +}; \ No newline at end of file From c1069c869b31c771278e45a4cfe7d574e263fa81 Mon Sep 17 00:00:00 2001 From: Yasith Jayawardana Date: Sat, 16 Jun 2018 16:37:53 +0530 Subject: [PATCH 02/11] partially implement workable insert. not refreshing still --- package.json | 3 ++- src/components/App.tsx | 1 + src/components/CarContainer.tsx | 11 ++++++++++- src/data/store.tsx | 26 ++++++++++++++++++-------- src/index.tsx | 2 +- 5 files changed, 32 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index 8bf196d..a54fae8 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,8 @@ "repository": "git://github.com", "main": "./src/index.js", "scripts": { - "start:dev": "webpack-dev-server --mode development" + "start": "webpack-dev-server --hot --inline --colors --open --port 3000", + "build": "webpack --config webpack.config.js --watch" }, "author": "YJ", "license": "MIT", diff --git a/src/components/App.tsx b/src/components/App.tsx index 213bf0a..1c41b3a 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -13,6 +13,7 @@ export class App extends React.Component {

Hello from {this.props.compiler} and {this.props.framework}!

+ {this.props.children}
diff --git a/src/components/CarContainer.tsx b/src/components/CarContainer.tsx index 090b8d1..d639b45 100644 --- a/src/components/CarContainer.tsx +++ b/src/components/CarContainer.tsx @@ -28,8 +28,15 @@ class CarContainer extends React.Component { super(props); } + handleAddCar(event) { + let car : Car = {make : "test", manufacturer: "test", model : "test", year : 2018}; + this.props.insertCar(car); + } + render(): JSX.Element { return ( +
+ {/* Table showing the contents */} @@ -42,10 +49,12 @@ class CarContainer extends React.Component { - {this.props.cars.forEach(car => )} + {this.props.cars.map(car => )} {this.props.children}
+ +
); } } diff --git a/src/data/store.tsx b/src/data/store.tsx index 866ac2f..64a650f 100644 --- a/src/data/store.tsx +++ b/src/data/store.tsx @@ -9,28 +9,38 @@ const initialState: ApplicationState = { // Root Reducer for all actions in the application function reduce (state: ApplicationState = initialState, action: StoreAction) : ApplicationState { + let nextState; switch (action.type) { - case ADD_CAR: + case ADD_CAR: { // Return all cars plus the new one - return { + nextState = { ...state, cars: [...state.cars, action.payload] }; - case UPDATE_CAR: + break; + } + case UPDATE_CAR: { // Return all cars along with the updated (merged) car - return { + nextState = { ...state, cars: [...state.cars, action.payload] }; - case DELETE_CAR: + break; + } + case DELETE_CAR: { // Return all cars except the deleted one - return { + nextState = { ...state, cars: [...state.cars, action.payload] }; - default: - return state; + break; + } + default: { + nextState = state; + break; + } } + return nextState; } const store : Store> = createStore(reduce); diff --git a/src/index.tsx b/src/index.tsx index 98c082d..a249d36 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -6,7 +6,7 @@ import Store from "./data/store"; ReactDOM.render( - , + , document.getElementById("example") ); From a59200e590f62ba2a3c9a87150f38a96036dedf8 Mon Sep 17 00:00:00 2001 From: Yasith Jayawardana Date: Sat, 16 Jun 2018 17:20:58 +0530 Subject: [PATCH 03/11] add insert/update modal --- src/components/CarContainer.tsx | 9 +++- src/components/CarEntryDialog.tsx | 85 +++++++++++++++++++++++++++++++ src/components/CarListItem.tsx | 9 +++- 3 files changed, 99 insertions(+), 4 deletions(-) create mode 100644 src/components/CarEntryDialog.tsx diff --git a/src/components/CarContainer.tsx b/src/components/CarContainer.tsx index d639b45..16980da 100644 --- a/src/components/CarContainer.tsx +++ b/src/components/CarContainer.tsx @@ -21,14 +21,19 @@ export interface OwnProps { // No properties } +// State inside the (if stateful) component +interface OwnState { + // No properties +} + type CombinedProps = StoreProps & DispatchProps & OwnProps; -class CarContainer extends React.Component { +class CarContainer extends React.Component { constructor(props: CombinedProps) { super(props); } - handleAddCar(event) { + handleAddCar(event) : void { let car : Car = {make : "test", manufacturer: "test", model : "test", year : 2018}; this.props.insertCar(car); } diff --git a/src/components/CarEntryDialog.tsx b/src/components/CarEntryDialog.tsx new file mode 100644 index 0000000..06e37d7 --- /dev/null +++ b/src/components/CarEntryDialog.tsx @@ -0,0 +1,85 @@ +import * as React from "react"; +import { Car } from "../data/models"; + +// Properties to get from Redux Store +interface StoreProps { + // No properties +} + +// Properties to get for Redux Dispatch +interface DispatchProps { + insertCar: (car: Car) => void; +} + +// Properties external to Redux Store +export interface OwnProps { + title : string; + description : string; +} + +// State inside the (if stateful) component +interface OwnState { + car: Car; +} + +type CombinedProps = StoreProps & DispatchProps & OwnProps; + + +export default class CarEntryDialog extends React.Component { + constructor(props: CombinedProps) { + super(props); + // Initially set all variables to empty + this.state = { + car: { manufacturer: "", make: "", model: "", year: undefined } + }; + } + + render() { + return ( +
+
+
+
+
{this.props.title}
+ +
+
+

{this.props.description}

+ {/* Input fields for each property should come here */} + + + + + + + + + + + + + + + + + + + +
Manufacturer
Make
Model
Year
+
+
+ + +
+
+
+
+ ); + } +} diff --git a/src/components/CarListItem.tsx b/src/components/CarListItem.tsx index da346d6..006f60c 100644 --- a/src/components/CarListItem.tsx +++ b/src/components/CarListItem.tsx @@ -21,14 +21,19 @@ export interface OwnProps { car: Car; } +// State inside the (if stateful) component +interface OwnState { + // No properties +} + type CombinedProps = StoreProps & DispatchProps & OwnProps; -class CarListItem extends React.Component { +class CarListItem extends React.Component { constructor(props: CombinedProps) { super(props); } - handleDelete(event) { + handleDelete(event) : void { const { manufacturer, make, model, year } = this.props.car; const id = UUID.v4(); } From 958a12787a5469bea80b7815ada332bb33132679 Mon Sep 17 00:00:00 2001 From: Yasith Jayawardana Date: Sat, 16 Jun 2018 18:15:50 +0530 Subject: [PATCH 04/11] fix config issues --- index.html | 43 +++++++++++++++++++++---------- package.json | 4 +-- src/components/App.tsx | 9 +++---- src/components/CarContainer.tsx | 6 ++--- src/components/CarEntryDialog.tsx | 2 +- src/components/CarListItem.tsx | 2 +- src/index.tsx | 2 +- tsconfig.json | 2 +- webpack.config.js | 11 ++++++-- 9 files changed, 51 insertions(+), 30 deletions(-) diff --git a/index.html b/index.html index 9943521..3b4010a 100644 --- a/index.html +++ b/index.html @@ -1,17 +1,32 @@ - - - - - Hello React! - - -
+ + - - - + + + + + + + + Cars + + + +
+ + + + + + + + + + + - - - \ No newline at end of file diff --git a/package.json b/package.json index a54fae8..e5922be 100644 --- a/package.json +++ b/package.json @@ -5,8 +5,8 @@ "repository": "git://github.com", "main": "./src/index.js", "scripts": { - "start": "webpack-dev-server --hot --inline --colors --open --port 3000", - "build": "webpack --config webpack.config.js --watch" + "start": "webpack --watch --mode=development & webpack-dev-server --hot", + "build": "webpack --mode=production" }, "author": "YJ", "license": "MIT", diff --git a/src/components/App.tsx b/src/components/App.tsx index 1c41b3a..46666c4 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -2,21 +2,20 @@ import * as React from "react"; import CarContainer from "./CarContainer"; export interface AppProps { - compiler: string; - framework: string; + title: string; } export class App extends React.Component { render() { return (
-

- Hello from {this.props.compiler} and {this.props.framework}! +

+ {this.props.title}

- {this.props.children}
+ {this.props.children}
); } diff --git a/src/components/CarContainer.tsx b/src/components/CarContainer.tsx index 16980da..e4f4adf 100644 --- a/src/components/CarContainer.tsx +++ b/src/components/CarContainer.tsx @@ -11,7 +11,7 @@ interface StoreProps { cars: Car[]; } -// Properties to get for Redux Dispactch +// Properties to get for Redux Dispatch interface DispatchProps { insertCar: (car: Car) => void; } @@ -21,7 +21,7 @@ export interface OwnProps { // No properties } -// State inside the (if stateful) component +// State inside (if stateful) Component interface OwnState { // No properties } @@ -54,7 +54,7 @@ class CarContainer extends React.Component { - {this.props.cars.map(car => )} + {this.props.cars.map(car => )} {this.props.children} diff --git a/src/components/CarEntryDialog.tsx b/src/components/CarEntryDialog.tsx index 06e37d7..c9e73d2 100644 --- a/src/components/CarEntryDialog.tsx +++ b/src/components/CarEntryDialog.tsx @@ -17,7 +17,7 @@ export interface OwnProps { description : string; } -// State inside the (if stateful) component +// State inside (if stateful) component interface OwnState { car: Car; } diff --git a/src/components/CarListItem.tsx b/src/components/CarListItem.tsx index 006f60c..5396bfa 100644 --- a/src/components/CarListItem.tsx +++ b/src/components/CarListItem.tsx @@ -21,7 +21,7 @@ export interface OwnProps { car: Car; } -// State inside the (if stateful) component +// State inside (if stateful) component interface OwnState { // No properties } diff --git a/src/index.tsx b/src/index.tsx index a249d36..7376b9c 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -6,7 +6,7 @@ import Store from "./data/store"; ReactDOM.render( - + , document.getElementById("example") ); diff --git a/tsconfig.json b/tsconfig.json index 15543b3..737a003 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,7 +4,7 @@ "sourceMap": true, "noImplicitAny": false, "module": "commonjs", - "target": "es5", + "target": "es6", "jsx": "react" }, "include": [ diff --git a/webpack.config.js b/webpack.config.js index 1d7610d..4be0a66 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -16,10 +16,17 @@ module.exports = { module: { rules: [ // All files with a '.ts' or '.tsx' extension will be handled by 'awesome-typescript-loader'. - { test: /\.tsx?$/, loader: "awesome-typescript-loader" }, + { + test: /\.tsx?$/, + loader: "awesome-typescript-loader" + }, // All output '.js' files will have any sourcemaps re-processed by 'source-map-loader'. - { enforce: "pre", test: /\.js$/, loader: "source-map-loader" } + { + enforce: "pre", + test: /\.js$/, + loader: "source-map-loader" + } ] }, From 0a331c4c83dd5c037f2469ec76dd2558b7d8e9f6 Mon Sep 17 00:00:00 2001 From: Yasith Jayawardana Date: Sat, 16 Jun 2018 19:24:21 +0530 Subject: [PATCH 05/11] add modal dialogs --- .gitignore | 3 + index.html | 3 + src/assets/css/fa-svg-with-js.css | 345 +++++++++++++++++++++++ src/assets/js/fontawesome-all.min.js | 5 + src/components/CarContainer.tsx | 13 +- src/components/CarEntryDialog.tsx | 134 +++++++-- src/components/CarListItem.tsx | 2 +- src/components/CarRemoveConfirmation.tsx | 177 ++++++++++++ src/data/models.tsx | 1 + 9 files changed, 653 insertions(+), 30 deletions(-) create mode 100644 src/assets/css/fa-svg-with-js.css create mode 100644 src/assets/js/fontawesome-all.min.js create mode 100644 src/components/CarRemoveConfirmation.tsx diff --git a/.gitignore b/.gitignore index f10dfc8..2a95a0e 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,6 @@ package-lock.json # VSCode .vscode/ + +# MacOS +.DS_Store diff --git a/index.html b/index.html index 3b4010a..027d140 100644 --- a/index.html +++ b/index.html @@ -8,6 +8,8 @@ + + Cars @@ -26,6 +28,7 @@ + diff --git a/src/assets/css/fa-svg-with-js.css b/src/assets/css/fa-svg-with-js.css new file mode 100644 index 0000000..264ae48 --- /dev/null +++ b/src/assets/css/fa-svg-with-js.css @@ -0,0 +1,345 @@ +/*! + * Font Awesome Free 5.0.13 by @fontawesome - https://fontawesome.com + * License - https://fontawesome.com/license (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) + */ +svg:not(:root).svg-inline--fa { + overflow: visible; } + +.svg-inline--fa { + display: inline-block; + font-size: inherit; + height: 1em; + overflow: visible; + vertical-align: -.125em; } + .svg-inline--fa.fa-lg { + vertical-align: -.225em; } + .svg-inline--fa.fa-w-1 { + width: 0.0625em; } + .svg-inline--fa.fa-w-2 { + width: 0.125em; } + .svg-inline--fa.fa-w-3 { + width: 0.1875em; } + .svg-inline--fa.fa-w-4 { + width: 0.25em; } + .svg-inline--fa.fa-w-5 { + width: 0.3125em; } + .svg-inline--fa.fa-w-6 { + width: 0.375em; } + .svg-inline--fa.fa-w-7 { + width: 0.4375em; } + .svg-inline--fa.fa-w-8 { + width: 0.5em; } + .svg-inline--fa.fa-w-9 { + width: 0.5625em; } + .svg-inline--fa.fa-w-10 { + width: 0.625em; } + .svg-inline--fa.fa-w-11 { + width: 0.6875em; } + .svg-inline--fa.fa-w-12 { + width: 0.75em; } + .svg-inline--fa.fa-w-13 { + width: 0.8125em; } + .svg-inline--fa.fa-w-14 { + width: 0.875em; } + .svg-inline--fa.fa-w-15 { + width: 0.9375em; } + .svg-inline--fa.fa-w-16 { + width: 1em; } + .svg-inline--fa.fa-w-17 { + width: 1.0625em; } + .svg-inline--fa.fa-w-18 { + width: 1.125em; } + .svg-inline--fa.fa-w-19 { + width: 1.1875em; } + .svg-inline--fa.fa-w-20 { + width: 1.25em; } + .svg-inline--fa.fa-pull-left { + margin-right: .3em; + width: auto; } + .svg-inline--fa.fa-pull-right { + margin-left: .3em; + width: auto; } + .svg-inline--fa.fa-border { + height: 1.5em; } + .svg-inline--fa.fa-li { + width: 2em; } + .svg-inline--fa.fa-fw { + width: 1.25em; } + +.fa-layers svg.svg-inline--fa { + bottom: 0; + left: 0; + margin: auto; + position: absolute; + right: 0; + top: 0; } + +.fa-layers { + display: inline-block; + height: 1em; + position: relative; + text-align: center; + vertical-align: -.125em; + width: 1em; } + .fa-layers svg.svg-inline--fa { + -webkit-transform-origin: center center; + transform-origin: center center; } + +.fa-layers-text, .fa-layers-counter { + display: inline-block; + position: absolute; + text-align: center; } + +.fa-layers-text { + left: 50%; + top: 50%; + -webkit-transform: translate(-50%, -50%); + transform: translate(-50%, -50%); + -webkit-transform-origin: center center; + transform-origin: center center; } + +.fa-layers-counter { + background-color: #ff253a; + border-radius: 1em; + -webkit-box-sizing: border-box; + box-sizing: border-box; + color: #fff; + height: 1.5em; + line-height: 1; + max-width: 5em; + min-width: 1.5em; + overflow: hidden; + padding: .25em; + right: 0; + text-overflow: ellipsis; + top: 0; + -webkit-transform: scale(0.25); + transform: scale(0.25); + -webkit-transform-origin: top right; + transform-origin: top right; } + +.fa-layers-bottom-right { + bottom: 0; + right: 0; + top: auto; + -webkit-transform: scale(0.25); + transform: scale(0.25); + -webkit-transform-origin: bottom right; + transform-origin: bottom right; } + +.fa-layers-bottom-left { + bottom: 0; + left: 0; + right: auto; + top: auto; + -webkit-transform: scale(0.25); + transform: scale(0.25); + -webkit-transform-origin: bottom left; + transform-origin: bottom left; } + +.fa-layers-top-right { + right: 0; + top: 0; + -webkit-transform: scale(0.25); + transform: scale(0.25); + -webkit-transform-origin: top right; + transform-origin: top right; } + +.fa-layers-top-left { + left: 0; + right: auto; + top: 0; + -webkit-transform: scale(0.25); + transform: scale(0.25); + -webkit-transform-origin: top left; + transform-origin: top left; } + +.fa-lg { + font-size: 1.33333em; + line-height: 0.75em; + vertical-align: -.0667em; } + +.fa-xs { + font-size: .75em; } + +.fa-sm { + font-size: .875em; } + +.fa-1x { + font-size: 1em; } + +.fa-2x { + font-size: 2em; } + +.fa-3x { + font-size: 3em; } + +.fa-4x { + font-size: 4em; } + +.fa-5x { + font-size: 5em; } + +.fa-6x { + font-size: 6em; } + +.fa-7x { + font-size: 7em; } + +.fa-8x { + font-size: 8em; } + +.fa-9x { + font-size: 9em; } + +.fa-10x { + font-size: 10em; } + +.fa-fw { + text-align: center; + width: 1.25em; } + +.fa-ul { + list-style-type: none; + margin-left: 2.5em; + padding-left: 0; } + .fa-ul > li { + position: relative; } + +.fa-li { + left: -2em; + position: absolute; + text-align: center; + width: 2em; + line-height: inherit; } + +.fa-border { + border: solid 0.08em #eee; + border-radius: .1em; + padding: .2em .25em .15em; } + +.fa-pull-left { + float: left; } + +.fa-pull-right { + float: right; } + +.fa.fa-pull-left, +.fas.fa-pull-left, +.far.fa-pull-left, +.fal.fa-pull-left, +.fab.fa-pull-left { + margin-right: .3em; } + +.fa.fa-pull-right, +.fas.fa-pull-right, +.far.fa-pull-right, +.fal.fa-pull-right, +.fab.fa-pull-right { + margin-left: .3em; } + +.fa-spin { + -webkit-animation: fa-spin 2s infinite linear; + animation: fa-spin 2s infinite linear; } + +.fa-pulse { + -webkit-animation: fa-spin 1s infinite steps(8); + animation: fa-spin 1s infinite steps(8); } + +@-webkit-keyframes fa-spin { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); } + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); } } + +@keyframes fa-spin { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); } + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); } } + +.fa-rotate-90 { + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=1)"; + -webkit-transform: rotate(90deg); + transform: rotate(90deg); } + +.fa-rotate-180 { + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2)"; + -webkit-transform: rotate(180deg); + transform: rotate(180deg); } + +.fa-rotate-270 { + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=3)"; + -webkit-transform: rotate(270deg); + transform: rotate(270deg); } + +.fa-flip-horizontal { + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)"; + -webkit-transform: scale(-1, 1); + transform: scale(-1, 1); } + +.fa-flip-vertical { + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)"; + -webkit-transform: scale(1, -1); + transform: scale(1, -1); } + +.fa-flip-horizontal.fa-flip-vertical { + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)"; + -webkit-transform: scale(-1, -1); + transform: scale(-1, -1); } + +:root .fa-rotate-90, +:root .fa-rotate-180, +:root .fa-rotate-270, +:root .fa-flip-horizontal, +:root .fa-flip-vertical { + -webkit-filter: none; + filter: none; } + +.fa-stack { + display: inline-block; + height: 2em; + position: relative; + width: 2em; } + +.fa-stack-1x, +.fa-stack-2x { + bottom: 0; + left: 0; + margin: auto; + position: absolute; + right: 0; + top: 0; } + +.svg-inline--fa.fa-stack-1x { + height: 1em; + width: 1em; } + +.svg-inline--fa.fa-stack-2x { + height: 2em; + width: 2em; } + +.fa-inverse { + color: #fff; } + +.sr-only { + border: 0; + clip: rect(0, 0, 0, 0); + height: 1px; + margin: -1px; + overflow: hidden; + padding: 0; + position: absolute; + width: 1px; } + +.sr-only-focusable:active, .sr-only-focusable:focus { + clip: auto; + height: auto; + margin: 0; + overflow: visible; + position: static; + width: auto; } diff --git a/src/assets/js/fontawesome-all.min.js b/src/assets/js/fontawesome-all.min.js new file mode 100644 index 0000000..a382743 --- /dev/null +++ b/src/assets/js/fontawesome-all.min.js @@ -0,0 +1,5 @@ +/*! + * Font Awesome Free 5.0.13 by @fontawesome - https://fontawesome.com + * License - https://fontawesome.com/license (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) + */ +!function(){"use strict";var c={};try{"undefined"!=typeof window&&(c=window)}catch(c){}var l=(c.navigator||{}).userAgent,h=void 0===l?"":l,v=c,z=(~h.indexOf("MSIE")||h.indexOf("Trident/"),"___FONT_AWESOME___"),e=function(){try{return!0}catch(c){return!1}}(),m=[1,2,3,4,5,6,7,8,9,10],a=m.concat([11,12,13,14,15,16,17,18,19,20]);["xs","sm","lg","fw","ul","li","border","pull-left","pull-right","spin","pulse","rotate-90","rotate-180","rotate-270","flip-horizontal","flip-vertical","stack","stack-1x","stack-2x","inverse","layers","layers-text","layers-counter"].concat(m.map(function(c){return c+"x"})).concat(a.map(function(c){return"w-"+c}));var s=v||{};s[z]||(s[z]={}),s[z].styles||(s[z].styles={}),s[z].hooks||(s[z].hooks={}),s[z].shims||(s[z].shims=[]);var t=s[z],f=Object.assign||function(c){for(var l=1;l>>0;h--;)l[h]=c[h];return l}function X(c){return c.classList?D(c.classList):(c.getAttribute("class")||"").split(" ").filter(function(c){return c})}function Y(c,l){var h,v=l.split("-"),z=v[0],e=v.slice(1).join("-");return z!==c||""===e||(h=e,~d.indexOf(h))?null:e}function U(c){return(""+c).replace(/&/g,"&").replace(/"/g,""").replace(/'/g,"'").replace(//g,">")}function K(h){return Object.keys(h||{}).reduce(function(c,l){return c+(l+": ")+h[l]+";"},"")}function G(c){return c.size!==I.size||c.x!==I.x||c.y!==I.y||c.rotate!==I.rotate||c.flipX||c.flipY}function J(c){var l=c.transform,h=c.containerWidth,v=c.iconWidth;return{outer:{transform:"translate("+h/2+" 256)"},inner:{transform:"translate("+32*l.x+", "+32*l.y+") "+" "+("scale("+l.size/16*(l.flipX?-1:1)+", "+l.size/16*(l.flipY?-1:1)+") ")+" "+("rotate("+l.rotate+" 0 0)")},path:{transform:"translate("+v/2*-1+" -256)"}}}var Q={x:0,y:0,width:"100%",height:"100%"},Z=function(c){var l=c.children,h=c.attributes,v=c.main,z=c.mask,e=c.transform,m=v.width,a=v.icon,s=z.width,t=z.icon,f=J({transform:e,containerWidth:s,iconWidth:m}),M={tag:"rect",attributes:S({},Q,{fill:"white"})},r={tag:"g",attributes:S({},f.inner),children:[{tag:"path",attributes:S({},a.attributes,f.path,{fill:"black"})}]},H={tag:"g",attributes:S({},f.outer),children:[r]},i="mask-"+B(),n="clip-"+B(),V={tag:"defs",children:[{tag:"clipPath",attributes:{id:n},children:[t]},{tag:"mask",attributes:S({},Q,{id:i,maskUnits:"userSpaceOnUse",maskContentUnits:"userSpaceOnUse"}),children:[M,H]}]};return l.push(V,{tag:"rect",attributes:S({fill:"currentColor","clip-path":"url(#"+n+")",mask:"url(#"+i+")"},Q)}),{children:l,attributes:h}},$=function(c){var l=c.children,h=c.attributes,v=c.main,z=c.transform,e=K(c.styles);if(0"+m.map(uc).join("")+""}var dc=function(){};function pc(c){return"string"==typeof(c.getAttribute?c.getAttribute(g):null)}var bc={replace:function(c){var l=c[0],h=c[1].map(function(c){return uc(c)}).join("\n");if(l.parentNode&&l.outerHTML)l.outerHTML=h+(O.keepOriginalSource&&"svg"!==l.tagName.toLowerCase()?"\x3c!-- "+l.outerHTML+" --\x3e":"");else if(l.parentNode){var v=document.createElement("span");l.parentNode.replaceChild(v,l),v.outerHTML=h}},nest:function(c){var l=c[0],h=c[1];if(~X(l).indexOf(O.replacementClass))return bc.replace(c);var v=new RegExp(O.familyPrefix+"-.*");delete h[0].attributes.style;var z=h[0].attributes.class.split(" ").reduce(function(c,l){return l===O.replacementClass||l.match(v)?c.toSvg.push(l):c.toNode.push(l),c},{toNode:[],toSvg:[]});h[0].attributes.class=z.toSvg.join(" ");var e=h.map(function(c){return uc(c)}).join("\n");l.setAttribute("class",z.toNode.join(" ")),l.setAttribute(g,""),l.innerHTML=e}};function gc(h,c){var v="function"==typeof c?c:dc;0===h.length?v():(a.requestAnimationFrame||function(c){return c()})(function(){var c=!0===O.autoReplaceSvg?bc.replace:bc[O.autoReplaceSvg]||bc.replace,l=sc.begin("mutate");h.map(c),l(),v()})}var wc=!1;var yc=null;var Sc=function(c){var l=c.getAttribute("style"),h=[];return l&&(h=l.split(";").reduce(function(c,l){var h=l.split(":"),v=h[0],z=h.slice(1);return v&&0li{position:relative}.fa-li{left:-2em;position:absolute;text-align:center;width:2em;line-height:inherit}.fa-border{border:solid .08em #eee;border-radius:.1em;padding:.2em .25em .15em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left,.fab.fa-pull-left,.fal.fa-pull-left,.far.fa-pull-left,.fas.fa-pull-left{margin-right:.3em}.fa.fa-pull-right,.fab.fa-pull-right,.fal.fa-pull-right,.far.fa-pull-right,.fas.fa-pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}.fa-rotate-90{-webkit-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-webkit-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-webkit-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-webkit-transform:scale(-1,1);transform:scale(-1,1)}.fa-flip-vertical{-webkit-transform:scale(1,-1);transform:scale(1,-1)}.fa-flip-horizontal.fa-flip-vertical{-webkit-transform:scale(-1,-1);transform:scale(-1,-1)}:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-rotate-90{-webkit-filter:none;filter:none}.fa-stack{display:inline-block;height:2em;position:relative;width:2em}.fa-stack-1x,.fa-stack-2x{bottom:0;left:0;margin:auto;position:absolute;right:0;top:0}.svg-inline--fa.fa-stack-1x{height:1em;width:1em}.svg-inline--fa.fa-stack-2x{height:2em;width:2em}.fa-inverse{color:#fff}.sr-only{border:0;clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.sr-only-focusable:active,.sr-only-focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}";if("fa"!==l||h!==c){var z=new RegExp("\\.fa\\-","g"),e=new RegExp("\\."+c,"g");v=v.replace(z,"."+l+"-").replace(e,"."+h)}return v};var Qc=function(){function c(){w(this,c),this.definitions={}}return y(c,[{key:"add",value:function(){for(var l=this,c=arguments.length,h=Array(c),v=0;v { super(props); } - handleAddCar(event) : void { - let car : Car = {make : "test", manufacturer: "test", model : "test", year : 2018}; - this.props.insertCar(car); - } - render(): JSX.Element { return (
{/* Table showing the contents */} - + +
@@ -58,7 +54,8 @@ class CarContainer extends React.Component { {this.props.children}
Manufacturer
- + +
); } diff --git a/src/components/CarEntryDialog.tsx b/src/components/CarEntryDialog.tsx index c9e73d2..43ddd3c 100644 --- a/src/components/CarEntryDialog.tsx +++ b/src/components/CarEntryDialog.tsx @@ -1,5 +1,8 @@ import * as React from "react"; -import { Car } from "../data/models"; +import { connect } from "react-redux"; +import * as Redux from "redux"; +import * as UUID from "uuid"; +import { ApplicationState, Car } from "../data/models"; // Properties to get from Redux Store interface StoreProps { @@ -8,73 +11,137 @@ interface StoreProps { // Properties to get for Redux Dispatch interface DispatchProps { - insertCar: (car: Car) => void; + // No properties } // Properties external to Redux Store export interface OwnProps { - title : string; - description : string; + id: string; + mode: "insert" | "update"; + car?: Car; + onSave: (car: Car) => void; } // State inside (if stateful) component interface OwnState { - car: Car; + newCar: Car; } type CombinedProps = StoreProps & DispatchProps & OwnProps; - -export default class CarEntryDialog extends React.Component { +class CarEntryDialog extends React.Component { constructor(props: CombinedProps) { super(props); // Initially set all variables to empty this.state = { - car: { manufacturer: "", make: "", model: "", year: undefined } + newCar: this.props.car + ? { ...this.props.car } + : { id: UUID.v4(), manufacturer: "", make: "", model: "", year: 2018 } }; } - + + handleEntry = (event) => { + let manufacturer = this.refs.manufacturer["value"]; + let make = this.refs.make["value"]; + let model = this.refs.model["value"]; + let year = this.refs.year["value"]; + this.setState( + { newCar: { ...this.state.newCar, manufacturer, make, model, year } }, + () => this.props.onSave(this.state.newCar) + ); + } + + getTitle = () => + this.props.mode == "insert" ? "Add New Item" : "Update Item"; + getSubtitle = () => + this.props.mode == "insert" + ? "Enter item details and click on Save to Add." + : "Please update the details and click on Save."; + render() { return ( -
+