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.
This commit is contained in:
Lucas Verney 2016-07-29 23:57:21 +02:00
父節點 921028c073
當前提交 2d9747482b
共有 36 個文件被更改,包括 500 次插入432 次删除

116
.bootstraprc Normal file
查看文件

@ -0,0 +1,116 @@
---
# Output debugging info
loglevel: disabled
# Major version of Bootstrap: 3 or 4
bootstrapVersion: 3
# If Bootstrap version 3 is used - turn on/off custom icon font path
useCustomIconFontPath: false
# Webpack loaders, order matters
styleLoaders:
- style
- css
- sass
# Extract styles to stand-alone css file
# Different settings for different environments can be used,
# It depends on value of NODE_ENV environment variable
# This param can also be set in webpack config:
# entry: 'bootstrap-loader/extractStyles'
extractStyles: true
# env:
# development:
# extractStyles: false
# production:
# extractStyles: true
# Customize Bootstrap variables that get imported before the original Bootstrap variables.
# Thus, derived Bootstrap variables can depend on values from here.
# See the Bootstrap _variables.scss file for examples of derived Bootstrap variables.
#
# preBootstrapCustomizations: ./path/to/bootstrap/pre-customizations.scss
# This gets loaded after bootstrap/variables is loaded
# Thus, you may customize Bootstrap variables
# based on the values established in the Bootstrap _variables.scss file
#
# bootstrapCustomizations: ./path/to/bootstrap/customizations.scss
# Import your custom styles here
# Usually this endpoint-file contains list of @imports of your application styles
#
# appStyles: ./path/to/your/app/styles/endpoint.scss
### Bootstrap styles
styles:
# Mixins
mixins: true
# Reset and dependencies
normalize: true
print: true
glyphicons: true
# Core CSS
scaffolding: true
type: true
code: true
grid: true
tables: true
forms: true
buttons: true
# Components
component-animations: true
dropdowns: true
button-groups: true
input-groups: true
navs: true
navbar: true
breadcrumbs: true
pagination: true
pager: true
labels: true
badges: true
jumbotron: true
thumbnails: true
alerts: true
progress-bars: true
media: true
list-group: true
panels: true
wells: true
responsive-embed: true
close: true
# Components w/ JavaScript
modals: true
tooltip: true
popovers: true
carousel: true
# Utility classes
utilities: true
responsive-utilities: true
### Bootstrap scripts
scripts:
transition: true
alert: true
button: true
carousel: true
collapse: true
dropdown: true
modal: true
tooltip: true
popover: true
scrollspy: true
tab: true
affix: true

查看文件

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

7
TODO
查看文件

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

查看文件

@ -1,7 +1,10 @@
import React, { Component, PropTypes } from "react";
import CSSModules from "react-css-modules";
import { formatLength } from "../utils";
import css from "../styles/Album.scss";
export class AlbumTrackRow extends Component {
render () {
const length = formatLength(this.props.track.length);
@ -40,28 +43,34 @@ AlbumTracksTable.propTypes = {
tracks: PropTypes.array.isRequired
};
export class AlbumRow extends Component {
class AlbumRowCSS extends Component {
render () {
return (
<div className="row albumRow">
<div className="col-sm-offset-2 col-xs-10 albumRowName">
<div className="row" styleName="row">
<div className="col-sm-offset-2 col-xs-10" styleName="nameRow">
<h2>{this.props.album.name}</h2>
</div>
<div className="col-xs-2 albumRowArt">
<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>
<div className="col-xs-2" styleName="artRow">
<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>
</div>
<div className="col-sm-10 table-responsive">
<AlbumTracksTable tracks={this.props.album.tracks} />
{
Array.isArray(this.props.album.tracks) ?
<AlbumTracksTable tracks={this.props.album.tracks} /> :
null
}
</div>
</div>
);
}
}
AlbumRow.propTypes = {
AlbumRowCSS.propTypes = {
album: PropTypes.object.isRequired
};
export let AlbumRow = CSSModules(AlbumRowCSS, css);
export default class Album extends Component {
render () {
return (

查看文件

@ -1,8 +1,11 @@
import React, { Component, PropTypes } from "react";
import CSSModules from "react-css-modules";
import { AlbumRow } from "./Album";
export default class Artist extends Component {
import css from "../styles/Artist.scss";
class ArtistCSS extends Component {
render () {
var albumsRows = [];
if (Array.isArray(this.props.artist.albums)) {
@ -12,7 +15,7 @@ export default class Artist extends Component {
}
return (
<div>
<div className="row artistNameRow">
<div className="row" styleName="name">
<div className="col-sm-12">
<h1>{this.props.artist.name}</h1>
<hr/>
@ -23,7 +26,7 @@ export default class Artist extends Component {
<p>{this.props.artist.summary}</p>
</div>
<div className="col-sm-3 text-center">
<p><img src={this.props.artist.art} width="200" height="200" className="img-responsive img-circle art" alt={this.props.artist.name}/></p>
<p><img src={this.props.artist.art} width="200" height="200" className="img-responsive img-circle" styleName="art" alt={this.props.artist.name}/></p>
</div>
</div>
{ albumsRows }
@ -32,6 +35,8 @@ export default class Artist extends Component {
}
}
Artist.propTypes = {
ArtistCSS.propTypes = {
artist: PropTypes.object.isRequired
};
export default CSSModules(ArtistCSS, css);

查看文件

@ -1,12 +1,15 @@
import React, { Component, PropTypes } from "react";
import CSSModules from "react-css-modules";
import { defineMessages, injectIntl, intlShape, FormattedMessage } from "react-intl";
import { messagesMap } from "../utils";
import messages from "../locales/messagesDescriptors/Login";
import css from "../styles/Login.scss";
const loginMessages = defineMessages(messagesMap(messages));
class LoginFormIntl extends Component {
class LoginFormCSSIntl extends Component {
constructor (props) {
super(props);
@ -61,8 +64,8 @@ class LoginFormIntl extends Component {
{
this.props.error ?
<div className="row">
<div className="alert alert-danger">
<p id="loginFormError">
<div className="alert alert-danger" id="loginFormError">
<p>
<span className="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span> { this.props.error }
</p>
</div>
@ -105,7 +108,7 @@ class LoginFormIntl extends Component {
<FormattedMessage {...loginMessages["app.login.rememberMe"]} />
</label>
</div>
<div className="col-sm-6 col-sm-12 submit text-right">
<div className="col-sm-6 col-xs-12 text-right" styleName="submit">
<input type="submit" className="btn btn-default" aria-label={formatMessage(loginMessages["app.login.signIn"])} defaultValue={formatMessage(loginMessages["app.login.signIn"])} disabled={this.props.isAuthenticating} />
</div>
</div>
@ -119,7 +122,7 @@ class LoginFormIntl extends Component {
}
}
LoginFormIntl.propTypes = {
LoginFormCSSIntl.propTypes = {
username: PropTypes.string,
endpoint: PropTypes.string,
rememberMe: PropTypes.bool,
@ -130,10 +133,10 @@ LoginFormIntl.propTypes = {
intl: intlShape.isRequired,
};
export let LoginForm = injectIntl(LoginFormIntl);
export let LoginForm = injectIntl(CSSModules(LoginFormCSSIntl, css));
export default class Login extends Component {
class Login extends Component {
render () {
const greeting = (
<p>
@ -141,8 +144,8 @@ export default class Login extends Component {
</p>
);
return (
<div className="login text-center container-fluid">
<h1><img src="./app/assets/img/ampache-blue.png" alt="A"/>mpache</h1>
<div className="text-center container-fluid">
<h1><img styleName="titleImage" src="./app/assets/img/ampache-blue.png" alt="A"/>mpache</h1>
<hr/>
{(!this.props.error && !this.props.info) ? greeting : null}
<div className="col-sm-9 col-sm-offset-2 col-md-6 col-md-offset-3">
@ -162,3 +165,5 @@ Login.propTypes = {
error: PropTypes.string,
info: PropTypes.string
};
export default CSSModules(Login, css);

查看文件

@ -65,13 +65,13 @@ export class SongsTable extends Component {
<FormattedMessage {...songsMessages["app.songs.title"]} />
</th>
<th>
<FormattedMessage {...songsMessages["app.common.artist"]} />
<FormattedMessage {...songsMessages["app.common.artist"]} values={{itemCount: 1}} />
</th>
<th>
<FormattedMessage {...songsMessages["app.common.album"]} />
<FormattedMessage {...songsMessages["app.common.album"]} values={{itemCount: 1}} />
</th>
<th>
<FormattedMessage {...songsMessages["app.common.genre"]} />
<FormattedMessage {...songsMessages["app.songs.genre"]} />
</th>
<th>
<FormattedMessage {...songsMessages["app.songs.length"]} />

查看文件

@ -1,12 +1,15 @@
import React, { Component, PropTypes } from "react";
import CSSModules from "react-css-modules";
import { defineMessages, injectIntl, intlShape, FormattedMessage } from "react-intl";
import { messagesMap } from "../../utils";
import messages from "../../locales/messagesDescriptors/elements/FilterBar";
import css from "../../styles/elements/FilterBar.scss";
const filterMessages = defineMessages(messagesMap(messages));
class FilterBarIntl extends Component {
class FilterBarCSSIntl extends Component {
constructor (props) {
super(props);
this.handleChange = this.handleChange.bind(this);
@ -21,14 +24,14 @@ class FilterBarIntl extends Component {
render () {
const {formatMessage} = this.props.intl;
return (
<div className="filter">
<p className="col-xs-12 col-sm-6 col-md-4 col-md-offset-1 filter-legend" id="filterInputDescription">
<div styleName="filter">
<p className="col-xs-12 col-sm-6 col-md-4 col-md-offset-1" styleName="legend" id="filterInputDescription">
<FormattedMessage {...filterMessages["app.filter.whatAreWeListeningToToday"]} />
</p>
<div className="col-xs-12 col-sm-6 col-md-4 input-group">
<form className="form-inline" onSubmit={this.handleChange} aria-describedby="filterInputDescription">
<div className="form-group">
<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" />
<div className="form-group" styleName="form-group">
<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" />
</div>
</form>
</div>
@ -37,11 +40,10 @@ class FilterBarIntl extends Component {
}
}
FilterBarIntl.propTypes = {
FilterBarCSSIntl.propTypes = {
onUserInput: PropTypes.func,
filterText: PropTypes.string,
intl: intlShape.isRequired
};
export let FilterBar = injectIntl(FilterBarIntl);
export default FilterBar;
export default injectIntl(CSSModules(FilterBarCSSIntl, css));

查看文件

@ -1,5 +1,6 @@
import React, { Component, PropTypes } from "react";
import { Link} from "react-router";
import CSSModules from "react-css-modules";
import imagesLoaded from "imagesloaded";
import Isotope from "isotope-layout";
import Fuse from "fuse.js";
@ -8,7 +9,9 @@ import _ from "lodash";
import FilterBar from "./FilterBar";
import Pagination from "./Pagination";
export class GridItem extends Component {
import css from "../../styles/elements/Grid.scss";
class GridItemCSS extends Component {
render () {
var nSubItems = this.props.item[this.props.subItemsType];
if (Array.isArray(nSubItems)) {
@ -27,10 +30,10 @@ export class GridItem extends Component {
// TODO: i18n
const title = "Go to " + this.props.itemsType.rstrip("s") + " page";
return (
<div className="grid-item col-xs-6 col-sm-3 placeholders" id={id}>
<div className="grid-item-content placeholder text-center">
<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>
<h4 className="name">{this.props.item.name}</h4>
<div className="grid-item col-xs-6 col-sm-3" styleName="placeholders" id={id}>
<div className="grid-item-content text-center">
<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>
<h4 className="name" styleName="name">{this.props.item.name}</h4>
<span className="sub-items text-muted"><span className="n-sub-items">{nSubItems}</span> <span className="sub-items-type">{subItemsLabel}</span></span>
</div>
</div>
@ -38,12 +41,14 @@ export class GridItem extends Component {
}
}
GridItem.propTypes = {
GridItemCSS.propTypes = {
item: PropTypes.object.isRequired,
itemsType: PropTypes.string.isRequired,
subItemsType: PropTypes.string.isRequired
};
export let GridItem = CSSModules(GridItemCSS, css);
const ISOTOPE_OPTIONS = { /** Default options for Isotope grid layout. */
getSortData: {
@ -93,9 +98,9 @@ export class Grid extends Component {
// Apply filter on grid
this.iso.arrange({
filter: function () {
var name = $(this).find(".name").text();
return result.find(function (item) { return item.item.name == name; });
filter: function (item) {
var name = $(item).find(".name").text();
return result.find(function (i) { return i.item.name == name; });
},
transitionDuration: "0.4s",
getSortData: {

查看文件

@ -1,14 +1,17 @@
import React, { Component, PropTypes } from "react";
import { Link, withRouter } from "react-router";
import CSSModules from "react-css-modules";
import { defineMessages, injectIntl, intlShape, FormattedMessage, FormattedHTMLMessage } from "react-intl";
import { messagesMap } from "../../utils";
import commonMessages from "../../locales/messagesDescriptors/common";
import messages from "../../locales/messagesDescriptors/elements/Pagination";
import css from "../../styles/elements/Pagination.scss";
const paginationMessages = defineMessages(messagesMap(Array.concat([], commonMessages, messages)));
export class PaginationIntl extends Component {
class PaginationCSSIntl extends Component {
constructor(props) {
super(props);
this.buildLinkTo.bind(this);
@ -134,8 +137,8 @@ export class PaginationIntl extends Component {
if (pagesButton.length > 1) {
return (
<div>
<nav className="pagination-nav" aria-label={formatMessage(paginationMessages["app.pagination.pageNavigation"])}>
<ul className="pagination">
<nav className="pagination-nav" styleName="nav" aria-label={formatMessage(paginationMessages["app.pagination.pageNavigation"])}>
<ul className="pagination" styleName="pointer">
{ pagesButton }
</ul>
</nav>
@ -171,12 +174,11 @@ export class PaginationIntl extends Component {
}
}
PaginationIntl.propTypes = {
PaginationCSSIntl.propTypes = {
currentPage: PropTypes.number.isRequired,
location: PropTypes.object.isRequired,
nPages: PropTypes.number.isRequired,
intl: intlShape.isRequired,
};
export let Pagination = withRouter(injectIntl(PaginationIntl));
export default Pagination;
export default withRouter(injectIntl(CSSModules(PaginationCSSIntl, css)));

查看文件

@ -1,39 +1,42 @@
import React, { Component, PropTypes } from "react";
import { IndexLink, Link} from "react-router";
import CSSModules from "react-css-modules";
import { defineMessages, injectIntl, intlShape, FormattedMessage } from "react-intl";
import { messagesMap } from "../../utils";
import commonMessages from "../../locales/messagesDescriptors/common";
import messages from "../../locales/messagesDescriptors/layouts/Sidebar";
import css from "../../styles/layouts/Sidebar.scss";
const sidebarLayoutMessages = defineMessages(messagesMap(Array.concat([], commonMessages, messages)));
export default class SidebarLayoutIntl extends Component {
class SidebarLayoutIntl extends Component {
render () {
const { formatMessage } = this.props.intl;
const isActive = {
discover: (this.props.location.pathname == "/discover") ? "active" : "",
browse: (this.props.location.pathname == "/browse") ? "active" : "",
artists: (this.props.location.pathname == "/artists") ? "active" : "",
albums: (this.props.location.pathname == "/albums") ? "active" : "",
songs: (this.props.location.pathname == "/songs") ? "active" : "",
search: (this.props.location.pathname == "/search") ? "active" : ""
discover: (this.props.location.pathname == "/discover") ? "active" : "link",
browse: (this.props.location.pathname == "/browse") ? "active" : "link",
artists: (this.props.location.pathname == "/artists") ? "active" : "link",
albums: (this.props.location.pathname == "/albums") ? "active" : "link",
songs: (this.props.location.pathname == "/songs") ? "active" : "link",
search: (this.props.location.pathname == "/search") ? "active" : "link"
};
return (
<div>
<div className="col-sm-1 col-md-2 sidebar hidden-xs">
<h1 className="text-center">
<IndexLink to="/">
<img alt="A" src="./app/assets/img/ampache-blue.png"/>
<div className="col-sm-1 col-md-2 hidden-xs" styleName="sidebar">
<h1 className="text-center" styleName="title">
<IndexLink to="/" styleName="link">
<img alt="A" src="./app/assets/img/ampache-blue.png" styleName="imgTitle" />
<span className="hidden-sm">mpache</span>
</IndexLink>
</h1>
<nav aria-label={formatMessage(sidebarLayoutMessages["app.sidebarLayout.mainNavigationMenu"])}>
<div className="navbar text-center icon-navbar">
<div className="navbar text-center" styleName="icon-navbar">
<div className="container-fluid">
<ul className="nav navbar-nav icon-navbar-nav">
<ul className="nav navbar-nav" styleName="nav">
<li>
<Link to="/" title={formatMessage(sidebarLayoutMessages["app.sidebarLayout.home"])}>
<Link to="/" title={formatMessage(sidebarLayoutMessages["app.sidebarLayout.home"])} styleName="link">
<span className="glyphicon glyphicon-home" aria-hidden="true"></span>
<span className="sr-only">
<FormattedMessage {...sidebarLayoutMessages["app.sidebarLayout.home"]} />
@ -41,7 +44,7 @@ export default class SidebarLayoutIntl extends Component {
</Link>
</li>
<li>
<Link to="/settings" title={formatMessage(sidebarLayoutMessages["app.sidebarLayout.settings"])}>
<Link to="/settings" title={formatMessage(sidebarLayoutMessages["app.sidebarLayout.settings"])} styleName="link">
<span className="glyphicon glyphicon-wrench" aria-hidden="true"></span>
<span className="sr-only">
<FormattedMessage {...sidebarLayoutMessages["app.sidebarLayout.settings"]} />
@ -49,7 +52,7 @@ export default class SidebarLayoutIntl extends Component {
</Link>
</li>
<li>
<Link to="/logout" title={formatMessage(sidebarLayoutMessages["app.sidebarLayout.logout"])}>
<Link to="/logout" title={formatMessage(sidebarLayoutMessages["app.sidebarLayout.logout"])} styleName="link">
<span className="glyphicon glyphicon-off" aria-hidden="true"></span>
<span className="sr-only">
<FormattedMessage {...sidebarLayoutMessages["app.sidebarLayout.logout"]} />
@ -59,9 +62,9 @@ export default class SidebarLayoutIntl extends Component {
</ul>
</div>
</div>
<ul className="nav nav-sidebar">
<ul className="nav" styleName="nav">
<li>
<Link to="/discover" title={formatMessage(sidebarLayoutMessages["app.sidebarLayout.discover"])} className={isActive.discover}>
<Link to="/discover" title={formatMessage(sidebarLayoutMessages["app.sidebarLayout.discover"])} styleName={isActive.discover}>
<span className="glyphicon glyphicon-globe" aria-hidden="true"></span>
<span className="hidden-sm">
&nbsp;<FormattedMessage {...sidebarLayoutMessages["app.sidebarLayout.discover"]} />
@ -69,7 +72,7 @@ export default class SidebarLayoutIntl extends Component {
</Link>
</li>
<li>
<Link to="/browse" title={formatMessage(sidebarLayoutMessages["app.sidebarLayout.browse"])} className={isActive.browse}>
<Link to="/browse" title={formatMessage(sidebarLayoutMessages["app.sidebarLayout.browse"])} styleName={isActive.browse}>
<span className="glyphicon glyphicon-headphones" aria-hidden="true"></span>
<span className="hidden-sm">
&nbsp;<FormattedMessage {...sidebarLayoutMessages["app.sidebarLayout.browse"]} />
@ -77,7 +80,7 @@ export default class SidebarLayoutIntl extends Component {
</Link>
<ul className="nav nav-list text-center">
<li>
<Link to="/artists" title={formatMessage(sidebarLayoutMessages["app.sidebarLayout.browseArtists"])} className={isActive.artists}>
<Link to="/artists" title={formatMessage(sidebarLayoutMessages["app.sidebarLayout.browseArtists"])} styleName={isActive.artists}>
<span className="glyphicon glyphicon-user" aria-hidden="true"></span>
<span className="sr-only">
<FormattedMessage {...sidebarLayoutMessages["app.common.artist"]} values={{itemCount: 42}} />
@ -88,7 +91,7 @@ export default class SidebarLayoutIntl extends Component {
</Link>
</li>
<li>
<Link to="/albums" title={formatMessage(sidebarLayoutMessages["app.sidebarLayout.browseAlbums"])} className={isActive.albums}>
<Link to="/albums" title={formatMessage(sidebarLayoutMessages["app.sidebarLayout.browseAlbums"])} styleName={isActive.albums}>
<span className="glyphicon glyphicon-cd" aria-hidden="true"></span>
<span className="sr-only"><FormattedMessage {...sidebarLayoutMessages["app.common.album"]} values={{itemCount: 42}} /></span>
<span className="hidden-sm">
@ -97,7 +100,7 @@ export default class SidebarLayoutIntl extends Component {
</Link>
</li>
<li>
<Link to="/songs" title={formatMessage(sidebarLayoutMessages["app.sidebarLayout.browseSongs"])} className={isActive.songs}>
<Link to="/songs" title={formatMessage(sidebarLayoutMessages["app.sidebarLayout.browseSongs"])} styleName={isActive.songs}>
<span className="glyphicon glyphicon-music" aria-hidden="true"></span>
<span className="sr-only">
<FormattedMessage {...sidebarLayoutMessages["app.common.song"]} values={{itemCount: 42}} />
@ -110,7 +113,7 @@ export default class SidebarLayoutIntl extends Component {
</ul>
</li>
<li>
<Link to="/search" title={formatMessage(sidebarLayoutMessages["app.sidebarLayout.search"])} className={isActive.search}>
<Link to="/search" title={formatMessage(sidebarLayoutMessages["app.sidebarLayout.search"])} styleName={isActive.search}>
<span className="glyphicon glyphicon-search" aria-hidden="true"></span>
<span className="hidden-sm">
&nbsp;<FormattedMessage {...sidebarLayoutMessages["app.sidebarLayout.search"]} />
@ -121,7 +124,7 @@ export default class SidebarLayoutIntl extends Component {
</nav>
</div>
<div className="col-sm-11 col-sm-offset-1 col-md-10 col-md-offset-2 main-panel">
<div className="col-sm-11 col-sm-offset-1 col-md-10 col-md-offset-2" styleName="main-panel">
{this.props.children}
</div>
</div>
@ -135,5 +138,4 @@ SidebarLayoutIntl.propTypes = {
intl: intlShape.isRequired
};
export let SidebarLayout = injectIntl(SidebarLayoutIntl);
export default SidebarLayout;
export default injectIntl(CSSModules(SidebarLayoutIntl, css));

6
app/dist/1.1.js vendored

File diff suppressed because one or more lines are too long

2
app/dist/1.1.js.map vendored

File diff suppressed because one or more lines are too long

38
app/dist/3.3.js vendored

File diff suppressed because one or more lines are too long

2
app/dist/fix.ie9.js vendored
查看文件

@ -1,2 +1,2 @@
!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)}});
!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)}});
//# sourceMappingURL=fix.ie9.js.map

File diff suppressed because one or more lines are too long

47
app/dist/index.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

查看文件

@ -1 +0,0 @@
[]

10
app/dist/style.css vendored

File diff suppressed because one or more lines are too long

27
app/styles/Album.scss Normal file
查看文件

@ -0,0 +1,27 @@
.row {
margin-top: 30px;
}
.art {
composes: art from "./elements/Grid.scss";
}
@media (max-width: 767px) {
.nameRow h2 {
margin-top: 0;
margin-bottom: 0;
}
.artRow p,
.artRow img {
margin: 0;
}
.nameRow,
.artRow {
float: none;
display: inline-block;
vertical-align: middle;
margin-bottom: 10px;
}
}

13
app/styles/Artist.scss Normal file
查看文件

@ -0,0 +1,13 @@
.name > h1 {
margin-bottom: 0;
}
.name > hr {
margin-top: 0.5em; /* Default value. */
}
.art {
composes: art from "./elements/Grid.scss";
}
/* TODO: Use table-condensed if xs screen */

9
app/styles/Login.scss Normal file
查看文件

@ -0,0 +1,9 @@
.titleImage {
height: 46px;
}
@media (max-width: 767px) {
.submit {
text-align: center;
}
}

查看文件

@ -1,248 +0,0 @@
/* Firefox hack for responsive table */
@-moz-document url-prefix() {
fieldset {
display: table-cell;
}
}
/*
* Sidebar
*/
.sidebar {
position: fixed;
top: 0;
bottom: 0;
left: 0;
z-index: 1000;
display: block;
padding: 20px;
overflow-x: hidden;
overflow-y: auto; /* Scrollable contents if viewport is shorter than content. */
background-color: #333;
color: white;
border-right: 1px solid #eee;
}
/* Sidebar elements */
.sidebar a {
color: white;
}
.sidebar h1 {
margin: 0;
margin-bottom: 20px;
}
.sidebar h1 img {
height: 46px;
}
.sidebar h1 a {
text-decoration: none;
}
/* Sidebar navigation */
.sidebar .navbar {
border: none;
}
.nav-sidebar {
margin-right: -20px;
margin-bottom: 20px;
margin-left: -20px;
}
.nav-sidebar > li > a {
padding-right: 20px;
padding-left: 20px;
}
@media (max-width: 991px) {
.sidebar,
.sidebar .navbar .container-fluid {
padding-left: 5px;
padding-right: 5px;
}
.sidebar .nav-list {
text-align: right;
}
.sidebar .nav-sidebar > li > a {
padding-left: 20px;
padding-right: 20px;
}
.sidebar .nav-list > li > a {
padding-left: 20px;
padding-right: 20px;
}
}
.nav-sidebar .nav-sidebar {
margin-bottom: 0; /* No margin bottom for nested nav-sidebar. */
}
.nav-sidebar > .active > a:focus,
.nav > li > a:focus {
color: #fff;
background-color: transparent;
}
.nav-sidebar > .active > a,
.nav-sidebar > .active > a:hover,
.nav > li > a:hover {
color: #fff;
background-color: #222;
}
.nav > li > a.active {
background-color: #222;
}
.icon-navbar {
background-color: #555;
font-size: 1.25em;
}
.icon-navbar-nav {
display: inline-block;
float: none;
vertical-align: top;
text-align: center;
}
/*
* Main content
*/
.main-panel {
padding: 20px;
}
@media (min-width: 768px) {
.main-panel {
padding-right: 40px;
padding-left: 40px;
}
}
/*
* Filtering field
*/
div.filter {
margin-bottom: 34px;
}
.filter-legend {
text-align: right;
line-height: 34px;
}
@media (max-width: 767px) {
.filter-legend {
text-align: center;
}
}
@media (min-width: 767px) {
.filter .form-group {
width: 75%;
}
}
/*
* Placeholder dashboard ideas
*/
.placeholders {
margin-bottom: 30px;
text-align: center;
}
.placeholders h4 {
margin-bottom: 0;
}
.placeholder img:hover {
transform: scale(1.1);
cursor: pointer;
}
/**
* Pager
*/
.pagination-nav {
text-align: center;
}
.pagination > li > span {
cursor: pointer;
}
/**
* Login screen
*/
.login h1 img {
height: 46px;
}
@media (max-width: 767px) {
.login .submit {
text-align: center;
}
}
/**
* Misc
*/
.art {
display: inline-block;
margin-bottom: 10px;
width: 75%;
height: auto;
/* doiuse-disable viewport-units */
max-width: 25vw;
max-height: 25vw;
/* doiuse-enable viewport-units */
}
.albumRow {
margin-top: 30px;
}
.artistNameRow h1 {
margin-bottom: 0;
}
.artistNameRow hr {
margin-top: 0.5em; /* Default value. */
}
@media (max-width: 767px) {
.table-responsive {
border: none;
}
.albumRowName h2 {
margin-top: 0;
margin-bottom: 0;
}
.albumRowArt p,
.albumRowArt img {
margin: 0;
}
.albumRowName,
.albumRowArt {
float: none;
display: inline-block;
vertical-align: middle;
margin-bottom: 10px;
}
}
/* TODO: Use table-condensed if xs screen */

查看文件

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

查看文件

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

查看文件

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

查看文件

@ -0,0 +1,20 @@
.filter {
margin-bottom: 34px;
}
.legend {
text-align: right;
line-height: 34px;
}
@media (max-width: 767px) {
.legend {
text-align: center;
}
}
@media (min-width: 767px) {
.form-group {
width: 75%;
}
}

查看文件

@ -0,0 +1,26 @@
.placeholders {
margin-bottom: 30px;
text-align: center;
}
.name {
margin-bottom: 0;
}
.art {
display: inline-block;
margin-bottom: 10px;
width: 75%;
height: auto;
/* doiuse-disable viewport-units */
max-width: 25vw;
/* doiuse-enable viewport-units */
}
.art:hover {
transform: scale(1.1);
cursor: pointer;
}

查看文件

@ -0,0 +1,7 @@
.nav {
text-align: center;
}
.pointer {
cursor: pointer;
}

查看文件

@ -0,0 +1,100 @@
$background: #333;
$hoverBackground: #222;
$activeBackground: $hoverBackground;
$foreground: white;
$lightgrey: #eee;
.sidebar {
position: fixed;
top: 0;
bottom: 0;
left: 0;
z-index: 1000;
display: block;
padding: 20px;
overflow-x: hidden;
overflow-y: auto; /* Scrollable contents if viewport is shorter than content. */
background-color: $background;
color: white;
border-right: 1px solid $lightgrey;
}
/* Sidebar elements */
.link {
color: $foreground;
text-decoration: none;
}
.link:hover,
.link:focus {
color: $foreground;
background-color: $hoverBackground !important; /* TODO: important */
}
.active {
composes: link;
background-color: $activeBackground;
}
.title {
margin: 0;
margin-bottom: 20px;
}
.imgTitle {
height: 46px;
}
/* Sidebar navigation */
.icon-navbar {
background-color: #555;
font-size: 1.25em;
border: none;
.nav {
display: inline-block;
float: none;
vertical-align: top;
text-align: center;
}
}
/*
* Main content
*/
.main-panel {
padding: 20px;
}
/*
* Media queries
* TODO: Sidebar responsiveness
*/
/*@media (max-width: 991px) {
.sidebar,
.sidebar .navbar .container-fluid {
padding-left: 5px;
padding-right: 5px;
}
.sidebar .nav-list {
text-align: right;
}
.sidebar .nav-sidebar > li > a {
padding-left: 20px;
padding-right: 20px;
}
.sidebar .nav-list > li > a {
padding-left: 20px;
padding-right: 20px;
}
}
@media (min-width: 768px) {
.main-panel {
padding-right: 40px;
padding-left: 40px;
}
}*/

查看文件

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

查看文件

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

查看文件

@ -11,12 +11,13 @@
"watch": "./node_modules/.bin/webpack --progress --watch",
"prod": "NODE_ENV=production ./node_modules/.bin/webpack --progress",
"extractTranslations": "./node_modules/.bin/babel-node scripts/extractTranslations.js",
"clean": "./node_modules/.bin/rimraf app/dist"
"clean": "./node_modules/.bin/rimraf .cache && ./node_modules/.bin/rimraf app/dist"
},
"dependencies": {
"babel-polyfill": "^6.9.1",
"babel-preset-es2015": "^6.9.0",
"bootstrap": "^3.3.6",
"bootstrap-loader": "^1.0.10",
"bootstrap-sass": "^3.3.7",
"fuse.js": "^2.3.0",
"html5shiv": "^3.7.3",
"humps": "^1.1.0",
@ -29,6 +30,7 @@
"jssha": "^2.1.0",
"lodash": "^4.13.1",
"react": "^15.2.0",
"react-css-modules": "^3.7.9",
"react-dom": "^15.2.0",
"react-intl": "^2.1.3",
"react-redux": "^4.4.5",
@ -44,7 +46,6 @@
"babel-core": "^6.10.4",
"babel-loader": "^6.2.4",
"babel-plugin-react-intl": "^2.1.3",
"babel-plugin-transform-runtime": "^6.12.0",
"babel-preset-react": "^6.11.1",
"css-loader": "^0.23.1",
"doiuse": "^2.4.1",
@ -55,6 +56,7 @@
"extract-text-webpack-plugin": "^1.0.1",
"file-loader": "^0.9.0",
"glob": "^7.0.5",
"node-sass": "^3.8.0",
"postcss": "^5.1.0",
"postcss-loader": "^0.9.1",
"postcss-reporter": "^1.4.1",
@ -63,7 +65,9 @@
"react-intl-webpack-plugin": "0.0.3",
"redbox-react": "^1.2.10",
"redux-logger": "^2.6.1",
"resolve-url-loader": "^1.6.0",
"rimraf": "^2.5.4",
"sass-loader": "^4.0.0",
"style-loader": "^0.13.1",
"stylelint": "^7.0.3",
"stylelint-config-standard": "^11.0.0",

查看文件

@ -11,7 +11,7 @@ var browsers = ["ie >= 9", "> 1%", "last 3 versions", "not op_mini all"];
module.exports = {
entry: {
"index": ["babel-polyfill", "./index.js"],
"index": ["babel-polyfill", "bootstrap-loader", "./app/styles/common/index.js", "./index.js"],
"fix.ie9": "./fix.ie9.js"
},
@ -40,24 +40,22 @@ module.exports = {
},
include: __dirname
},
// Do not postcss vendor modules
{
test: /\.css$/,
exclude: /node_modules/,
loader: ExtractTextPlugin.extract("style-loader", "css-loader!postcss-loader")
},
{
test: /\.css$/,
exclude: /app/,
loader: ExtractTextPlugin.extract("style-loader", "css-loader")
},
{
test: /\.less$/,
loader: ExtractTextPlugin.extract("style-loader", "css-loader!less-loader")
loader: ExtractTextPlugin.extract(
"style-loader",
"css-loader?modules&importLoaders=1&localIdentName=[name]__[local]__[hash:base64:5]" +
"!postcss-loader"
)
},
{
test: /\.scss$/,
loader: ExtractTextPlugin.extract("style-loader", "css-loader!sass-loader")
loader: ExtractTextPlugin.extract(
"style-loader",
"css-loader?modules&importLoaders=1&localIdentName=[name]__[local]__[hash:base64:5]" +
// TODO: "!postcss-loader" +
"!sass-loader"
)
},
{
test: /\.eot(\?v=\d+\.\d+\.\d+)?$/,
@ -86,9 +84,7 @@ module.exports = {
new ExtractTextPlugin("style.css", { allChunks: true })
],
postcss: function () {
return [doiuse({ browsers: browsers }), stylelint, precss, autoprefixer({ browsers: browsers }), postcssReporter({ throwError: true, clearMessages: true })];
},
postcss: [doiuse({ browsers: browsers }), stylelint, precss, autoprefixer({ browsers: browsers }), postcssReporter({ throwError: true, clearMessages: true })],
resolve: {
// Include empty string "" to resolve files by their explicit extension

查看文件

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