Lint code and introduce BEM

This commit is contained in:
Anders Ytterström 2019-04-23 15:18:35 +02:00
parent d447143312
commit 055511e555
11 changed files with 361 additions and 78 deletions

15
.editorconfig Normal file
View file

@ -0,0 +1,15 @@
# Based on the React EditorConfig file. See:
# - http://editorconfig.org
# - https://github.com/facebook/react/blob/master/.editorconfig
root = true
[*]
charset = utf-8
end_of_line = lf
indent_style = space
indent_size = 4
insert_final_newline = true
trim_trailing_whitespace = true
[*.{html,js,jsx,json,scss,css,yml}]
indent_size = 4

216
.eslintrc.js Normal file
View file

@ -0,0 +1,216 @@
var OFF = 0, WARN = 1, ERROR = 2;
module.exports = exports = {
"env": {
"es6": true
},
"ecmaFeatures": {
// env=es6 doesn't include modules, which we are using
"modules": true
},
"extends": "eslint:recommended",
"rules": {
// Possible Errors (overrides from recommended set)
"no-extra-parens": ERROR,
"no-unexpected-multiline": ERROR,
// All JSDoc comments must be valid
"valid-jsdoc": [ ERROR, {
"requireReturn": false,
"requireReturnDescription": false,
"requireParamDescription": true,
"prefer": {
"return": "returns"
}
}],
// Best Practices
// Allowed a getter without setter, but all setters require getters
"accessor-pairs": [ ERROR, {
"getWithoutSet": false,
"setWithoutGet": true
}],
"block-scoped-var": WARN,
"consistent-return": ERROR,
"curly": ERROR,
"default-case": WARN,
// the dot goes with the property when doing multiline
"dot-location": [ WARN, "property" ],
"dot-notation": WARN,
"eqeqeq": [ ERROR, "smart" ],
"guard-for-in": WARN,
"no-alert": ERROR,
"no-caller": ERROR,
"no-case-declarations": WARN,
"no-div-regex": WARN,
"no-else-return": WARN,
"no-empty-label": WARN,
"no-empty-pattern": WARN,
"no-eq-null": WARN,
"no-eval": ERROR,
"no-extend-native": ERROR,
"no-extra-bind": WARN,
"no-floating-decimal": WARN,
"no-implicit-coercion": [ WARN, {
"boolean": true,
"number": true,
"string": true
}],
"no-implied-eval": ERROR,
"no-invalid-this": ERROR,
"no-iterator": ERROR,
"no-labels": WARN,
"no-lone-blocks": WARN,
"no-loop-func": ERROR,
"no-magic-numbers": WARN,
"no-multi-spaces": ERROR,
"no-multi-str": WARN,
"no-native-reassign": ERROR,
"no-new-func": ERROR,
"no-new-wrappers": ERROR,
"no-new": ERROR,
"no-octal-escape": ERROR,
"no-param-reassign": ERROR,
"no-process-env": WARN,
"no-proto": ERROR,
"no-redeclare": ERROR,
"no-return-assign": ERROR,
"no-script-url": ERROR,
"no-self-compare": ERROR,
"no-throw-literal": ERROR,
"no-unused-expressions": ERROR,
"no-useless-call": ERROR,
"no-useless-concat": ERROR,
"no-void": WARN,
// Produce warnings when something is commented as TODO or FIXME
"no-warning-comments": [ WARN, {
"terms": [ "TODO", "FIXME" ],
"location": "start"
}],
"no-with": WARN,
"radix": WARN,
"vars-on-top": ERROR,
// Enforces the style of wrapped functions
"wrap-iife": [ ERROR, "outside" ],
"yoda": ERROR,
// Strict Mode - for ES6, never use strict.
"strict": [ ERROR, "never" ],
// Variables
"init-declarations": [ ERROR, "always" ],
"no-catch-shadow": WARN,
"no-delete-var": ERROR,
"no-label-var": ERROR,
"no-shadow-restricted-names": ERROR,
"no-shadow": WARN,
// We require all vars to be initialized (see init-declarations)
// If we NEED a var to be initialized to undefined, it needs to be explicit
"no-undef-init": OFF,
"no-undef": ERROR,
"no-undefined": OFF,
"no-unused-vars": WARN,
// Disallow hoisting - let & const don't allow hoisting anyhow
"no-use-before-define": ERROR,
// Node.js and CommonJS
"callback-return": [ WARN, [ "callback", "next" ]],
"global-require": ERROR,
"handle-callback-err": WARN,
"no-mixed-requires": WARN,
"no-new-require": ERROR,
// Use path.concat instead
"no-path-concat": ERROR,
"no-process-exit": ERROR,
"no-restricted-modules": OFF,
"no-sync": WARN,
// ECMAScript 6 support
"arrow-body-style": [ ERROR, "always" ],
"arrow-parens": [ ERROR, "always" ],
"arrow-spacing": [ ERROR, { "before": true, "after": true }],
"constructor-super": ERROR,
"generator-star-spacing": [ ERROR, "before" ],
"no-arrow-condition": ERROR,
"no-class-assign": ERROR,
"no-const-assign": ERROR,
"no-dupe-class-members": ERROR,
"no-this-before-super": ERROR,
"no-var": WARN,
"object-shorthand": [ WARN, "never" ],
"prefer-arrow-callback": WARN,
"prefer-spread": WARN,
"prefer-template": WARN,
"require-yield": ERROR,
// Stylistic - everything here is a warning because of style.
"array-bracket-spacing": [ WARN, "always" ],
"block-spacing": [ WARN, "always" ],
"brace-style": [ WARN, "1tbs", { "allowSingleLine": false } ],
"camelcase": WARN,
"comma-spacing": [ WARN, { "before": false, "after": true } ],
"comma-style": [ WARN, "last" ],
"computed-property-spacing": [ WARN, "never" ],
"consistent-this": [ WARN, "self" ],
"eol-last": WARN,
"func-names": WARN,
"func-style": [ WARN, "declaration" ],
"id-length": [ WARN, { "min": 2, "max": 32 } ],
"indent": [ WARN, 4 ],
"jsx-quotes": [ WARN, "prefer-double" ],
"linebreak-style": [ WARN, "unix" ],
"lines-around-comment": [ WARN, { "beforeBlockComment": true } ],
"max-depth": [ WARN, 8 ],
"max-len": [ WARN, 132 ],
"max-nested-callbacks": [ WARN, 8 ],
"max-params": [ WARN, 8 ],
"new-cap": WARN,
"new-parens": WARN,
"no-array-constructor": WARN,
"no-bitwise": OFF,
"no-continue": OFF,
"no-inline-comments": OFF,
"no-lonely-if": WARN,
"no-mixed-spaces-and-tabs": WARN,
"no-multiple-empty-lines": WARN,
"no-negated-condition": OFF,
"no-nested-ternary": WARN,
"no-new-object": WARN,
"no-plusplus": OFF,
"no-spaced-func": WARN,
"no-ternary": OFF,
"no-trailing-spaces": WARN,
"no-underscore-dangle": WARN,
"no-unneeded-ternary": WARN,
"object-curly-spacing": [ WARN, "always" ],
"one-var": OFF,
"operator-assignment": [ WARN, "never" ],
"operator-linebreak": [ WARN, "after" ],
"padded-blocks": [ WARN, "never" ],
"quote-props": [ WARN, "consistent-as-needed" ],
"quotes": [ WARN, "single" ],
"require-jsdoc": [ WARN, {
"require": {
"FunctionDeclaration": true,
"MethodDefinition": true,
"ClassDeclaration": false
}
}],
"semi-spacing": [ WARN, { "before": false, "after": true }],
"semi": [ ERROR, "always" ],
"sort-vars": OFF,
"space-after-keywords": [ WARN, "always" ],
"space-before-blocks": [ WARN, "always" ],
"space-before-function-paren": [ WARN, "never" ],
"space-before-keywords": [ WARN, "always" ],
"space-in-parens": [ WARN, "never" ],
"space-infix-ops": [ WARN, { "int32Hint": true } ],
"space-return-throw-case": ERROR,
"space-unary-ops": ERROR,
"spaced-comment": [ WARN, "always" ],
"wrap-regex": WARN
}
};

35
.stylelintrc.js Normal file
View file

@ -0,0 +1,35 @@
"use strict";
module.exports = {
rules: {
"at-rule-no-unknown": true,
"block-no-empty": true,
"color-no-invalid-hex": true,
"comment-no-empty": true,
"declaration-block-no-duplicate-properties": [
true,
{
ignore: ["consecutive-duplicates-with-different-values"]
}
],
"declaration-block-no-shorthand-property-overrides": true,
"font-family-no-duplicate-names": true,
"font-family-no-missing-generic-family-keyword": true,
"function-calc-no-unspaced-operator": true,
"function-linear-gradient-no-nonstandard-direction": true,
"keyframe-declaration-no-important": true,
"media-feature-name-no-unknown": true,
"no-descending-specificity": true,
"no-duplicate-at-import-rules": true,
"no-duplicate-selectors": true,
"no-empty-source": true,
"no-extra-semicolons": true,
"no-invalid-double-slash-comments": true,
"property-no-unknown": true,
"selector-pseudo-class-no-unknown": true,
"selector-pseudo-element-no-unknown": true,
"selector-type-no-unknown": true,
"string-no-newline": true,
"unit-no-unknown": true
}
};

View file

@ -1,4 +1,10 @@
# Getting started
# BRÜTAL LEGEND
Progress visualisation of the quest to own a vinyl copy of all songs older than 1990 used in [Brütal Legend](https://en.wikipedia.org/wiki/Br%C3%BCtal_Legend), where possible.
Also a project for learning CSS Grid layout, React, Redux and Redux-sagas.
## Getting started
Install dependencies and start webpack watcher.
@ -7,8 +13,6 @@ Install dependencies and start webpack watcher.
in another terminal, start a web server using python.
python2 -m 'SimpleHTTPServer' 1337
# or
python3 -m 'http.server' 1337
npm run serve
Visit site on http://localhost:1337
Visit site on http://localhost:10667

View file

@ -1,8 +1,9 @@
/* === Base === */
body {
background-color: #111;
color: #aaa;
font-size: large;
font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif;
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica Neue, Arial, sans-serif;
padding: 5rem 10rem;
}
@ -67,29 +68,33 @@ footer {
right: 0;
}
article {
/* === /Base === */
/* === Helpers === */
.blur {
filter: blur(25px);
}
/* === /Helpers === */
/* === Albums === */
.album {
display: flex;
align-items: center;
}
figure {
margin: 1rem;
}
article:hover,
article:focus {
.album:hover {
background: #333;
color: #fff;
transform: scale(1.05, 1.05);
transition: transform .4s linear;
}
article:hover img {
transform: scale(1.5, 1.5) translateX(-1.5rem);
transition: transform .2s linear;
.album__cover {
margin: 1rem;
}
figure img {
.album__cover__media {
width: 10vw;
height: 10vw;
padding: 5px;
@ -98,7 +103,24 @@ figure img {
background-color: #000;
}
#selected-album > div {
.album__cover__media:hover {
transform: scale(1.5, 1.5) translateX(-1.5rem);
transition: transform .2s linear;
}
@media (min-width: 600px) {
.albums {
display: grid;
grid-template-columns: auto auto auto;
grid-gap: 10px;
text-transform: uppercase;
}
}
/* === /Albums === */
/* === Selected album === */
.selected-album__inner {
display: grid;
max-width: 80%;
transform: translateY(-3em);
@ -106,27 +128,13 @@ figure img {
grid-template-rows: 1fr 2fr;
}
#selected-album figure {
.selected-album__cover {
grid-column: 1;
grid-row: 1 / 3;
margin: 0;
}
#selected-album span {
align-self: end;
text-transform: uppercase;
padding: 0 1rem;
}
#selected-album p {
color: #fff;
margin: 0;
font-size: 1.5rem;
align-self: start;
padding: 0 1rem;
}
#selected-album img {
.selected-album__cover__media {
width: 100%;
padding: 0;
border: 0;
@ -134,15 +142,22 @@ figure img {
object-fit: cover;
}
@media (min-width: 600px) {
#albums {
display: grid;
grid-template-columns: auto auto auto;
grid-gap: 10px;
text-transform: uppercase;
}
.selected-album__summary {
align-self: end;
text-transform: uppercase;
padding: 0 1rem;
}
#selected-album {
.selected-album__description {
color: #fff;
margin: 0;
font-size: 1.5rem;
align-self: start;
padding: 0 1rem;
}
@media (min-width: 600px) {
.selected-album {
position: fixed;
top: 0;
right: 0;
@ -155,6 +170,4 @@ figure img {
}
}
.blur {
filter: blur(25px);
}
/* === /Selected album === */

View file

@ -1,8 +1,8 @@
export const SET_VISIBILITY_FILTER = 'SET_VISIBILITY_FILTER';
export const SET_SORT_KEY = 'SET_SORT_KEY';
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 => ({

View file

@ -10,7 +10,7 @@ export default class AlbumList extends Component {
} = this.props;
const classNames = blurred ? 'blur' : ''
return (
<div id="albums" className={classNames}>
<div className={"albums " + classNames}>
{albums.map(album => (
<Album
key={album.id}

View file

@ -2,7 +2,7 @@ import React, { Component } from 'react';
export default class Album extends Component {
render() {
const { album, handleOnClick } = this.props
const { album, handleOnClick } = this.props;
const {
id,
artist,
@ -15,15 +15,15 @@ export default class Album extends Component {
const imagePath = `assets/covers/${img}`;
const song = songs.join(', ');
return (
<article onClick={() => handleOnClick(album)}>
<figure>
<img src={imagePath} alt="cover" />
<article className="album" 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>
)
);
}
}

View file

@ -18,15 +18,15 @@ export default class Modal extends Component {
const imagePath = `assets/covers/${img}`;
const song = songs.join(', ');
return (
<div id="selected-album" tabIndex="0" onClick={handleOnClick}>
<div>
<figure>
<img src={imagePath} alt="cover" />
<div className="selected-album" tabIndex="0" onClick={handleOnClick}>
<div className="selected-album__inner">
<figure className="selected-album__cover">
<img src={imagePath} alt="cover" className="selected-album__cover__media" />
</figure>
<span>
<span className="selected-album__summary">
#{id+1}: {artist} - {song}, från "{title}" ({year})<br />
</span>
<p>
<p className="selected-album__description">
{description.split('\n\n').map(text => (
<React.Fragment>
{text}
@ -37,6 +37,6 @@ export default class Modal extends Component {
</p>
</div>
</div>
)
);
}
}

View file

@ -1,4 +1,4 @@
import "babel-polyfill";
import 'babel-polyfill';
import React from 'react';
import { render } from 'react-dom';
import { createStore, applyMiddleware, compose } from 'redux';
@ -29,4 +29,4 @@ render(
document.getElementById('brutal')
);
store.dispatch({ type: 'LOAD_ALBUMS', payload: { source: '/json/albums.json' }});
store.dispatch({ type: 'LOAD_ALBUMS', payload: { source: 'json/albums.json' }});

View file

@ -1,25 +1,25 @@
import { put, takeEvery, all, call } from 'redux-saga/effects';
import { LOAD_ALBUMS, albumsLoadedOk } from '../actions';
export 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);
}
function* watchLoadAlbumsAsync() {
yield takeEvery(LOAD_ALBUMS, loadAlbumsAsync);
}
export 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(),
])
yield all([
watchLoadAlbumsAsync(),
]);
};