This commit is contained in:
Matt Claypotch 2015-01-20 14:46:18 -08:00
Родитель 805d83fd1e
Коммит ed7ea48b29
4 изменённых файлов: 324 добавлений и 38 удалений

Просмотреть файл

@ -21,10 +21,13 @@ body {
height: 100%;
}
a:link, a:visited, a:active {
a:link, a:visited, a:active, a:hover {
color: #fff;
text-decoration: none;
}
a.active {
background: #444;
}
/* App */
#app {
@ -64,6 +67,7 @@ a:link, a:visited, a:active {
.queue {
height: 100%;
overflow: auto;
-webkit-overflow-scrolling: touch;
font-size: 2rem;
}
.queue-item {
@ -206,14 +210,20 @@ header > h1 {
display: flex;
flex-direction: row;
align-items: center;
background: #fff;
border-radius: 2em;
padding-right: .5em;
font-size: 1.5rem;
}
.searchForm .throb {
color: #000;
}
.query {
flex: 1;
font: inherit;
font-size: 1.5rem;
background: transparent;
border: 0 none;
padding: .5rem 2rem;
border-radius: 2em;
border: 0;
}
.results-container {
overflow: hidden;
@ -222,15 +232,24 @@ header > h1 {
}
.results {
overflow: auto;
overflow-x: hidden;
-webkit-overflow-scrolling: touch;
height: 100%;
font-size: 2rem;
}
.results li {
padding: .5rem 0;
display: flex;
flex-direction: row;
overflow: hidden;
}
.results h2 {
font-style: italic;
}
.results a {
display: block;
flex: 1;
}
h1 {
font-size: 2.5rem;
}
@ -238,6 +257,28 @@ h2 {
font-size: 2.25rem;
color: #ddd;
}
.results .throb {
color: #fff;
}
/* loading */
.throb {
width: 1.5em;
height: 1.5em;
background-color: red;
flex-shrink: 0;
background: transparent;
border: .25em solid currentColor;
border-radius: 2em;
border-color: currentColor transparent currentColor transparent;
animation: 500ms spin linear infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
@media (max-device-width: 480px) {
html {

Просмотреть файл

@ -5,6 +5,16 @@ function extend(o, n) {
return o;
}
function classSet(o) {
var c = [];
for (var k in o) {
if (!!o[k]) {
c.push(k);
}
}
return c.join(' ');
}
var App = React.createClass({
setMode: function (mode) {
this.setState(extend(this.state, {
@ -196,7 +206,8 @@ var SearchPanel = React.createClass({
return {
artist: [],
song: [],
query: ''
query: '',
pending: false
};
},
searchArtist: function (artistId) {
@ -211,17 +222,25 @@ var SearchPanel = React.createClass({
venue: this.props.zone.id
});
},
onChange: function (e) {
var q = e.target.value;
updateQuery: function (q) {
if (q.length > 0) {
this.setState(extend(this.state, {
query: q
}));
if (q.length > 2) {
this.performSearch();
}
},
onChange: function (e) {
console.log(e);
var q = e.target.value;
clearTimeout(this.to);
this.to = setTimeout(this.updateQuery.bind(this, q), 200);
},
performSearch: function () {
console.log('search', this.state.query);
this.setState(extend(this.state, {
pending: true
}));
socket.emit('search', {
query: this.state.query,
venue: this.props.zone.id
@ -230,25 +249,37 @@ var SearchPanel = React.createClass({
componentDidMount: function() {
var self = this;
socket.on('results', function (data) {
console.log(data.query, self.state.query);
if (data.query === self.state.query) {
console.log(data);
self.setState(extend(self.state, data));
console.log('results', data);
self.setState(extend(self.state, {
artist: data.artist,
song: data.song,
pending: false
}));
}
});
},
render: function () {
if (!this.props.zone) {
return <div></div>;
}
var pending = '';
if (this.state.pending) {
pending = 'pending';
}
return (
<section className="search">
<header>
<a href="#" onClick={this.go.bind(this, 'detail')}>Back</a>
<h1>Search</h1>
</header>
<div className="searchForm">
<input className="query" onChange={this.onChange} value={this.state.query} />
<div className={"searchForm " + pending}>
<input className="query" onChange={this.onChange} />
{this.state.pending ? <Throbber /> : ''}
</div>
<div className="results-container">
<SearchResults results={this.state}
queue={this.props.zone.aData.aQueue}
searchArtist={this.searchArtist}
pickSong={this.pickSong} />
</div>
@ -258,15 +289,22 @@ var SearchPanel = React.createClass({
});
var SearchResults = React.createClass({
pickSong: function (songId, e) {
pickSong: function (songId) {
this.props.pickSong(songId);
},
listArtist: function (artistId, e) {
e.preventDefault();
this.props.searchArtist(artistId);
},
render: function () {
var artists = this.props.results.artist;
var songs = this.props.results.song;
if (!artists) {
artists = [];
}
if (!songs) {
songs = [];
}
return (
<div className="results">
<h2>Artists ({artists.length})</h2>
@ -274,7 +312,14 @@ var SearchResults = React.createClass({
{artists.filter(function (r) {
return r.bEnabled !== false;
}).map(function (r) {
return <li onClick={this.listArtist.bind(this, r.idArtist)}>{r.sName}</li>;
return (
<li key={r.idArtist}>
<a href="#"
onClick={this.listArtist.bind(this, r.idArtist)}>
{r.sName}
</a>
</li>
);
}, this)}
</ul>
<h2>Songs ({songs.length})</h2>
@ -282,7 +327,15 @@ var SearchResults = React.createClass({
{songs.filter(function (r) {
return r.bEnabled !== false;
}).map(function (r) {
return <li onClick={this.pickSong.bind(this, r.idSong)}>{r.sArtist} - {r.sName}</li>;
var picked = false;
if (this.props.queue) {
this.props.queue.forEach(function (q) {
if (q.sSong === r.sName && q.sArtist === r.sArtist) {
picked = true;
}
});
}
return <SongResult key={r.idSong} picked={picked} song={r} pick={this.pickSong}/>;
}, this)}
</ul>
</div>
@ -290,6 +343,44 @@ var SearchResults = React.createClass({
}
});
var SongResult = React.createClass({
getInitialState: function () {
return {
pending: false
};
},
pick: function (e) {
e.preventDefault();
this.props.pick(this.props.song.idSong);
this.setState({
pending: true
});
},
render: function () {
var song = this.props.song;
var pending = this.state.pending && !this.props.picked;
var classes = classSet({
'picked': this.props.picked,
'pending': pending
});
return (
<li key={song.idSong} className={classes}>
<a href="#"
onClick={this.pick}>
{song.sArtist} - {song.sName}
</a>
{pending ? <Throbber /> : ''}
{this.props.picked ? <div></div> : ''}
</li>
);
}
});
var Throbber = React.createClass({
render: function () {
return <div className="throb"></div>;
}
});
var socket = io.connect('/');
var o = React.createElement(Overview);

Просмотреть файл

@ -21,10 +21,13 @@ body {
height: 100%;
}
a:link, a:visited, a:active {
a:link, a:visited, a:active, a:hover {
color: #fff;
text-decoration: none;
}
a.active {
background: #444;
}
/* App */
#app {
@ -84,6 +87,7 @@ a:link, a:visited, a:active {
.queue {
height: 100%;
overflow: auto;
-webkit-overflow-scrolling: touch;
font-size: 2rem;
}
.queue-item {
@ -320,6 +324,13 @@ header > h1 {
-webkit-align-items: center;
-ms-flex-align: center;
align-items: center;
background: #fff;
border-radius: 2em;
padding-right: .5em;
font-size: 1.5rem;
}
.searchForm .throb {
color: #000;
}
.query {
-webkit-box-flex: 1;
@ -327,10 +338,9 @@ header > h1 {
-ms-flex: 1;
flex: 1;
font: inherit;
font-size: 1.5rem;
background: transparent;
border: 0 none;
padding: .5rem 2rem;
border-radius: 2em;
border: 0;
}
.results-container {
overflow: hidden;
@ -342,15 +352,34 @@ header > h1 {
}
.results {
overflow: auto;
overflow-x: hidden;
-webkit-overflow-scrolling: touch;
height: 100%;
font-size: 2rem;
}
.results li {
padding: .5rem 0;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-box-orient: horizontal;
-webkit-box-direction: normal;
-webkit-flex-direction: row;
-ms-flex-direction: row;
flex-direction: row;
overflow: hidden;
}
.results h2 {
font-style: italic;
}
.results a {
display: block;
-webkit-box-flex: 1;
-webkit-flex: 1;
-ms-flex: 1;
flex: 1;
}
h1 {
font-size: 2.5rem;
}
@ -358,6 +387,40 @@ h2 {
font-size: 2.25rem;
color: #ddd;
}
.results .throb {
color: #fff;
}
/* loading */
.throb {
width: 1.5em;
height: 1.5em;
background-color: red;
-webkit-flex-shrink: 0;
-ms-flex-negative: 0;
flex-shrink: 0;
background: transparent;
border: .25em solid currentColor;
border-radius: 2em;
border-color: currentColor transparent currentColor transparent;
-webkit-animation: 500ms spin linear infinite;
animation: 500ms spin linear infinite;
}
@-webkit-keyframes spin {
to {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}
@keyframes spin {
to {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}
@media (max-device-width: 480px) {
html {

Просмотреть файл

@ -5,6 +5,16 @@ function extend(o, n) {
return o;
}
function classSet(o) {
var c = [];
for (var k in o) {
if (!!o[k]) {
c.push(k);
}
}
return c.join(' ');
}
var App = React.createClass({displayName: "App",
setMode: function (mode) {
this.setState(extend(this.state, {
@ -196,7 +206,8 @@ var SearchPanel = React.createClass({displayName: "SearchPanel",
return {
artist: [],
song: [],
query: ''
query: '',
pending: false
};
},
searchArtist: function (artistId) {
@ -211,17 +222,25 @@ var SearchPanel = React.createClass({displayName: "SearchPanel",
venue: this.props.zone.id
});
},
onChange: function (e) {
var q = e.target.value;
updateQuery: function (q) {
if (q.length > 0) {
this.setState(extend(this.state, {
query: q
}));
if (q.length > 2) {
this.performSearch();
}
},
onChange: function (e) {
console.log(e);
var q = e.target.value;
clearTimeout(this.to);
this.to = setTimeout(this.updateQuery.bind(this, q), 200);
},
performSearch: function () {
console.log('search', this.state.query);
this.setState(extend(this.state, {
pending: true
}));
socket.emit('search', {
query: this.state.query,
venue: this.props.zone.id
@ -230,25 +249,37 @@ var SearchPanel = React.createClass({displayName: "SearchPanel",
componentDidMount: function() {
var self = this;
socket.on('results', function (data) {
console.log(data.query, self.state.query);
if (data.query === self.state.query) {
console.log(data);
self.setState(extend(self.state, data));
console.log('results', data);
self.setState(extend(self.state, {
artist: data.artist,
song: data.song,
pending: false
}));
}
});
},
render: function () {
if (!this.props.zone) {
return React.createElement("div", null);
}
var pending = '';
if (this.state.pending) {
pending = 'pending';
}
return (
React.createElement("section", {className: "search"},
React.createElement("header", null,
React.createElement("a", {href: "#", onClick: this.go.bind(this, 'detail')}, "Back"),
React.createElement("h1", null, "Search")
),
React.createElement("div", {className: "searchForm"},
React.createElement("input", {className: "query", onChange: this.onChange, value: this.state.query})
React.createElement("div", {className: "searchForm " + pending},
React.createElement("input", {className: "query", onChange: this.onChange}),
this.state.pending ? React.createElement(Throbber, null) : ''
),
React.createElement("div", {className: "results-container"},
React.createElement(SearchResults, {results: this.state,
queue: this.props.zone.aData.aQueue,
searchArtist: this.searchArtist,
pickSong: this.pickSong})
)
@ -258,15 +289,22 @@ var SearchPanel = React.createClass({displayName: "SearchPanel",
});
var SearchResults = React.createClass({displayName: "SearchResults",
pickSong: function (songId, e) {
pickSong: function (songId) {
this.props.pickSong(songId);
},
listArtist: function (artistId, e) {
e.preventDefault();
this.props.searchArtist(artistId);
},
render: function () {
var artists = this.props.results.artist;
var songs = this.props.results.song;
if (!artists) {
artists = [];
}
if (!songs) {
songs = [];
}
return (
React.createElement("div", {className: "results"},
React.createElement("h2", null, "Artists (", artists.length, ")"),
@ -274,7 +312,14 @@ var SearchResults = React.createClass({displayName: "SearchResults",
artists.filter(function (r) {
return r.bEnabled !== false;
}).map(function (r) {
return React.createElement("li", {onClick: this.listArtist.bind(this, r.idArtist)}, r.sName);
return (
React.createElement("li", {key: r.idArtist},
React.createElement("a", {href: "#",
onClick: this.listArtist.bind(this, r.idArtist)},
r.sName
)
)
);
}, this)
),
React.createElement("h2", null, "Songs (", songs.length, ")"),
@ -282,7 +327,15 @@ var SearchResults = React.createClass({displayName: "SearchResults",
songs.filter(function (r) {
return r.bEnabled !== false;
}).map(function (r) {
return React.createElement("li", {onClick: this.pickSong.bind(this, r.idSong)}, r.sArtist, " - ", r.sName);
var picked = false;
if (this.props.queue) {
this.props.queue.forEach(function (q) {
if (q.sSong === r.sName && q.sArtist === r.sArtist) {
picked = true;
}
});
}
return React.createElement(SongResult, {key: r.idSong, picked: picked, song: r, pick: this.pickSong});
}, this)
)
)
@ -290,6 +343,44 @@ var SearchResults = React.createClass({displayName: "SearchResults",
}
});
var SongResult = React.createClass({displayName: "SongResult",
getInitialState: function () {
return {
pending: false
};
},
pick: function (e) {
e.preventDefault();
this.props.pick(this.props.song.idSong);
this.setState({
pending: true
});
},
render: function () {
var song = this.props.song;
var pending = this.state.pending && !this.props.picked;
var classes = classSet({
'picked': this.props.picked,
'pending': pending
});
return (
React.createElement("li", {key: song.idSong, className: classes},
React.createElement("a", {href: "#",
onClick: this.pick},
song.sArtist, " - ", song.sName
),
pending ? React.createElement(Throbber, null) : '',
this.props.picked ? React.createElement("div", null, "✓") : ''
)
);
}
});
var Throbber = React.createClass({displayName: "Throbber",
render: function () {
return React.createElement("div", {className: "throb"});
}
});
var socket = io.connect('/');
var o = React.createElement(Overview);