Convert app to TypeScript
This commit is contained in:
parent
39f0542ef2
commit
cb9f3ec3d1
42 changed files with 485 additions and 418 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -9,3 +9,4 @@ npm-debug.log
|
|||
|
||||
.cache
|
||||
_build
|
||||
.vscode
|
||||
|
|
|
|||
|
|
@ -9,5 +9,5 @@
|
|||
<body>
|
||||
<div id="brutal"></div>
|
||||
</body>
|
||||
<script src="./src/index.js"></script>
|
||||
<script src="./src/index.tsx"></script>
|
||||
</html>
|
||||
|
|
|
|||
6
package-lock.json
generated
6
package-lock.json
generated
|
|
@ -8758,6 +8758,12 @@
|
|||
"integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=",
|
||||
"dev": true
|
||||
},
|
||||
"typescript": {
|
||||
"version": "3.6.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.6.3.tgz",
|
||||
"integrity": "sha512-N7bceJL1CtRQ2RiG0AQME13ksR7DiuQh/QehubYcghzv20tnh+MQnQIuJddTmsbqYj+dztchykemz0zFzlvdQw==",
|
||||
"dev": true
|
||||
},
|
||||
"typescript-compare": {
|
||||
"version": "0.0.2",
|
||||
"resolved": "https://registry.npmjs.org/typescript-compare/-/typescript-compare-0.0.2.tgz",
|
||||
|
|
|
|||
69
package.json
69
package.json
|
|
@ -1,36 +1,37 @@
|
|||
{
|
||||
"scripts": {
|
||||
"start": "cross-env NODE_ENV=development parcel index.html --public-url / --out-dir _build",
|
||||
"build": "cross-env NODE_ENV=production parcel build index.html --public-url /urban-enigma/ --out-dir docs --no-source-maps --no-content-hash",
|
||||
"lint": "cross-env NODE_ENV=development prettier --check src/**/* assets/*.css"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.4.0",
|
||||
"@babel/plugin-transform-runtime": "^7.4.0",
|
||||
"@babel/preset-env": "^7.5.5",
|
||||
"@babel/preset-react": "^7.0.0",
|
||||
"autoprefixer": "^9.5.0",
|
||||
"cross-env": "^5.2.0",
|
||||
"jest": "^24.9.0",
|
||||
"parcel-bundler": "^1.12.3",
|
||||
"parcel-plugin-static-files-copy": "^2.3.1",
|
||||
"prettier": "^1.18.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime-corejs2": "^7.4.2",
|
||||
"react": "^16.8.5",
|
||||
"react-dom": "^16.8.5",
|
||||
"react-redux": "^6.0.1",
|
||||
"redux": "^4.0.1",
|
||||
"redux-saga": "^1.0.2"
|
||||
},
|
||||
"postcss": {
|
||||
"modules": false,
|
||||
"plugins": {
|
||||
"autoprefixer": {}
|
||||
}
|
||||
},
|
||||
"browserslist": [
|
||||
"defaults"
|
||||
]
|
||||
"scripts": {
|
||||
"start": "cross-env NODE_ENV=development parcel index.html --public-url / --out-dir _build",
|
||||
"build": "cross-env NODE_ENV=production parcel build index.html --public-url /urban-enigma/ --out-dir docs --no-source-maps --no-content-hash",
|
||||
"lint": "cross-env NODE_ENV=development prettier --check src/**/*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.4.0",
|
||||
"@babel/plugin-transform-runtime": "^7.4.0",
|
||||
"@babel/preset-env": "^7.5.5",
|
||||
"@babel/preset-react": "^7.0.0",
|
||||
"autoprefixer": "^9.5.0",
|
||||
"cross-env": "^5.2.0",
|
||||
"jest": "^24.9.0",
|
||||
"parcel-bundler": "^1.12.3",
|
||||
"parcel-plugin-static-files-copy": "^2.3.1",
|
||||
"prettier": "^1.18.2",
|
||||
"typescript": "^3.6.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime-corejs2": "^7.4.2",
|
||||
"react": "^16.8.5",
|
||||
"react-dom": "^16.8.5",
|
||||
"react-redux": "^6.0.1",
|
||||
"redux": "^4.0.1",
|
||||
"redux-saga": "^1.0.2"
|
||||
},
|
||||
"postcss": {
|
||||
"modules": false,
|
||||
"plugins": {
|
||||
"autoprefixer": {}
|
||||
}
|
||||
},
|
||||
"browserslist": [
|
||||
"defaults"
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,38 +0,0 @@
|
|||
export const LOAD_ALBUMS = 'LOAD_ALBUMS';
|
||||
export const LOAD_ALBUMS_OK = 'LOAD_ALBUMS_OK';
|
||||
export const SELECT_ALBUM = 'SELECT_ALBUM';
|
||||
export const SET_VISIBILITY_FILTER = 'SET_VISIBILITY_FILTER';
|
||||
export const SET_SORT_KEY = 'SET_SORT_KEY';
|
||||
export const UNSELECT_ALBUM = 'UNSELECT_ALBUM';
|
||||
|
||||
export const setVisibilityFilter = filter => ({
|
||||
type: SET_VISIBILITY_FILTER,
|
||||
payload: {
|
||||
filter
|
||||
},
|
||||
});
|
||||
|
||||
export const setSortKey = key => ({
|
||||
type: SET_SORT_KEY,
|
||||
payload: {
|
||||
key,
|
||||
},
|
||||
});
|
||||
|
||||
export const albumsLoadedOk = albums => ({
|
||||
type: LOAD_ALBUMS_OK,
|
||||
payload: {
|
||||
albums,
|
||||
},
|
||||
})
|
||||
|
||||
export const selectAlbum = album => ({
|
||||
type: SELECT_ALBUM,
|
||||
payload: {
|
||||
album
|
||||
},
|
||||
});
|
||||
|
||||
export const unselectAlbum = () => ({
|
||||
type: UNSELECT_ALBUM,
|
||||
});
|
||||
40
src/actions/index.ts
Normal file
40
src/actions/index.ts
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
import { Album } from "../interfaces";
|
||||
|
||||
export const LOAD_ALBUMS = "LOAD_ALBUMS";
|
||||
export const LOAD_ALBUMS_OK = "LOAD_ALBUMS_OK";
|
||||
export const SELECT_ALBUM = "SELECT_ALBUM";
|
||||
export const SET_VISIBILITY_FILTER = "SET_VISIBILITY_FILTER";
|
||||
export const SET_SORT_KEY = "SET_SORT_KEY";
|
||||
export const UNSELECT_ALBUM = "UNSELECT_ALBUM";
|
||||
|
||||
export const setVisibilityFilter = (filter: string) => ({
|
||||
type: SET_VISIBILITY_FILTER,
|
||||
payload: {
|
||||
filter
|
||||
}
|
||||
});
|
||||
|
||||
export const setSortKey = (key: string) => ({
|
||||
type: SET_SORT_KEY,
|
||||
payload: {
|
||||
key
|
||||
}
|
||||
});
|
||||
|
||||
export const albumsLoadedOk = (albums: Array<Album>) => ({
|
||||
type: LOAD_ALBUMS_OK,
|
||||
payload: {
|
||||
albums
|
||||
}
|
||||
});
|
||||
|
||||
export const selectAlbum = (album: Album) => ({
|
||||
type: SELECT_ALBUM,
|
||||
payload: {
|
||||
album
|
||||
}
|
||||
});
|
||||
|
||||
export const unselectAlbum = () => ({
|
||||
type: UNSELECT_ALBUM
|
||||
});
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
import React, { Component } from 'react';
|
||||
import Album from './album';
|
||||
|
||||
export default class AlbumList extends Component {
|
||||
render() {
|
||||
const {
|
||||
albums,
|
||||
handleOnClick,
|
||||
blurred,
|
||||
} = this.props;
|
||||
const classNames = blurred ? 'blur' : ''
|
||||
return (
|
||||
<div className={"albums " + classNames}>
|
||||
{albums.map(album => (
|
||||
<Album
|
||||
key={album.id}
|
||||
album={album}
|
||||
handleOnClick={handleOnClick}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
25
src/components/album-list.tsx
Normal file
25
src/components/album-list.tsx
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
import React from "react";
|
||||
import Album from "./album";
|
||||
import * as interfaces from "../interfaces";
|
||||
|
||||
export default (props: AlbumList) => {
|
||||
const { albums, handleOnClick, blurred } = props;
|
||||
const classNames = blurred ? "blur" : "";
|
||||
return (
|
||||
<div className={"albums " + classNames}>
|
||||
{albums.map((album: interfaces.Album) => (
|
||||
<Album
|
||||
key={album.id}
|
||||
album={album}
|
||||
handleOnClick={handleOnClick}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
interface AlbumList {
|
||||
albums: Array<interfaces.Album>;
|
||||
handleOnClick: Function;
|
||||
blurred: boolean;
|
||||
}
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
import React, { Component } from 'react';
|
||||
|
||||
export default class Album extends Component {
|
||||
handleKeyPress(e, callback) {
|
||||
const SPACE_KEY = 32
|
||||
const ENTER_KEY = 13
|
||||
if (e.charCode === SPACE_KEY || e.charCode === ENTER_KEY) {
|
||||
e.preventDefault();
|
||||
callback();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { album, handleOnClick } = this.props;
|
||||
const {
|
||||
id,
|
||||
artist,
|
||||
title,
|
||||
songs,
|
||||
year,
|
||||
img,
|
||||
purchased_on,
|
||||
} = album;
|
||||
const imagePath = `./covers/${img}`;
|
||||
const song = songs.join(', ');
|
||||
return (
|
||||
<article className="album" tabIndex="0" role="button" onClick={() => handleOnClick(album)}>
|
||||
<figure className="album__cover">
|
||||
<img src={imagePath} alt="cover" className="album__cover__media" />
|
||||
</figure>
|
||||
<span>
|
||||
#{id+1}: {artist} - {song}, från "{title}" ({year})<br />
|
||||
<small>✔️ {purchased_on}</small>
|
||||
</span>
|
||||
</article>
|
||||
);
|
||||
}
|
||||
}
|
||||
43
src/components/album.tsx
Normal file
43
src/components/album.tsx
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
import React from "react";
|
||||
import * as interfaces from "../interfaces";
|
||||
|
||||
export default (props: Album) => {
|
||||
const handleKeyPress = (e: KeyboardEvent, callback: Function) => {
|
||||
const SPACE_KEY = 32;
|
||||
const ENTER_KEY = 13;
|
||||
if (e.charCode === SPACE_KEY || e.charCode === ENTER_KEY) {
|
||||
e.preventDefault();
|
||||
callback();
|
||||
}
|
||||
};
|
||||
|
||||
const { album, handleOnClick } = props;
|
||||
const { id, artist, title, songs, year, img, purchased_on } = album;
|
||||
const imagePath = `./covers/${img}`;
|
||||
const song = songs.join(", ");
|
||||
return (
|
||||
<article
|
||||
className="album"
|
||||
tabIndex={0}
|
||||
role="button"
|
||||
onClick={() => handleOnClick(album)}
|
||||
>
|
||||
<figure className="album__cover">
|
||||
<img
|
||||
src={imagePath}
|
||||
alt="cover"
|
||||
className="album__cover__media"
|
||||
/>
|
||||
</figure>
|
||||
<span>
|
||||
#{id + 1}: {artist} - {song}, från "{title}" ({year})<br />
|
||||
<small>✔️ {purchased_on}</small>
|
||||
</span>
|
||||
</article>
|
||||
);
|
||||
};
|
||||
|
||||
interface Album {
|
||||
album: interfaces.Album;
|
||||
handleOnClick: Function;
|
||||
}
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
import React, { Component } from 'react';
|
||||
import AlbumList from '../containers/album-list';
|
||||
import FilterInput from '../containers/filter-input';
|
||||
import Modal from '../containers/modal';
|
||||
|
||||
export default class App extends Component {
|
||||
render() {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<header>
|
||||
<h1>Brütal Legend</h1>
|
||||
<FilterInput />
|
||||
</header>
|
||||
<AlbumList />
|
||||
<Modal />
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
15
src/components/app.tsx
Normal file
15
src/components/app.tsx
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import React from "react";
|
||||
import AlbumList from "../containers/album-list";
|
||||
import FilterInput from "../containers/filter-input";
|
||||
import Modal from "../containers/modal";
|
||||
|
||||
export default () => (
|
||||
<React.Fragment>
|
||||
<header>
|
||||
<h1>Brütal Legend</h1>
|
||||
<FilterInput />
|
||||
</header>
|
||||
<AlbumList />
|
||||
<Modal />
|
||||
</React.Fragment>
|
||||
);
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
import React, { Component } from 'react';
|
||||
|
||||
export default class FilterInput extends Component {
|
||||
render() {
|
||||
const {
|
||||
value,
|
||||
handleOnChange,
|
||||
} = this.props;
|
||||
return (
|
||||
<div>
|
||||
<input
|
||||
type='text'
|
||||
value={value}
|
||||
onChange={evt => handleOnChange(evt.target.value)}
|
||||
placeholder='Filtrera på år, artist, låt, skivtitel ...'
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
20
src/components/filter-input.tsx
Normal file
20
src/components/filter-input.tsx
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import React from "react";
|
||||
|
||||
export default (props: FilterInput) => {
|
||||
const { value, handleOnChange } = props;
|
||||
return (
|
||||
<div>
|
||||
<input
|
||||
type="text"
|
||||
value={value}
|
||||
onChange={evt => handleOnChange(evt.target.value)}
|
||||
placeholder="Filtrera på år, artist, låt, skivtitel ..."
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
interface FilterInput {
|
||||
value: string;
|
||||
handleOnChange: Function;
|
||||
}
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
import React, { Component } from 'react';
|
||||
|
||||
export default class Modal extends Component {
|
||||
handleKeyPress(e, callback) {
|
||||
alert("sdsdsd")
|
||||
console.log(e.charCode)
|
||||
callback();
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
id,
|
||||
artist,
|
||||
title,
|
||||
songs,
|
||||
year,
|
||||
img,
|
||||
description,
|
||||
handleOnClick,
|
||||
} = this.props;
|
||||
if (id === undefined) {
|
||||
return '';
|
||||
}
|
||||
const imagePath = `assets/covers/${img}`;
|
||||
const song = songs.join(', ');
|
||||
return (
|
||||
<div className="selected-album"
|
||||
tabIndex="0"
|
||||
onClick={handleOnClick}
|
||||
onKeyPress={e => this.handleKeyPress(e, handleOnClick)}>
|
||||
<div className="selected-album__inner">
|
||||
<span className="selected-album__summary">
|
||||
#{id+1}: {artist} - {song}, från "{title}" ({year})<br />
|
||||
</span>
|
||||
<div className="selected-album__description">
|
||||
{description.split('\n\n').map(text => (
|
||||
<p key={text}>
|
||||
{text}
|
||||
</p>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
42
src/components/modal.tsx
Normal file
42
src/components/modal.tsx
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
import React from "react";
|
||||
import { Album } from "../interfaces";
|
||||
|
||||
interface Modal {
|
||||
album: Album;
|
||||
handleOnClick: Function;
|
||||
}
|
||||
|
||||
export default (props: Modal) => {
|
||||
const handleKeyPress = (e: KeyboardEvent, callback: Function) => {
|
||||
console.log(e.charCode);
|
||||
callback();
|
||||
};
|
||||
|
||||
const { album, handleOnClick } = props;
|
||||
const { id, artist, title, songs, year, img, description } = album;
|
||||
if (id === undefined) {
|
||||
return "";
|
||||
}
|
||||
// const imagePath = `assets/covers/${img}`;
|
||||
const song = songs.join(", ");
|
||||
return (
|
||||
<div
|
||||
className="selected-album"
|
||||
tabIndex={0}
|
||||
onClick={() => handleOnClick()}
|
||||
onKeyPress={e => handleKeyPress(e, handleOnClick)}
|
||||
>
|
||||
<div className="selected-album__inner">
|
||||
<span className="selected-album__summary">
|
||||
#{id + 1}: {artist} - {song}, från "{title}" ({year})
|
||||
<br />
|
||||
</span>
|
||||
<div className="selected-album__description">
|
||||
{description.split("\n\n").map(text => (
|
||||
<p key={text}>{text}</p>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
import React, { Component } from 'react';
|
||||
|
||||
export default class SortSelect extends Component {
|
||||
render() {
|
||||
const { value, handleOnChange } = this.props;
|
||||
return (
|
||||
<div hidden>
|
||||
<label htmlFor="sortBy">Sortera efter</label>
|
||||
<select
|
||||
id="sortBy"
|
||||
value={value}
|
||||
onChange={evt => handleOnChange(evt.target.value)}>
|
||||
<option value="id">Inköpsdatum</option>
|
||||
<option value="artist">Artist</option>
|
||||
<option value="year">År</option>
|
||||
</select>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
24
src/components/sort-select.tsx
Normal file
24
src/components/sort-select.tsx
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
import React from "react";
|
||||
|
||||
export default (props: SortSelect) => {
|
||||
const { value, handleOnChange } = props;
|
||||
return (
|
||||
<div hidden>
|
||||
<label htmlFor="sortBy">Sortera efter</label>
|
||||
<select
|
||||
id="sortBy"
|
||||
value={value}
|
||||
onChange={evt => handleOnChange(evt.target.value)}
|
||||
>
|
||||
<option value="id">Inköpsdatum</option>
|
||||
<option value="artist">Artist</option>
|
||||
<option value="year">År</option>
|
||||
</select>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
interface SortSelect {
|
||||
value: string;
|
||||
handleOnChange: Function;
|
||||
}
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import AlbumList from '../components/album-list';
|
||||
import { selectAlbum } from '../actions'
|
||||
|
||||
const getAlbums = (albums, filter) => {
|
||||
const atos = o => [o.artist, o.title, o.songs.join(' '), o.year].join(' ').toLowerCase();
|
||||
if (filter) {
|
||||
const term = filter.toLowerCase();
|
||||
return albums.filter(album => atos(album).match(term));
|
||||
}
|
||||
return albums;
|
||||
};
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
albums: getAlbums(state.albums, state.visibilityFilter),
|
||||
blurred: "id" in state.selectedAlbum,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
handleOnClick: album => dispatch(selectAlbum(album)),
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(AlbumList);
|
||||
26
src/containers/album-list.ts
Normal file
26
src/containers/album-list.ts
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
import { connect } from "react-redux";
|
||||
import AlbumList from "../components/album-list";
|
||||
import { selectAlbum } from "../actions";
|
||||
import { Album, State } from "../interfaces";
|
||||
|
||||
const atos = (o: Album) =>
|
||||
[o.artist, o.title, o.songs.join(" "), o.year].join(" ").toLowerCase();
|
||||
|
||||
const getAlbums = (albums: Array<Object>, filter: string) => {
|
||||
if (filter) {
|
||||
const term = filter.toLowerCase();
|
||||
return albums.filter((album: Album) => atos(album).match(term));
|
||||
}
|
||||
return albums;
|
||||
};
|
||||
|
||||
const mapStateToProps = (state: State) => ({
|
||||
albums: getAlbums(state.albums, state.visibilityFilter),
|
||||
blurred: "id" in state.selectedAlbum
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch: Function) => ({
|
||||
handleOnClick: (album: Album) => dispatch(selectAlbum(album))
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(AlbumList);
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import FilterInput from '../components/filter-input';
|
||||
import { setVisibilityFilter } from '../actions';
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
value: state.visibilityFilter,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
handleOnChange: filter => dispatch(setVisibilityFilter(filter)),
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(FilterInput);
|
||||
14
src/containers/filter-input.ts
Normal file
14
src/containers/filter-input.ts
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import { connect } from "react-redux";
|
||||
import FilterInput from "../components/filter-input";
|
||||
import { setVisibilityFilter } from "../actions";
|
||||
import { State } from "../interfaces";
|
||||
|
||||
const mapStateToProps = (state: State) => ({
|
||||
value: state.visibilityFilter
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch: Function) => ({
|
||||
handleOnChange: (filter: string) => dispatch(setVisibilityFilter(filter))
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(FilterInput);
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import Modal from '../components/modal';
|
||||
import { unselectAlbum } from '../actions';
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
...state.selectedAlbum,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
handleOnClick: album => dispatch(unselectAlbum(album)),
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(Modal);
|
||||
15
src/containers/modal.ts
Normal file
15
src/containers/modal.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import Modal from "../components/modal";
|
||||
import { unselectAlbum } from "../actions";
|
||||
import { State } from "../interfaces";
|
||||
|
||||
const mapStateToProps = (state: State) => ({
|
||||
album: state.selectedAlbum
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch: Function) => ({
|
||||
handleOnClick: () => dispatch(unselectAlbum())
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(Modal);
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import SortSelect from '../components/sort-select';
|
||||
import {setSortKey} from "../actions";
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
value: state.sortKey
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
handleOnChange: (key) => dispatch(setSortKey(key))
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(SortSelect);
|
||||
15
src/containers/sort-select.ts
Normal file
15
src/containers/sort-select.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import SortSelect from "../components/sort-select";
|
||||
import { setSortKey } from "../actions";
|
||||
import { State } from "../interfaces";
|
||||
|
||||
const mapStateToProps = (state: State) => ({
|
||||
value: state.sortKey
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch: Function) => ({
|
||||
handleOnChange: (key: string) => dispatch(setSortKey(key))
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(SortSelect);
|
||||
32
src/index.js
32
src/index.js
|
|
@ -1,32 +0,0 @@
|
|||
import '@babel/polyfill';
|
||||
import React from 'react';
|
||||
import { render } from 'react-dom';
|
||||
import { createStore, applyMiddleware, compose } from 'redux';
|
||||
import { Provider } from 'react-redux';
|
||||
import createSagaMiddleware from 'redux-saga';
|
||||
import rootReducer from './reducers';
|
||||
import rootSagas from './sagas';
|
||||
import App from './components/app';
|
||||
|
||||
const sagaMiddleware = createSagaMiddleware()
|
||||
|
||||
/* eslint-disable no-underscore-dangle */
|
||||
const composeEnhancers =
|
||||
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
|
||||
/* eslint-enable */
|
||||
|
||||
const store = createStore(
|
||||
rootReducer,
|
||||
composeEnhancers(applyMiddleware(sagaMiddleware)),
|
||||
)
|
||||
|
||||
sagaMiddleware.run(rootSagas)
|
||||
|
||||
render(
|
||||
<Provider store={store}>
|
||||
<App />
|
||||
</Provider>,
|
||||
document.getElementById('brutal')
|
||||
);
|
||||
|
||||
store.dispatch({ type: 'LOAD_ALBUMS', payload: { source: './albums.json' }});
|
||||
31
src/index.tsx
Normal file
31
src/index.tsx
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
import React from "react";
|
||||
import { render } from "react-dom";
|
||||
import { createStore, applyMiddleware, compose } from "redux";
|
||||
import { Provider } from "react-redux";
|
||||
import createSagaMiddleware from "redux-saga";
|
||||
import rootReducer from "./reducers";
|
||||
import rootSagas from "./sagas";
|
||||
import App from "./components/app";
|
||||
import { LOAD_ALBUMS } from "./actions";
|
||||
|
||||
const sagaMiddleware = createSagaMiddleware();
|
||||
|
||||
/* eslint-disable no-underscore-dangle */
|
||||
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
|
||||
/* eslint-enable */
|
||||
|
||||
const store = createStore(
|
||||
rootReducer,
|
||||
composeEnhancers(applyMiddleware(sagaMiddleware))
|
||||
);
|
||||
|
||||
sagaMiddleware.run(rootSagas);
|
||||
|
||||
render(
|
||||
<Provider store={store}>
|
||||
<App />
|
||||
</Provider>,
|
||||
document.getElementById("brutal")
|
||||
);
|
||||
|
||||
store.dispatch({ type: LOAD_ALBUMS, payload: { source: "./albums.json" } });
|
||||
17
src/interfaces/index.ts
Normal file
17
src/interfaces/index.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
export interface Album {
|
||||
id: number;
|
||||
artist: string;
|
||||
title: string;
|
||||
songs: Array<string>;
|
||||
year: number;
|
||||
img: string;
|
||||
purchased_on: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
export interface State {
|
||||
albums: Array<Album>;
|
||||
selectedAlbum: Album;
|
||||
visibilityFilter: string;
|
||||
sortKey: string;
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
import { LOAD_ALBUMS_OK } from '../actions';
|
||||
|
||||
export default (state = [], action) => {
|
||||
switch (action.type) {
|
||||
case LOAD_ALBUMS_OK:
|
||||
const { albums } = action.payload;
|
||||
return albums;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
19
src/reducers/albums.ts
Normal file
19
src/reducers/albums.ts
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import { LOAD_ALBUMS_OK } from "../actions";
|
||||
import { Album } from "../interfaces";
|
||||
|
||||
interface AlbumsAction {
|
||||
type: string;
|
||||
payload: {
|
||||
albums: Array<Album>;
|
||||
};
|
||||
}
|
||||
|
||||
export default (state: Array<Album> = [], action: AlbumsAction) => {
|
||||
switch (action.type) {
|
||||
case LOAD_ALBUMS_OK:
|
||||
const { albums } = action.payload;
|
||||
return albums;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
import { combineReducers } from 'redux';
|
||||
import albums from './albums';
|
||||
import visibilityFilter from './visibility-filter';
|
||||
import sortKey from "./sort-key";
|
||||
import selectedAlbum from './selected-album';
|
||||
|
||||
export default combineReducers({
|
||||
albums,
|
||||
visibilityFilter,
|
||||
sortKey,
|
||||
selectedAlbum,
|
||||
});
|
||||
0
src/reducers/index.ts
Normal file
0
src/reducers/index.ts
Normal file
|
|
@ -1,12 +0,0 @@
|
|||
import { SELECT_ALBUM, UNSELECT_ALBUM } from '../actions';
|
||||
|
||||
export default (state = {}, action) => {
|
||||
switch (action.type) {
|
||||
case SELECT_ALBUM:
|
||||
return action.payload.album;
|
||||
case UNSELECT_ALBUM:
|
||||
return {};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
20
src/reducers/selected-album.ts
Normal file
20
src/reducers/selected-album.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import { SELECT_ALBUM, UNSELECT_ALBUM } from "../actions";
|
||||
import { Album } from "../interfaces";
|
||||
|
||||
interface AlbumAction {
|
||||
type: string;
|
||||
payload: {
|
||||
album: Album;
|
||||
};
|
||||
}
|
||||
|
||||
export default (state: Object = {}, action: AlbumAction) => {
|
||||
switch (action.type) {
|
||||
case SELECT_ALBUM:
|
||||
return action.payload.album;
|
||||
case UNSELECT_ALBUM:
|
||||
return {};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
import { SET_SORT_KEY } from '../actions';
|
||||
|
||||
export default (state = 'id', action) => {
|
||||
switch (action.type) {
|
||||
case SET_SORT_KEY:
|
||||
return action.payload.key;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
17
src/reducers/sort-key.ts
Normal file
17
src/reducers/sort-key.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
import { SET_SORT_KEY } from "../actions";
|
||||
|
||||
interface SortKeyAction {
|
||||
type: string;
|
||||
payload: {
|
||||
key: string;
|
||||
};
|
||||
}
|
||||
|
||||
export default (state: string = "id", action: SortKeyAction) => {
|
||||
switch (action.type) {
|
||||
case SET_SORT_KEY:
|
||||
return action.payload.key;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
import { SET_VISIBILITY_FILTER } from '../actions';
|
||||
|
||||
export default (state = '', action) => {
|
||||
switch (action.type) {
|
||||
case SET_VISIBILITY_FILTER:
|
||||
return action.payload.filter;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
17
src/reducers/visibility-filter.ts
Normal file
17
src/reducers/visibility-filter.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
import { SET_VISIBILITY_FILTER } from "../actions";
|
||||
|
||||
interface VisibilityFilterAction {
|
||||
type: string;
|
||||
payload: {
|
||||
filter: string;
|
||||
};
|
||||
}
|
||||
|
||||
export default (state: string = "", action: VisibilityFilterAction) => {
|
||||
switch (action.type) {
|
||||
case SET_VISIBILITY_FILTER:
|
||||
return action.payload.filter;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
import { put, takeEvery, all, call } from 'redux-saga/effects';
|
||||
import { LOAD_ALBUMS, albumsLoadedOk } from '../actions';
|
||||
|
||||
function* watchLoadAlbumsAsync() {
|
||||
yield takeEvery(LOAD_ALBUMS, loadAlbumsAsync);
|
||||
}
|
||||
|
||||
function* loadAlbumsAsync(action) {
|
||||
try {
|
||||
const { source } = action.payload;
|
||||
const data = yield call(() => fetch(source)
|
||||
.then(response => response.json())
|
||||
.then(data => data), {}
|
||||
);
|
||||
yield put(albumsLoadedOk(data));
|
||||
} catch (error) {
|
||||
yield console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
export default function* () {
|
||||
yield all([
|
||||
watchLoadAlbumsAsync(),
|
||||
]);
|
||||
};
|
||||
32
src/sagas/index.ts
Normal file
32
src/sagas/index.ts
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
import { put, takeEvery, all, call } from "redux-saga/effects";
|
||||
import { LOAD_ALBUMS, albumsLoadedOk } from "../actions";
|
||||
|
||||
function* watchLoadAlbumsAsync() {
|
||||
yield takeEvery(LOAD_ALBUMS, loadAlbumsAsync);
|
||||
}
|
||||
|
||||
function* loadAlbumsAsync(action: LoadAlbumsAction) {
|
||||
try {
|
||||
const { source } = action.payload;
|
||||
const data = yield call(
|
||||
() =>
|
||||
fetch(source)
|
||||
.then(response => response.json())
|
||||
.then(data => data),
|
||||
{}
|
||||
);
|
||||
yield put(albumsLoadedOk(data));
|
||||
} catch (error) {
|
||||
yield console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
export default function*() {
|
||||
yield all([watchLoadAlbumsAsync()]);
|
||||
}
|
||||
|
||||
interface LoadAlbumsAction {
|
||||
payload: {
|
||||
source: string;
|
||||
};
|
||||
}
|
||||
10
tsconfig.json
Normal file
10
tsconfig.json
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"jsx": "react",
|
||||
"baseUrl": "./src",
|
||||
"paths": {
|
||||
"~*": ["./*"]
|
||||
}
|
||||
},
|
||||
"include": ["src/**/*"]
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue