Browse Source

Move to CSS modules

Also includes misc fixes

* Use CSS modules for all the CSS.
* Fix a bug in the filterbar which was not filtering anything.
* Fix a l10n issue in songs view.
* Update hook to clean build repo before running, preventing to push
dev built code.
* Update webpack build code.
Phyks (Lucas Verney) 5 years ago
parent
commit
2d9747482b

+ 116
- 0
.bootstraprc View File

@@ -0,0 +1,116 @@
1
+---
2
+# Output debugging info
3
+loglevel: disabled
4
+
5
+# Major version of Bootstrap: 3 or 4
6
+bootstrapVersion: 3
7
+
8
+# If Bootstrap version 3 is used - turn on/off custom icon font path
9
+useCustomIconFontPath: false
10
+
11
+# Webpack loaders, order matters
12
+styleLoaders:
13
+  - style
14
+  - css
15
+  - sass
16
+
17
+# Extract styles to stand-alone css file
18
+# Different settings for different environments can be used,
19
+# It depends on value of NODE_ENV environment variable
20
+# This param can also be set in webpack config:
21
+#   entry: 'bootstrap-loader/extractStyles'
22
+extractStyles: true
23
+# env:
24
+#   development:
25
+#     extractStyles: false
26
+#   production:
27
+#     extractStyles: true
28
+
29
+
30
+# Customize Bootstrap variables that get imported before the original Bootstrap variables.
31
+# Thus, derived Bootstrap variables can depend on values from here.
32
+# See the Bootstrap _variables.scss file for examples of derived Bootstrap variables.
33
+#
34
+# preBootstrapCustomizations: ./path/to/bootstrap/pre-customizations.scss
35
+
36
+
37
+# This gets loaded after bootstrap/variables is loaded
38
+# Thus, you may customize Bootstrap variables
39
+# based on the values established in the Bootstrap _variables.scss file
40
+#
41
+# bootstrapCustomizations: ./path/to/bootstrap/customizations.scss
42
+
43
+
44
+# Import your custom styles here
45
+# Usually this endpoint-file contains list of @imports of your application styles
46
+#
47
+# appStyles: ./path/to/your/app/styles/endpoint.scss
48
+
49
+
50
+### Bootstrap styles
51
+styles:
52
+
53
+  # Mixins
54
+  mixins: true
55
+
56
+  # Reset and dependencies
57
+  normalize: true
58
+  print: true
59
+  glyphicons: true
60
+
61
+  # Core CSS
62
+  scaffolding: true
63
+  type: true
64
+  code: true
65
+  grid: true
66
+  tables: true
67
+  forms: true
68
+  buttons: true
69
+
70
+  # Components
71
+  component-animations: true
72
+  dropdowns: true
73
+  button-groups: true
74
+  input-groups: true
75
+  navs: true
76
+  navbar: true
77
+  breadcrumbs: true
78
+  pagination: true
79
+  pager: true
80
+  labels: true
81
+  badges: true
82
+  jumbotron: true
83
+  thumbnails: true
84
+  alerts: true
85
+  progress-bars: true
86
+  media: true
87
+  list-group: true
88
+  panels: true
89
+  wells: true
90
+  responsive-embed: true
91
+  close: true
92
+
93
+  # Components w/ JavaScript
94
+  modals: true
95
+  tooltip: true
96
+  popovers: true
97
+  carousel: true
98
+
99
+  # Utility classes
100
+  utilities: true
101
+  responsive-utilities: true
102
+
103
+### Bootstrap scripts
104
+scripts:
105
+  transition: true
106
+  alert: true
107
+  button: true
108
+  carousel: true
109
+  collapse: true
110
+  dropdown: true
111
+  modal: true
112
+  tooltip: true
113
+  popover: true
114
+  scrollspy: true
115
+  tab: true
116
+  affix: true

+ 2
- 2
CONTRIBUTING.md View File

@@ -21,13 +21,13 @@ A few `npm` scripts are provided:
21 21
 
22 22
 Translations are handled by [react-intl](https://github.com/yahoo/react-intl/).
23 23
 
24
-`npm run extractTranslations` output a file containing all the english
24
+`npm run --silent extractTranslations` output a file containing all the english
25 25
 translations, in the expected form. It is a mapping of ids and strings to
26 26
 translate, with an extra description provided as a comment at the end of the
27 27
 line, for some translation context.
28 28
 
29 29
 Typically, if you want to translate to another `$LOCALE` (say `fr-FR`), create
30 30
 a folder `./app/locales/$LOCALE`, put inside the generated file from `npm run
31
-extractTranslations`, called `index.js`. Copy the lines in
31
+--silent extractTranslations`, called `index.js`. Copy the lines in
32 32
 `./app/locales/index.js` to include your new translation and translate all the
33 33
 strings in the `./app/locales/$LOCALE/index.js` file you have just created.

+ 2
- 5
TODO View File

@@ -5,12 +5,8 @@
5 5
 8. Search
6 6
 9. Discover
7 7
 
8
-
9 8
 # CSS
10
-    * Sidebar responsiveness
11
-    * Move CSS in modules
12
-        => https://github.com/gajus/react-css-modules
13
-
9
+    * Focus stays on navbar
14 10
 
15 11
 # API middleware
16 12
     * https://github.com/reactjs/redux/issues/1824#issuecomment-228609501
@@ -18,6 +14,7 @@
18 14
     * https://github.com/reactjs/redux/issues/644
19 15
     * https://github.com/peterpme/redux-crud-api-middleware/blob/master/README.md
20 16
     * https://github.com/madou/armory-front/tree/master/src/app/reducers
17
+    * https://github.com/erikras/multireducer
21 18
     * Immutable.js (?) + get rid of lodash
22 19
 
23 20
 

+ 16
- 7
app/components/Album.jsx View File

@@ -1,7 +1,10 @@
1 1
 import React, { Component, PropTypes } from "react";
2
+import CSSModules from "react-css-modules";
2 3
 
3 4
 import { formatLength } from "../utils";
4 5
 
6
+import css from "../styles/Album.scss";
7
+
5 8
 export class AlbumTrackRow extends Component {
6 9
     render () {
7 10
         const length = formatLength(this.props.track.length);
@@ -40,28 +43,34 @@ AlbumTracksTable.propTypes = {
40 43
     tracks: PropTypes.array.isRequired
41 44
 };
42 45
 
43
-export class AlbumRow extends Component {
46
+class AlbumRowCSS extends Component {
44 47
     render () {
45 48
         return (
46
-            <div className="row albumRow">
47
-                <div className="col-sm-offset-2 col-xs-10 albumRowName">
49
+            <div className="row" styleName="row">
50
+                <div className="col-sm-offset-2 col-xs-10" styleName="nameRow">
48 51
                     <h2>{this.props.album.name}</h2>
49 52
                 </div>
50
-                <div className="col-xs-2 albumRowArt">
51
-                    <p className="text-center"><img src={this.props.album.art} width="200" height="200" className="img-responsive img-circle art" alt={this.props.album.name} /></p>
53
+                <div className="col-xs-2" styleName="artRow">
54
+                    <p className="text-center"><img src={this.props.album.art} width="200" height="200" className="img-responsive img-circle" styleName="art" alt={this.props.album.name} /></p>
52 55
                 </div>
53 56
                 <div className="col-sm-10 table-responsive">
54
-                    <AlbumTracksTable tracks={this.props.album.tracks} />
57
+                    {
58
+                        Array.isArray(this.props.album.tracks) ?
59
+                            <AlbumTracksTable tracks={this.props.album.tracks} /> :
60
+                            null
61
+                    }
55 62
                 </div>
56 63
             </div>
57 64
         );
58 65
     }
59 66
 }
60 67
 
61
-AlbumRow.propTypes = {
68
+AlbumRowCSS.propTypes = {
62 69
     album: PropTypes.object.isRequired
63 70
 };
64 71
 
72
+export let AlbumRow = CSSModules(AlbumRowCSS, css);
73
+
65 74
 export default class Album extends Component {
66 75
     render () {
67 76
         return (

+ 9
- 4
app/components/Artist.jsx View File

@@ -1,8 +1,11 @@
1 1
 import React, { Component, PropTypes } from "react";
2
+import CSSModules from "react-css-modules";
2 3
 
3 4
 import { AlbumRow } from "./Album";
4 5
 
5
-export default class Artist extends Component {
6
+import css from "../styles/Artist.scss";
7
+
8
+class ArtistCSS extends Component {
6 9
     render () {
7 10
         var albumsRows = [];
8 11
         if (Array.isArray(this.props.artist.albums)) {
@@ -12,7 +15,7 @@ export default class Artist extends Component {
12 15
         }
13 16
         return (
14 17
             <div>
15
-                <div className="row artistNameRow">
18
+                <div className="row" styleName="name">
16 19
                     <div className="col-sm-12">
17 20
                         <h1>{this.props.artist.name}</h1>
18 21
                         <hr/>
@@ -23,7 +26,7 @@ export default class Artist extends Component {
23 26
                         <p>{this.props.artist.summary}</p>
24 27
                     </div>
25 28
                     <div className="col-sm-3 text-center">
26
-                        <p><img src={this.props.artist.art} width="200" height="200" className="img-responsive img-circle art" alt={this.props.artist.name}/></p>
29
+                        <p><img src={this.props.artist.art} width="200" height="200" className="img-responsive img-circle" styleName="art" alt={this.props.artist.name}/></p>
27 30
                     </div>
28 31
                 </div>
29 32
                 { albumsRows }
@@ -32,6 +35,8 @@ export default class Artist extends Component {
32 35
     }
33 36
 }
34 37
 
35
-Artist.propTypes = {
38
+ArtistCSS.propTypes = {
36 39
     artist: PropTypes.object.isRequired
37 40
 };
41
+
42
+export default CSSModules(ArtistCSS, css);

+ 14
- 9
app/components/Login.jsx View File

@@ -1,12 +1,15 @@
1 1
 import React, { Component, PropTypes } from "react";
2
+import CSSModules from "react-css-modules";
2 3
 import { defineMessages, injectIntl, intlShape, FormattedMessage } from "react-intl";
3 4
 
4 5
 import { messagesMap } from "../utils";
5 6
 import messages from "../locales/messagesDescriptors/Login";
6 7
 
8
+import css from "../styles/Login.scss";
9
+
7 10
 const loginMessages = defineMessages(messagesMap(messages));
8 11
 
9
-class LoginFormIntl extends Component {
12
+class LoginFormCSSIntl extends Component {
10 13
     constructor (props) {
11 14
         super(props);
12 15
 
@@ -61,8 +64,8 @@ class LoginFormIntl extends Component {
61 64
                 {
62 65
                     this.props.error ?
63 66
                         <div className="row">
64
-                            <div className="alert alert-danger">
65
-                                <p id="loginFormError">
67
+                            <div className="alert alert-danger" id="loginFormError">
68
+                                <p>
66 69
                                     <span className="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span> { this.props.error }
67 70
                                 </p>
68 71
                             </div>
@@ -105,7 +108,7 @@ class LoginFormIntl extends Component {
105 108
                                                 <FormattedMessage {...loginMessages["app.login.rememberMe"]} />
106 109
                                             </label>
107 110
                                         </div>
108
-                                        <div className="col-sm-6 col-sm-12 submit text-right">
111
+                                        <div className="col-sm-6 col-xs-12 text-right" styleName="submit">
109 112
                                             <input type="submit" className="btn btn-default" aria-label={formatMessage(loginMessages["app.login.signIn"])} defaultValue={formatMessage(loginMessages["app.login.signIn"])} disabled={this.props.isAuthenticating} />
110 113
                                         </div>
111 114
                                     </div>
@@ -119,7 +122,7 @@ class LoginFormIntl extends Component {
119 122
     }
120 123
 }
121 124
 
122
-LoginFormIntl.propTypes = {
125
+LoginFormCSSIntl.propTypes = {
123 126
     username: PropTypes.string,
124 127
     endpoint: PropTypes.string,
125 128
     rememberMe: PropTypes.bool,
@@ -130,10 +133,10 @@ LoginFormIntl.propTypes = {
130 133
     intl: intlShape.isRequired,
131 134
 };
132 135
 
133
-export let LoginForm = injectIntl(LoginFormIntl);
136
+export let LoginForm = injectIntl(CSSModules(LoginFormCSSIntl, css));
134 137
 
135 138
 
136
-export default class Login extends Component {
139
+class Login extends Component {
137 140
     render () {
138 141
         const greeting = (
139 142
             <p>
@@ -141,8 +144,8 @@ export default class Login extends Component {
141 144
             </p>
142 145
         );
143 146
         return (
144
-            <div className="login text-center container-fluid">
145
-                <h1><img src="./app/assets/img/ampache-blue.png" alt="A"/>mpache</h1>
147
+            <div className="text-center container-fluid">
148
+                <h1><img styleName="titleImage" src="./app/assets/img/ampache-blue.png" alt="A"/>mpache</h1>
146 149
                 <hr/>
147 150
                 {(!this.props.error && !this.props.info) ? greeting : null}
148 151
                 <div className="col-sm-9 col-sm-offset-2 col-md-6 col-md-offset-3">
@@ -162,3 +165,5 @@ Login.propTypes = {
162 165
     error: PropTypes.string,
163 166
     info: PropTypes.string
164 167
 };
168
+
169
+export default CSSModules(Login, css);

+ 3
- 3
app/components/Songs.jsx View File

@@ -65,13 +65,13 @@ export class SongsTable extends Component {
65 65
                                 <FormattedMessage {...songsMessages["app.songs.title"]} />
66 66
                             </th>
67 67
                             <th>
68
-                                <FormattedMessage {...songsMessages["app.common.artist"]} />
68
+                                <FormattedMessage {...songsMessages["app.common.artist"]} values={{itemCount: 1}} />
69 69
                             </th>
70 70
                             <th>
71
-                                <FormattedMessage {...songsMessages["app.common.album"]} />
71
+                                <FormattedMessage {...songsMessages["app.common.album"]} values={{itemCount: 1}} />
72 72
                             </th>
73 73
                             <th>
74
-                                <FormattedMessage {...songsMessages["app.common.genre"]} />
74
+                                <FormattedMessage {...songsMessages["app.songs.genre"]} />
75 75
                             </th>
76 76
                             <th>
77 77
                                 <FormattedMessage {...songsMessages["app.songs.length"]} />

+ 10
- 8
app/components/elements/FilterBar.jsx View File

@@ -1,12 +1,15 @@
1 1
 import React, { Component, PropTypes } from "react";
2
+import CSSModules from "react-css-modules";
2 3
 import { defineMessages, injectIntl, intlShape, FormattedMessage } from "react-intl";
3 4
 
4 5
 import { messagesMap } from "../../utils";
5 6
 import messages from "../../locales/messagesDescriptors/elements/FilterBar";
6 7
 
8
+import css from "../../styles/elements/FilterBar.scss";
9
+
7 10
 const filterMessages = defineMessages(messagesMap(messages));
8 11
 
9
-class FilterBarIntl extends Component {
12
+class FilterBarCSSIntl extends Component {
10 13
     constructor (props) {
11 14
         super(props);
12 15
         this.handleChange = this.handleChange.bind(this);
@@ -21,14 +24,14 @@ class FilterBarIntl extends Component {
21 24
     render () {
22 25
         const {formatMessage} = this.props.intl;
23 26
         return (
24
-            <div className="filter">
25
-                <p className="col-xs-12 col-sm-6 col-md-4 col-md-offset-1 filter-legend" id="filterInputDescription">
27
+            <div styleName="filter">
28
+                <p className="col-xs-12 col-sm-6 col-md-4 col-md-offset-1" styleName="legend" id="filterInputDescription">
26 29
                     <FormattedMessage {...filterMessages["app.filter.whatAreWeListeningToToday"]} />
27 30
                 </p>
28 31
                 <div className="col-xs-12 col-sm-6 col-md-4 input-group">
29 32
                     <form className="form-inline" onSubmit={this.handleChange} aria-describedby="filterInputDescription">
30
-                        <div className="form-group">
31
-                            <input type="text" className="form-control filter-input" placeholder={formatMessage(filterMessages["app.filter.filter"])} aria-label={formatMessage(filterMessages["app.filter.filter"])} value={this.props.filterText} onChange={this.handleChange} ref="filterTextInput" />
33
+                        <div className="form-group" styleName="form-group">
34
+                            <input type="text" className="form-control" placeholder={formatMessage(filterMessages["app.filter.filter"])} aria-label={formatMessage(filterMessages["app.filter.filter"])} value={this.props.filterText} onChange={this.handleChange} ref="filterTextInput" />
32 35
                         </div>
33 36
                     </form>
34 37
                 </div>
@@ -37,11 +40,10 @@ class FilterBarIntl extends Component {
37 40
     }
38 41
 }
39 42
 
40
-FilterBarIntl.propTypes = {
43
+FilterBarCSSIntl.propTypes = {
41 44
     onUserInput: PropTypes.func,
42 45
     filterText: PropTypes.string,
43 46
     intl: intlShape.isRequired
44 47
 };
45 48
 
46
-export let FilterBar = injectIntl(FilterBarIntl);
47
-export default FilterBar;
49
+export default injectIntl(CSSModules(FilterBarCSSIntl, css));

+ 14
- 9
app/components/elements/Grid.jsx View File

@@ -1,5 +1,6 @@
1 1
 import React, { Component, PropTypes } from "react";
2 2
 import { Link} from "react-router";
3
+import CSSModules from "react-css-modules";
3 4
 import imagesLoaded from "imagesloaded";
4 5
 import Isotope from "isotope-layout";
5 6
 import Fuse from "fuse.js";
@@ -8,7 +9,9 @@ import _ from "lodash";
8 9
 import FilterBar from "./FilterBar";
9 10
 import Pagination from "./Pagination";
10 11
 
11
-export class GridItem extends Component {
12
+import css from "../../styles/elements/Grid.scss";
13
+
14
+class GridItemCSS extends Component {
12 15
     render () {
13 16
         var nSubItems = this.props.item[this.props.subItemsType];
14 17
         if (Array.isArray(nSubItems)) {
@@ -27,10 +30,10 @@ export class GridItem extends Component {
27 30
         // TODO: i18n
28 31
         const title = "Go to " + this.props.itemsType.rstrip("s") + " page";
29 32
         return (
30
-            <div className="grid-item col-xs-6 col-sm-3 placeholders" id={id}>
31
-                <div className="grid-item-content placeholder text-center">
32
-                    <Link title={title} to={to}><img src={this.props.item.art} width="200" height="200" className="img-responsive img-circle art" alt={this.props.item.name}/></Link>
33
-                    <h4 className="name">{this.props.item.name}</h4>
33
+            <div className="grid-item col-xs-6 col-sm-3" styleName="placeholders" id={id}>
34
+                <div className="grid-item-content text-center">
35
+                    <Link title={title} to={to}><img src={this.props.item.art} width="200" height="200" className="img-responsive img-circle art" styleName="art" alt={this.props.item.name}/></Link>
36
+                    <h4 className="name" styleName="name">{this.props.item.name}</h4>
34 37
                     <span className="sub-items text-muted"><span className="n-sub-items">{nSubItems}</span> <span className="sub-items-type">{subItemsLabel}</span></span>
35 38
                 </div>
36 39
             </div>
@@ -38,12 +41,14 @@ export class GridItem extends Component {
38 41
     }
39 42
 }
40 43
 
41
-GridItem.propTypes = {
44
+GridItemCSS.propTypes = {
42 45
     item: PropTypes.object.isRequired,
43 46
     itemsType: PropTypes.string.isRequired,
44 47
     subItemsType: PropTypes.string.isRequired
45 48
 };
46 49
 
50
+export let GridItem = CSSModules(GridItemCSS, css);
51
+
47 52
 
48 53
 const ISOTOPE_OPTIONS = {  /** Default options for Isotope grid layout. */
49 54
     getSortData: {
@@ -93,9 +98,9 @@ export class Grid extends Component {
93 98
 
94 99
         // Apply filter on grid
95 100
         this.iso.arrange({
96
-            filter: function () {
97
-                var name = $(this).find(".name").text();
98
-                return result.find(function (item) { return item.item.name == name; });
101
+            filter: function (item) {
102
+                var name = $(item).find(".name").text();
103
+                return result.find(function (i) { return i.item.name == name; });
99 104
             },
100 105
             transitionDuration: "0.4s",
101 106
             getSortData: {

+ 8
- 6
app/components/elements/Pagination.jsx View File

@@ -1,14 +1,17 @@
1 1
 import React, { Component, PropTypes } from "react";
2 2
 import { Link, withRouter } from "react-router";
3
+import CSSModules from "react-css-modules";
3 4
 import { defineMessages, injectIntl, intlShape, FormattedMessage, FormattedHTMLMessage } from "react-intl";
4 5
 
5 6
 import { messagesMap } from "../../utils";
6 7
 import commonMessages from "../../locales/messagesDescriptors/common";
7 8
 import messages from "../../locales/messagesDescriptors/elements/Pagination";
8 9
 
10
+import css from "../../styles/elements/Pagination.scss";
11
+
9 12
 const paginationMessages = defineMessages(messagesMap(Array.concat([], commonMessages, messages)));
10 13
 
11
-export class PaginationIntl extends Component {
14
+class PaginationCSSIntl extends Component {
12 15
     constructor(props) {
13 16
         super(props);
14 17
         this.buildLinkTo.bind(this);
@@ -134,8 +137,8 @@ export class PaginationIntl extends Component {
134 137
         if (pagesButton.length > 1) {
135 138
             return (
136 139
                 <div>
137
-                    <nav className="pagination-nav" aria-label={formatMessage(paginationMessages["app.pagination.pageNavigation"])}>
138
-                        <ul className="pagination">
140
+                    <nav className="pagination-nav" styleName="nav" aria-label={formatMessage(paginationMessages["app.pagination.pageNavigation"])}>
141
+                        <ul className="pagination" styleName="pointer">
139 142
                             { pagesButton }
140 143
                         </ul>
141 144
                     </nav>
@@ -171,12 +174,11 @@ export class PaginationIntl extends Component {
171 174
     }
172 175
 }
173 176
 
174
-PaginationIntl.propTypes = {
177
+PaginationCSSIntl.propTypes = {
175 178
     currentPage: PropTypes.number.isRequired,
176 179
     location: PropTypes.object.isRequired,
177 180
     nPages: PropTypes.number.isRequired,
178 181
     intl: intlShape.isRequired,
179 182
 };
180 183
 
181
-export let Pagination = withRouter(injectIntl(PaginationIntl));
182
-export default Pagination;
184
+export default withRouter(injectIntl(CSSModules(PaginationCSSIntl, css)));

+ 28
- 26
app/components/layouts/Sidebar.jsx View File

@@ -1,39 +1,42 @@
1 1
 import React, { Component, PropTypes } from "react";
2 2
 import { IndexLink, Link} from "react-router";
3
+import CSSModules from "react-css-modules";
3 4
 import { defineMessages, injectIntl, intlShape, FormattedMessage } from "react-intl";
4 5
 
5 6
 import { messagesMap } from "../../utils";
6 7
 import commonMessages from "../../locales/messagesDescriptors/common";
7 8
 import messages from "../../locales/messagesDescriptors/layouts/Sidebar";
8 9
 
10
+import css from "../../styles/layouts/Sidebar.scss";
11
+
9 12
 const sidebarLayoutMessages = defineMessages(messagesMap(Array.concat([], commonMessages, messages)));
10 13
 
11
-export default class SidebarLayoutIntl extends Component {
14
+class SidebarLayoutIntl extends Component {
12 15
     render () {
13 16
         const { formatMessage } = this.props.intl;
14 17
         const isActive = {
15
-            discover: (this.props.location.pathname == "/discover") ? "active" : "",
16
-            browse: (this.props.location.pathname == "/browse") ? "active" : "",
17
-            artists: (this.props.location.pathname == "/artists") ? "active" : "",
18
-            albums: (this.props.location.pathname == "/albums") ? "active" : "",
19
-            songs: (this.props.location.pathname == "/songs") ? "active" : "",
20
-            search: (this.props.location.pathname == "/search") ? "active" : ""
18
+            discover: (this.props.location.pathname == "/discover") ? "active" : "link",
19
+            browse: (this.props.location.pathname == "/browse") ? "active" : "link",
20
+            artists: (this.props.location.pathname == "/artists") ? "active" : "link",
21
+            albums: (this.props.location.pathname == "/albums") ? "active" : "link",
22
+            songs: (this.props.location.pathname == "/songs") ? "active" : "link",
23
+            search: (this.props.location.pathname == "/search") ? "active" : "link"
21 24
         };
22 25
         return (
23 26
             <div>
24
-                <div className="col-sm-1 col-md-2 sidebar hidden-xs">
25
-                    <h1 className="text-center">
26
-                        <IndexLink to="/">
27
-                            <img alt="A" src="./app/assets/img/ampache-blue.png"/>
27
+                <div className="col-sm-1 col-md-2 hidden-xs" styleName="sidebar">
28
+                    <h1 className="text-center" styleName="title">
29
+                        <IndexLink to="/" styleName="link">
30
+                            <img alt="A" src="./app/assets/img/ampache-blue.png" styleName="imgTitle" />
28 31
                             <span className="hidden-sm">mpache</span>
29 32
                         </IndexLink>
30 33
                     </h1>
31 34
                     <nav aria-label={formatMessage(sidebarLayoutMessages["app.sidebarLayout.mainNavigationMenu"])}>
32
-                        <div className="navbar text-center icon-navbar">
35
+                        <div className="navbar text-center" styleName="icon-navbar">
33 36
                             <div className="container-fluid">
34
-                                <ul className="nav navbar-nav icon-navbar-nav">
37
+                                <ul className="nav navbar-nav" styleName="nav">
35 38
                                     <li>
36
-                                        <Link to="/" title={formatMessage(sidebarLayoutMessages["app.sidebarLayout.home"])}>
39
+                                        <Link to="/" title={formatMessage(sidebarLayoutMessages["app.sidebarLayout.home"])} styleName="link">
37 40
                                             <span className="glyphicon glyphicon-home" aria-hidden="true"></span>
38 41
                                             <span className="sr-only">
39 42
                                                 <FormattedMessage {...sidebarLayoutMessages["app.sidebarLayout.home"]} />
@@ -41,7 +44,7 @@ export default class SidebarLayoutIntl extends Component {
41 44
                                         </Link>
42 45
                                     </li>
43 46
                                     <li>
44
-                                        <Link to="/settings" title={formatMessage(sidebarLayoutMessages["app.sidebarLayout.settings"])}>
47
+                                        <Link to="/settings" title={formatMessage(sidebarLayoutMessages["app.sidebarLayout.settings"])} styleName="link">
45 48
                                             <span className="glyphicon glyphicon-wrench" aria-hidden="true"></span>
46 49
                                             <span className="sr-only">
47 50
                                                 <FormattedMessage {...sidebarLayoutMessages["app.sidebarLayout.settings"]} />
@@ -49,7 +52,7 @@ export default class SidebarLayoutIntl extends Component {
49 52
                                         </Link>
50 53
                                     </li>
51 54
                                     <li>
52
-                                        <Link to="/logout" title={formatMessage(sidebarLayoutMessages["app.sidebarLayout.logout"])}>
55
+                                        <Link to="/logout" title={formatMessage(sidebarLayoutMessages["app.sidebarLayout.logout"])} styleName="link">
53 56
                                             <span className="glyphicon glyphicon-off" aria-hidden="true"></span>
54 57
                                             <span className="sr-only">
55 58
                                                 <FormattedMessage {...sidebarLayoutMessages["app.sidebarLayout.logout"]} />
@@ -59,9 +62,9 @@ export default class SidebarLayoutIntl extends Component {
59 62
                                 </ul>
60 63
                             </div>
61 64
                         </div>
62
-                        <ul className="nav nav-sidebar">
65
+                        <ul className="nav" styleName="nav">
63 66
                             <li>
64
-                                <Link to="/discover" title={formatMessage(sidebarLayoutMessages["app.sidebarLayout.discover"])} className={isActive.discover}>
67
+                                <Link to="/discover" title={formatMessage(sidebarLayoutMessages["app.sidebarLayout.discover"])} styleName={isActive.discover}>
65 68
                                     <span className="glyphicon glyphicon-globe" aria-hidden="true"></span>
66 69
                                     <span className="hidden-sm">
67 70
                                         &nbsp;<FormattedMessage {...sidebarLayoutMessages["app.sidebarLayout.discover"]} />
@@ -69,7 +72,7 @@ export default class SidebarLayoutIntl extends Component {
69 72
                                 </Link>
70 73
                             </li>
71 74
                             <li>
72
-                                <Link to="/browse" title={formatMessage(sidebarLayoutMessages["app.sidebarLayout.browse"])} className={isActive.browse}>
75
+                                <Link to="/browse" title={formatMessage(sidebarLayoutMessages["app.sidebarLayout.browse"])} styleName={isActive.browse}>
73 76
                                     <span className="glyphicon glyphicon-headphones" aria-hidden="true"></span>
74 77
                                     <span className="hidden-sm">
75 78
                                         &nbsp;<FormattedMessage {...sidebarLayoutMessages["app.sidebarLayout.browse"]} />
@@ -77,7 +80,7 @@ export default class SidebarLayoutIntl extends Component {
77 80
                                 </Link>
78 81
                                 <ul className="nav nav-list text-center">
79 82
                                     <li>
80
-                                        <Link to="/artists" title={formatMessage(sidebarLayoutMessages["app.sidebarLayout.browseArtists"])} className={isActive.artists}>
83
+                                        <Link to="/artists" title={formatMessage(sidebarLayoutMessages["app.sidebarLayout.browseArtists"])} styleName={isActive.artists}>
81 84
                                             <span className="glyphicon glyphicon-user" aria-hidden="true"></span>
82 85
                                             <span className="sr-only">
83 86
                                                 <FormattedMessage {...sidebarLayoutMessages["app.common.artist"]} values={{itemCount: 42}} />
@@ -88,7 +91,7 @@ export default class SidebarLayoutIntl extends Component {
88 91
                                         </Link>
89 92
                                     </li>
90 93
                                     <li>
91
-                                        <Link to="/albums" title={formatMessage(sidebarLayoutMessages["app.sidebarLayout.browseAlbums"])} className={isActive.albums}>
94
+                                        <Link to="/albums" title={formatMessage(sidebarLayoutMessages["app.sidebarLayout.browseAlbums"])} styleName={isActive.albums}>
92 95
                                             <span className="glyphicon glyphicon-cd" aria-hidden="true"></span>
93 96
                                             <span className="sr-only"><FormattedMessage {...sidebarLayoutMessages["app.common.album"]} values={{itemCount: 42}} /></span>
94 97
                                             <span className="hidden-sm">
@@ -97,7 +100,7 @@ export default class SidebarLayoutIntl extends Component {
97 100
                                         </Link>
98 101
                                     </li>
99 102
                                     <li>
100
-                                        <Link to="/songs" title={formatMessage(sidebarLayoutMessages["app.sidebarLayout.browseSongs"])} className={isActive.songs}>
103
+                                        <Link to="/songs" title={formatMessage(sidebarLayoutMessages["app.sidebarLayout.browseSongs"])} styleName={isActive.songs}>
101 104
                                             <span className="glyphicon glyphicon-music" aria-hidden="true"></span>
102 105
                                             <span className="sr-only">
103 106
                                                 <FormattedMessage {...sidebarLayoutMessages["app.common.song"]} values={{itemCount: 42}} />
@@ -110,7 +113,7 @@ export default class SidebarLayoutIntl extends Component {
110 113
                                 </ul>
111 114
                             </li>
112 115
                             <li>
113
-                                <Link to="/search" title={formatMessage(sidebarLayoutMessages["app.sidebarLayout.search"])} className={isActive.search}>
116
+                                <Link to="/search" title={formatMessage(sidebarLayoutMessages["app.sidebarLayout.search"])} styleName={isActive.search}>
114 117
                                     <span className="glyphicon glyphicon-search" aria-hidden="true"></span>
115 118
                                     <span className="hidden-sm">
116 119
                                         &nbsp;<FormattedMessage {...sidebarLayoutMessages["app.sidebarLayout.search"]} />
@@ -121,7 +124,7 @@ export default class SidebarLayoutIntl extends Component {
121 124
                     </nav>
122 125
                 </div>
123 126
 
124
-                <div className="col-sm-11 col-sm-offset-1 col-md-10 col-md-offset-2 main-panel">
127
+                <div className="col-sm-11 col-sm-offset-1 col-md-10 col-md-offset-2" styleName="main-panel">
125 128
                     {this.props.children}
126 129
                 </div>
127 130
             </div>
@@ -135,5 +138,4 @@ SidebarLayoutIntl.propTypes = {
135 138
     intl: intlShape.isRequired
136 139
 };
137 140
 
138
-export let SidebarLayout = injectIntl(SidebarLayoutIntl);
139
-export default SidebarLayout;
141
+export default injectIntl(CSSModules(SidebarLayoutIntl, css));

+ 3
- 3
app/dist/1.1.js
File diff suppressed because it is too large
View File


+ 1
- 1
app/dist/1.1.js.map
File diff suppressed because it is too large
View File


+ 0
- 38
app/dist/3.3.js
File diff suppressed because it is too large
View File


+ 1
- 1
app/dist/fix.ie9.js View File

@@ -1,2 +1,2 @@
1
-!function(e){function t(r){if(n[r])return n[r].exports;var a=n[r]={exports:{},id:r,loaded:!1};return e[r].call(a.exports,a,a.exports,t),a.loaded=!0,a.exports}var n={};return t.m=e,t.c=n,t.p="./",t(0)}({0:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=n(526);Object.keys(r).forEach(function(e){"default"!==e&&Object.defineProperty(t,e,{enumerable:!0,get:function(){return r[e]}})})},526:function(e,t){!function(t,n){function r(e,t){var n=e.createElement("p"),r=e.getElementsByTagName("head")[0]||e.documentElement;return n.innerHTML="x<style>"+t+"</style>",r.insertBefore(n.lastChild,r.firstChild)}function a(){var e=b.elements;return"string"==typeof e?e.split(" "):e}function o(e,t){var n=b.elements;"string"!=typeof n&&(n=n.join(" ")),"string"!=typeof e&&(e=e.join(" ")),b.elements=n+" "+e,s(t)}function c(e){var t=E[e[v]];return t||(t={},y++,e[v]=y,E[y]=t),t}function i(e,t,r){if(t||(t=n),f)return t.createElement(e);r||(r=c(t));var a;return a=r.cache[e]?r.cache[e].cloneNode():g.test(e)?(r.cache[e]=r.createElem(e)).cloneNode():r.createElem(e),!a.canHaveChildren||p.test(e)||a.tagUrn?a:r.frag.appendChild(a)}function l(e,t){if(e||(e=n),f)return e.createDocumentFragment();t=t||c(e);for(var r=t.frag.cloneNode(),o=0,i=a(),l=i.length;o<l;o++)r.createElement(i[o]);return r}function u(e,t){t.cache||(t.cache={},t.createElem=e.createElement,t.createFrag=e.createDocumentFragment,t.frag=t.createFrag()),e.createElement=function(n){return b.shivMethods?i(n,e,t):t.createElem(n)},e.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+a().join().replace(/[\w\-:]+/g,function(e){return t.createElem(e),t.frag.createElement(e),'c("'+e+'")'})+");return n}")(b,t.frag)}function s(e){e||(e=n);var t=c(e);return!b.shivCSS||d||t.hasCSS||(t.hasCSS=!!r(e,"article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}")),f||u(e,t),e}var d,f,m="3.7.3-pre",h=t.html5||{},p=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,g=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,v="_html5shiv",y=0,E={};!function(){try{var e=n.createElement("a");e.innerHTML="<xyz></xyz>",d="hidden"in e,f=1==e.childNodes.length||function(){n.createElement("a");var e=n.createDocumentFragment();return"undefined"==typeof e.cloneNode||"undefined"==typeof e.createDocumentFragment||"undefined"==typeof e.createElement}()}catch(t){d=!0,f=!0}}();var b={elements:h.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video",version:m,shivCSS:h.shivCSS!==!1,supportsUnknownElements:f,shivMethods:h.shivMethods!==!1,type:"default",shivDocument:s,createElement:i,createDocumentFragment:l,addElements:o};t.html5=b,s(n),"object"==typeof e&&e.exports&&(e.exports=b)}("undefined"!=typeof window?window:this,document)}});
1
+!function(e){function t(r){if(n[r])return n[r].exports;var a=n[r]={exports:{},id:r,loaded:!1};return e[r].call(a.exports,a,a.exports,t),a.loaded=!0,a.exports}var n={};return t.m=e,t.c=n,t.p="./",t(0)}({0:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=n(585);Object.keys(r).forEach(function(e){"default"!==e&&Object.defineProperty(t,e,{enumerable:!0,get:function(){return r[e]}})})},585:function(e,t){!function(t,n){function r(e,t){var n=e.createElement("p"),r=e.getElementsByTagName("head")[0]||e.documentElement;return n.innerHTML="x<style>"+t+"</style>",r.insertBefore(n.lastChild,r.firstChild)}function a(){var e=b.elements;return"string"==typeof e?e.split(" "):e}function o(e,t){var n=b.elements;"string"!=typeof n&&(n=n.join(" ")),"string"!=typeof e&&(e=e.join(" ")),b.elements=n+" "+e,s(t)}function c(e){var t=E[e[v]];return t||(t={},y++,e[v]=y,E[y]=t),t}function i(e,t,r){if(t||(t=n),f)return t.createElement(e);r||(r=c(t));var a;return a=r.cache[e]?r.cache[e].cloneNode():g.test(e)?(r.cache[e]=r.createElem(e)).cloneNode():r.createElem(e),!a.canHaveChildren||p.test(e)||a.tagUrn?a:r.frag.appendChild(a)}function l(e,t){if(e||(e=n),f)return e.createDocumentFragment();t=t||c(e);for(var r=t.frag.cloneNode(),o=0,i=a(),l=i.length;o<l;o++)r.createElement(i[o]);return r}function u(e,t){t.cache||(t.cache={},t.createElem=e.createElement,t.createFrag=e.createDocumentFragment,t.frag=t.createFrag()),e.createElement=function(n){return b.shivMethods?i(n,e,t):t.createElem(n)},e.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+a().join().replace(/[\w\-:]+/g,function(e){return t.createElem(e),t.frag.createElement(e),'c("'+e+'")'})+");return n}")(b,t.frag)}function s(e){e||(e=n);var t=c(e);return!b.shivCSS||d||t.hasCSS||(t.hasCSS=!!r(e,"article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}")),f||u(e,t),e}var d,f,m="3.7.3-pre",h=t.html5||{},p=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,g=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,v="_html5shiv",y=0,E={};!function(){try{var e=n.createElement("a");e.innerHTML="<xyz></xyz>",d="hidden"in e,f=1==e.childNodes.length||function(){n.createElement("a");var e=n.createDocumentFragment();return"undefined"==typeof e.cloneNode||"undefined"==typeof e.createDocumentFragment||"undefined"==typeof e.createElement}()}catch(t){d=!0,f=!0}}();var b={elements:h.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video",version:m,shivCSS:h.shivCSS!==!1,supportsUnknownElements:f,shivMethods:h.shivMethods!==!1,type:"default",shivDocument:s,createElement:i,createDocumentFragment:l,addElements:o};t.html5=b,s(n),"object"==typeof e&&e.exports&&(e.exports=b)}("undefined"!=typeof window?window:this,document)}});
2 2
 //# sourceMappingURL=fix.ie9.js.map

+ 1
- 1
app/dist/fix.ie9.js.map
File diff suppressed because it is too large
View File


+ 24
- 23
app/dist/index.js
File diff suppressed because it is too large
View File


+ 1
- 1
app/dist/index.js.map
File diff suppressed because it is too large
View File


+ 0
- 1
app/dist/reactIntlMessages.json View File

@@ -1 +0,0 @@
1
-[]

+ 3
- 7
app/dist/style.css
File diff suppressed because it is too large
View File


+ 27
- 0
app/styles/Album.scss View File

@@ -0,0 +1,27 @@
1
+.row {
2
+    margin-top: 30px;
3
+}
4
+
5
+.art {
6
+    composes: art from "./elements/Grid.scss";
7
+}
8
+
9
+@media (max-width: 767px) {
10
+    .nameRow h2 {
11
+        margin-top: 0;
12
+        margin-bottom: 0;
13
+    }
14
+
15
+    .artRow p,
16
+    .artRow img {
17
+        margin: 0;
18
+    }
19
+
20
+    .nameRow,
21
+    .artRow {
22
+        float: none;
23
+        display: inline-block;
24
+        vertical-align: middle;
25
+        margin-bottom: 10px;
26
+    }
27
+}

+ 13
- 0
app/styles/Artist.scss View File

@@ -0,0 +1,13 @@
1
+.name > h1 {
2
+    margin-bottom: 0;
3
+}
4
+
5
+.name > hr {
6
+    margin-top: 0.5em;  /* Default value. */
7
+}
8
+
9
+.art {
10
+    composes: art from "./elements/Grid.scss";
11
+}
12
+
13
+/* TODO: Use table-condensed if xs screen */

+ 9
- 0
app/styles/Login.scss View File

@@ -0,0 +1,9 @@
1
+.titleImage {
2
+    height: 46px;
3
+}
4
+
5
+@media (max-width: 767px) {
6
+    .submit {
7
+        text-align: center;
8
+    }
9
+}

+ 0
- 248
app/styles/ampache.css View File

@@ -1,248 +0,0 @@
1
-/* Firefox hack for responsive table */
2
-@-moz-document url-prefix() {
3
-    fieldset {
4
-        display: table-cell;
5
-    }
6
-}
7
-
8
-/*
9
- * Sidebar
10
- */
11
-.sidebar {
12
-    position: fixed;
13
-    top: 0;
14
-    bottom: 0;
15
-    left: 0;
16
-    z-index: 1000;
17
-    display: block;
18
-    padding: 20px;
19
-    overflow-x: hidden;
20
-    overflow-y: auto; /* Scrollable contents if viewport is shorter than content. */
21
-    background-color: #333;
22
-    color: white;
23
-    border-right: 1px solid #eee;
24
-}
25
-
26
-/* Sidebar elements */
27
-.sidebar a {
28
-    color: white;
29
-}
30
-
31
-.sidebar h1 {
32
-    margin: 0;
33
-    margin-bottom: 20px;
34
-}
35
-
36
-.sidebar h1 img {
37
-    height: 46px;
38
-}
39
-
40
-.sidebar h1 a {
41
-    text-decoration: none;
42
-}
43
-
44
-/* Sidebar navigation */
45
-.sidebar .navbar {
46
-    border: none;
47
-}
48
-
49
-.nav-sidebar {
50
-    margin-right: -20px;
51
-    margin-bottom: 20px;
52
-    margin-left: -20px;
53
-}
54
-
55
-.nav-sidebar > li > a {
56
-    padding-right: 20px;
57
-    padding-left: 20px;
58
-}
59
-
60
-@media (max-width: 991px) {
61
-    .sidebar,
62
-    .sidebar .navbar .container-fluid {
63
-        padding-left: 5px;
64
-        padding-right: 5px;
65
-    }
66
-
67
-    .sidebar .nav-list {
68
-        text-align: right;
69
-    }
70
-
71
-    .sidebar .nav-sidebar > li > a {
72
-        padding-left: 20px;
73
-        padding-right: 20px;
74
-    }
75
-
76
-    .sidebar .nav-list > li > a {
77
-        padding-left: 20px;
78
-        padding-right: 20px;
79
-    }
80
-}
81
-
82
-.nav-sidebar .nav-sidebar {
83
-    margin-bottom: 0;  /* No margin bottom for nested nav-sidebar. */
84
-}
85
-
86
-.nav-sidebar > .active > a:focus,
87
-.nav > li > a:focus {
88
-    color: #fff;
89
-    background-color: transparent;
90
-}
91
-
92
-.nav-sidebar > .active > a,
93
-.nav-sidebar > .active > a:hover,
94
-.nav > li > a:hover {
95
-    color: #fff;
96
-    background-color: #222;
97
-}
98
-
99
-.nav > li > a.active {
100
-    background-color: #222;
101
-}
102
-
103
-.icon-navbar {
104
-    background-color: #555;
105
-    font-size: 1.25em;
106
-}
107
-
108
-.icon-navbar-nav {
109
-    display: inline-block;
110
-    float: none;
111
-    vertical-align: top;
112
-    text-align: center;
113
-}
114
-
115
-/*
116
- * Main content
117
- */
118
-.main-panel {
119
-    padding: 20px;
120
-}
121
-
122
-@media (min-width: 768px) {
123
-    .main-panel {
124
-        padding-right: 40px;
125
-        padding-left: 40px;
126
-    }
127
-}
128
-
129
-/*
130
- * Filtering field
131
- */
132
-div.filter {
133
-    margin-bottom: 34px;
134
-}
135
-
136
-.filter-legend {
137
-    text-align: right;
138
-    line-height: 34px;
139
-}
140
-
141
-@media (max-width: 767px) {
142
-    .filter-legend {
143
-        text-align: center;
144
-    }
145
-}
146
-
147
-@media (min-width: 767px) {
148
-    .filter .form-group {
149
-        width: 75%;
150
-    }
151
-}
152
-
153
-/*
154
- * Placeholder dashboard ideas
155
- */
156
-.placeholders {
157
-    margin-bottom: 30px;
158
-    text-align: center;
159
-}
160
-
161
-.placeholders h4 {
162
-    margin-bottom: 0;
163
-}
164
-
165
-.placeholder img:hover {
166
-    transform: scale(1.1);
167
-    cursor: pointer;
168
-}
169
-
170
-/**
171
- * Pager
172
- */
173
-.pagination-nav {
174
-    text-align: center;
175
-}
176
-
177
-.pagination > li > span {
178
-    cursor: pointer;
179
-}
180
-
181
-/**
182
- * Login screen
183
- */
184
-.login h1 img {
185
-    height: 46px;
186
-}
187
-
188
-@media (max-width: 767px) {
189
-    .login .submit {
190
-        text-align: center;
191
-    }
192
-}
193
-
194
-/**
195
- * Misc
196
- */
197
-
198
-.art {
199
-    display: inline-block;
200
-    margin-bottom: 10px;
201
-    width: 75%;
202
-    height: auto;
203
-
204
-    /* doiuse-disable viewport-units */
205
-
206
-    max-width: 25vw;
207
-    max-height: 25vw;
208
-
209
-    /* doiuse-enable viewport-units */
210
-}
211
-
212
-.albumRow {
213
-    margin-top: 30px;
214
-}
215
-
216
-.artistNameRow h1 {
217
-    margin-bottom: 0;
218
-}
219
-
220
-.artistNameRow hr {
221
-    margin-top: 0.5em;  /* Default value. */
222
-}
223
-
224
-@media (max-width: 767px) {
225
-    .table-responsive {
226
-        border: none;
227
-    }
228
-
229
-    .albumRowName h2 {
230
-        margin-top: 0;
231
-        margin-bottom: 0;
232
-    }
233
-
234
-    .albumRowArt p,
235
-    .albumRowArt img {
236
-        margin: 0;
237
-    }
238
-
239
-    .albumRowName,
240
-    .albumRowArt {
241
-        float: none;
242
-        display: inline-block;
243
-        vertical-align: middle;
244
-        margin-bottom: 10px;
245
-    }
246
-}
247
-
248
-/* TODO: Use table-condensed if xs screen */

+ 7
- 0
app/styles/common/common.scss View File

@@ -0,0 +1,7 @@
1
+:global {
2
+    @media (max-width: 767px) {
3
+        .table-responsive {
4
+            border: none;
5
+        }
6
+    }
7
+}

+ 8
- 0
app/styles/common/hacks.scss View File

@@ -0,0 +1,8 @@
1
+:global {
2
+    /* Firefox hack for responsive table */
3
+    @-moz-document url-prefix() {
4
+        fieldset {
5
+            display: table-cell;
6
+        }
7
+    }
8
+}

+ 2
- 0
app/styles/common/index.js View File

@@ -0,0 +1,2 @@
1
+export * from "./hacks.scss";
2
+export * from "./common.scss";

+ 20
- 0
app/styles/elements/FilterBar.scss View File

@@ -0,0 +1,20 @@
1
+.filter {
2
+    margin-bottom: 34px;
3
+}
4
+
5
+.legend {
6
+    text-align: right;
7
+    line-height: 34px;
8
+}
9
+
10
+@media (max-width: 767px) {
11
+    .legend {
12
+        text-align: center;
13
+    }
14
+}
15
+
16
+@media (min-width: 767px) {
17
+    .form-group {
18
+        width: 75%;
19
+    }
20
+}

+ 26
- 0
app/styles/elements/Grid.scss View File

@@ -0,0 +1,26 @@
1
+.placeholders {
2
+    margin-bottom: 30px;
3
+    text-align: center;
4
+}
5
+
6
+.name {
7
+    margin-bottom: 0;
8
+}
9
+
10
+.art {
11
+    display: inline-block;
12
+    margin-bottom: 10px;
13
+    width: 75%;
14
+    height: auto;
15
+
16
+    /* doiuse-disable viewport-units */
17
+
18
+    max-width: 25vw;
19
+
20
+    /* doiuse-enable viewport-units */
21
+}
22
+
23
+.art:hover {
24
+    transform: scale(1.1);
25
+    cursor: pointer;
26
+}

+ 7
- 0
app/styles/elements/Pagination.scss View File

@@ -0,0 +1,7 @@
1
+.nav {
2
+    text-align: center;
3
+}
4
+
5
+.pointer {
6
+    cursor: pointer;
7
+}

+ 100
- 0
app/styles/layouts/Sidebar.scss View File

@@ -0,0 +1,100 @@
1
+$background: #333;
2
+$hoverBackground: #222;
3
+$activeBackground: $hoverBackground;
4
+$foreground: white;
5
+$lightgrey: #eee;
6
+
7
+.sidebar {
8
+    position: fixed;
9
+    top: 0;
10
+    bottom: 0;
11
+    left: 0;
12
+    z-index: 1000;
13
+    display: block;
14
+    padding: 20px;
15
+    overflow-x: hidden;
16
+    overflow-y: auto; /* Scrollable contents if viewport is shorter than content. */
17
+    background-color: $background;
18
+    color: white;
19
+    border-right: 1px solid $lightgrey;
20
+}
21
+
22
+/* Sidebar elements */
23
+.link {
24
+    color: $foreground;
25
+    text-decoration: none;
26
+}
27
+
28
+.link:hover,
29
+.link:focus {
30
+    color: $foreground;
31
+    background-color: $hoverBackground !important;  /* TODO: important */
32
+}
33
+
34
+.active {
35
+    composes: link;
36
+    background-color: $activeBackground;
37
+}
38
+
39
+.title {
40
+    margin: 0;
41
+    margin-bottom: 20px;
42
+}
43
+
44
+.imgTitle {
45
+    height: 46px;
46
+}
47
+
48
+/* Sidebar navigation */
49
+.icon-navbar {
50
+    background-color: #555;
51
+    font-size: 1.25em;
52
+    border: none;
53
+
54
+    .nav {
55
+        display: inline-block;
56
+        float: none;
57
+        vertical-align: top;
58
+        text-align: center;
59
+    }
60
+}
61
+
62
+/*
63
+ * Main content
64
+ */
65
+.main-panel {
66
+    padding: 20px;
67
+}
68
+
69
+/*
70
+ * Media queries
71
+ * TODO: Sidebar responsiveness
72
+ */
73
+/*@media (max-width: 991px) {
74
+    .sidebar,
75
+    .sidebar .navbar .container-fluid {
76
+        padding-left: 5px;
77
+        padding-right: 5px;
78
+    }
79
+
80
+    .sidebar .nav-list {
81
+        text-align: right;
82
+    }
83
+
84
+    .sidebar .nav-sidebar > li > a {
85
+        padding-left: 20px;
86
+        padding-right: 20px;
87
+    }
88
+
89
+    .sidebar .nav-list > li > a {
90
+        padding-left: 20px;
91
+        padding-right: 20px;
92
+    }
93
+}
94
+
95
+@media (min-width: 768px) {
96
+    .main-panel {
97
+        padding-right: 40px;
98
+        padding-left: 40px;
99
+    }
100
+}*/

+ 3
- 2
hooks/pre-commit View File

@@ -4,7 +4,6 @@ then
4 4
 	against=HEAD
5 5
 else
6 6
 	# Initial commit
7
-    # TODO
8 7
     exit 1
9 8
 fi
10 9
 
@@ -17,5 +16,7 @@ then
17 16
 fi
18 17
 
19 18
 echo "Rebuilding dist JavaScript files…"
20
-NODE_ENV=production npm run build
19
+export NODE_ENV=production
20
+npm run clean
21
+npm run build
21 22
 git add app/dist

+ 0
- 5
index.all.js View File

@@ -1,8 +1,3 @@
1
-// Export
2
-import "bootstrap";
3
-import "bootstrap/dist/css/bootstrap.css";
4
-import "./app/styles/ampache.css";
5
-
6 1
 // Handle app init
7 2
 import React from "react";
8 3
 import ReactDOM from "react-dom";

+ 7
- 3
package.json View File

@@ -11,12 +11,13 @@
11 11
     "watch": "./node_modules/.bin/webpack --progress  --watch",
12 12
     "prod": "NODE_ENV=production ./node_modules/.bin/webpack --progress",
13 13
     "extractTranslations": "./node_modules/.bin/babel-node scripts/extractTranslations.js",
14
-    "clean": "./node_modules/.bin/rimraf app/dist"
14
+    "clean": "./node_modules/.bin/rimraf .cache && ./node_modules/.bin/rimraf app/dist"
15 15
   },
16 16
   "dependencies": {
17 17
     "babel-polyfill": "^6.9.1",
18 18
     "babel-preset-es2015": "^6.9.0",
19
-    "bootstrap": "^3.3.6",
19
+    "bootstrap-loader": "^1.0.10",
20
+    "bootstrap-sass": "^3.3.7",
20 21
     "fuse.js": "^2.3.0",
21 22
     "html5shiv": "^3.7.3",
22 23
     "humps": "^1.1.0",
@@ -29,6 +30,7 @@
29 30
     "jssha": "^2.1.0",
30 31
     "lodash": "^4.13.1",
31 32
     "react": "^15.2.0",
33
+    "react-css-modules": "^3.7.9",
32 34
     "react-dom": "^15.2.0",
33 35
     "react-intl": "^2.1.3",
34 36
     "react-redux": "^4.4.5",
@@ -44,7 +46,6 @@
44 46
     "babel-core": "^6.10.4",
45 47
     "babel-loader": "^6.2.4",
46 48
     "babel-plugin-react-intl": "^2.1.3",
47
-    "babel-plugin-transform-runtime": "^6.12.0",
48 49
     "babel-preset-react": "^6.11.1",
49 50
     "css-loader": "^0.23.1",
50 51
     "doiuse": "^2.4.1",
@@ -55,6 +56,7 @@
55 56
     "extract-text-webpack-plugin": "^1.0.1",
56 57
     "file-loader": "^0.9.0",
57 58
     "glob": "^7.0.5",
59
+    "node-sass": "^3.8.0",
58 60
     "postcss": "^5.1.0",
59 61
     "postcss-loader": "^0.9.1",
60 62
     "postcss-reporter": "^1.4.1",
@@ -63,7 +65,9 @@
63 65
     "react-intl-webpack-plugin": "0.0.3",
64 66
     "redbox-react": "^1.2.10",
65 67
     "redux-logger": "^2.6.1",
68
+    "resolve-url-loader": "^1.6.0",
66 69
     "rimraf": "^2.5.4",
70
+    "sass-loader": "^4.0.0",
67 71
     "style-loader": "^0.13.1",
68 72
     "stylelint": "^7.0.3",
69 73
     "stylelint-config-standard": "^11.0.0",

+ 13
- 17
webpack.config.base.js View File

@@ -11,7 +11,7 @@ var browsers = ["ie >= 9", "> 1%", "last 3 versions", "not op_mini all"];
11 11
 
12 12
 module.exports = {
13 13
     entry: {
14
-        "index": ["babel-polyfill", "./index.js"],
14
+        "index": ["babel-polyfill", "bootstrap-loader", "./app/styles/common/index.js", "./index.js"],
15 15
         "fix.ie9": "./fix.ie9.js"
16 16
     },
17 17
 
@@ -40,24 +40,22 @@ module.exports = {
40 40
                 },
41 41
                 include: __dirname
42 42
             },
43
-            // Do not postcss vendor modules
44 43
             {
45 44
                 test: /\.css$/,
46
-                exclude: /node_modules/,
47
-                loader: ExtractTextPlugin.extract("style-loader", "css-loader!postcss-loader")
48
-            },
49
-            {
50
-                test: /\.css$/,
51
-                exclude: /app/,
52
-                loader: ExtractTextPlugin.extract("style-loader", "css-loader")
53
-            },
54
-            {
55
-                test: /\.less$/,
56
-                loader: ExtractTextPlugin.extract("style-loader", "css-loader!less-loader")
45
+                loader: ExtractTextPlugin.extract(
46
+                    "style-loader",
47
+                    "css-loader?modules&importLoaders=1&localIdentName=[name]__[local]__[hash:base64:5]" +
48
+                    "!postcss-loader"
49
+                )
57 50
             },
58 51
             {
59 52
                 test: /\.scss$/,
60
-                loader: ExtractTextPlugin.extract("style-loader", "css-loader!sass-loader")
53
+                loader: ExtractTextPlugin.extract(
54
+                    "style-loader",
55
+                    "css-loader?modules&importLoaders=1&localIdentName=[name]__[local]__[hash:base64:5]" +
56
+                    // TODO: "!postcss-loader" +
57
+                    "!sass-loader"
58
+                )
61 59
             },
62 60
             {
63 61
                 test: /\.eot(\?v=\d+\.\d+\.\d+)?$/,
@@ -86,9 +84,7 @@ module.exports = {
86 84
         new ExtractTextPlugin("style.css", { allChunks: true })
87 85
     ],
88 86
 
89
-    postcss: function () {
90
-        return [doiuse({ browsers: browsers }), stylelint, precss, autoprefixer({ browsers: browsers }), postcssReporter({ throwError: true, clearMessages: true })];
91
-    },
87
+    postcss: [doiuse({ browsers: browsers }), stylelint, precss, autoprefixer({ browsers: browsers }), postcssReporter({ throwError: true, clearMessages: true })],
92 88
 
93 89
     resolve: {
94 90
         // Include empty string "" to resolve files by their explicit extension

+ 2
- 2
webpack.config.development.js View File

@@ -4,9 +4,9 @@ var config = require("./webpack.config.base.js");
4 4
 config.devtool = "cheap-module-eval-source-map";
5 5
 
6 6
 // necessary for hot reloading with IE:
7
-config.entry.eventsourcePolyfill = 'eventsource-polyfill';
7
+config.entry.index.splice(1, 0, 'eventsource-polyfill');
8 8
 // listen to code updates emitted by hot middleware:
9
-config.entry.webpackHotMiddlewareClient = 'webpack-hot-middleware/client';
9
+config.entry.index.splice(2, 0, 'webpack-hot-middleware/client');
10 10
 
11 11
 config.plugins = config.plugins.concat([
12 12
     new webpack.HotModuleReplacementPlugin()