Merge branch 'feature/streaming' into develop

This commit is contained in:
James Barnsley
2018-02-01 08:53:02 +13:00
9 changed files with 559 additions and 239 deletions

View File

@ -49455,7 +49455,9 @@ var initialState = {
artists: {},
playlists: {},
users: {},
tracks: {}
tracks: {},
http_streaming_enabled: false,
http_streaming_url: "http://" + window.location.hostname + ":8000/mopidy"
},
ui: {
show_initial_setup: true,
@ -58619,6 +58621,11 @@ var PlaybackControls = function (_React$Component) {
return _react2.default.createElement(
'div',
{ className: this.state.expanded ? "expanded playback-controls" : "playback-controls" },
this.props.http_streaming ? _react2.default.createElement(
'audio',
{ className: 'http-streaming', autoPlay: true },
_react2.default.createElement('source', { src: this.props.http_streaming, type: 'audio/mpeg' })
) : null,
_react2.default.createElement(
'div',
{ className: 'current-track' },
@ -58730,6 +58737,7 @@ var PlaybackControls = function (_React$Component) {
var mapStateToProps = function mapStateToProps(state, ownProps) {
return {
http_streaming: state.core.http_streaming_enabled && state.core.http_streaming_url ? state.core.http_streaming_url : '',
current_track: state.core.current_track && state.core.tracks[state.core.current_track.uri] !== undefined ? state.core.tracks[state.core.current_track.uri] : null,
radio_enabled: state.ui.radio && state.ui.radio.enabled ? true : false,
play_state: state.mopidy.play_state,
@ -60367,7 +60375,7 @@ var _SearchURISchemesModal = __webpack_require__(410);
var _SearchURISchemesModal2 = _interopRequireDefault(_SearchURISchemesModal);
var _InitialSetupModal = __webpack_require__(481);
var _InitialSetupModal = __webpack_require__(414);
var _InitialSetupModal2 = _interopRequireDefault(_InitialSetupModal);
@ -63827,7 +63835,139 @@ exports.default = Sortable;
/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(22)))
/***/ }),
/* 414 */,
/* 414 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
var _react = __webpack_require__(1);
var _react2 = _interopRequireDefault(_react);
var _reactFontawesome = __webpack_require__(6);
var _reactFontawesome2 = _interopRequireDefault(_reactFontawesome);
var _Icon = __webpack_require__(21);
var _Icon2 = _interopRequireDefault(_Icon);
var _helpers = __webpack_require__(2);
var helpers = _interopRequireWildcard(_helpers);
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
var InitialSetupModal = function (_React$Component) {
_inherits(InitialSetupModal, _React$Component);
function InitialSetupModal(props) {
_classCallCheck(this, InitialSetupModal);
var _this = _possibleConstructorReturn(this, (InitialSetupModal.__proto__ || Object.getPrototypeOf(InitialSetupModal)).call(this, props));
_this.state = {
username: 'Anonymous'
};
return _this;
}
_createClass(InitialSetupModal, [{
key: 'handleSubmit',
value: function handleSubmit(e) {
// save and falseify show_initial_setup
}
}, {
key: 'handleCancel',
value: function handleCancel(e) {
// falseify show_initial_setup
}
}, {
key: 'render',
value: function render() {
var _this2 = this;
return _react2.default.createElement(
'div',
null,
_react2.default.createElement(
'h1',
null,
'Welcome to Iris'
),
_react2.default.createElement(
'form',
{ onSubmit: function onSubmit(e) {
return _this2.handleSubmit(e);
} },
_react2.default.createElement(
'div',
{ className: 'field' },
_react2.default.createElement(
'div',
{ className: 'name' },
'Username'
),
_react2.default.createElement(
'div',
{ className: 'input' },
_react2.default.createElement('input', {
type: 'text',
onChange: function onChange(e) {
return _this2.setState({ username: e.target.value.replace(/\W/g, '') });
},
value: this.state.username }),
_react2.default.createElement(
'div',
{ className: 'description' },
'A non-unique string used to identify this client (no special characters)'
)
)
),
_react2.default.createElement(
'div',
{ className: 'actions centered-text' },
_react2.default.createElement(
'button',
{ className: 'large', onClick: function onClick(e) {
return _this2.handleCancel(e);
} },
'Skip'
),
_react2.default.createElement(
'button',
{ className: 'primary large', onClick: function onClick(e) {
return _this2.handleSubmit(e);
} },
'Save'
)
)
)
);
}
}]);
return InitialSetupModal;
}(_react2.default.Component);
exports.default = InitialSetupModal;
/***/ }),
/* 415 */
/***/ (function(module, exports, __webpack_require__) {
@ -67943,6 +68083,69 @@ var Settings = function (_React$Component) {
),
this.renderApplyButton()
),
_react2.default.createElement(
'h4',
{ className: 'underline' },
'Streaming'
),
_react2.default.createElement(
'div',
{ className: 'field checkbox' },
_react2.default.createElement(
'div',
{ className: 'name' },
'Enable'
),
_react2.default.createElement(
'div',
{ className: 'input' },
_react2.default.createElement(
'label',
null,
_react2.default.createElement('input', {
type: 'checkbox',
name: 'ssl',
checked: this.props.core.http_streaming_enabled,
onChange: function onChange(e) {
return _this3.props.coreActions.set({ http_streaming_enabled: !_this3.props.core.http_streaming_enabled });
} }),
_react2.default.createElement(
'span',
{ className: 'label has-tooltip' },
'Enable HTTP streaming',
_react2.default.createElement(
'span',
{ className: 'tooltip' },
'Requires streaming service like Icecast2'
)
)
)
)
),
_react2.default.createElement(
'div',
{ className: 'field' },
_react2.default.createElement(
'div',
{ className: 'name' },
'Stream location'
),
_react2.default.createElement(
'div',
{ className: 'input' },
_react2.default.createElement('input', {
type: 'text',
onChange: function onChange(e) {
return _this3.props.coreActions.set({ http_streaming_url: e.target.value });
},
value: this.props.core.http_streaming_url }),
_react2.default.createElement(
'div',
{ className: 'description' },
'The full URL to your stream endpoint'
)
)
),
_react2.default.createElement(
'h4',
{ className: 'underline' },
@ -75305,158 +75508,6 @@ exports.default = (0, _reactRedux.connect)(mapStateToProps, mapDispatchToProps)(
// removed by extract-text-webpack-plugin
/***/ }),
/* 462 */,
/* 463 */,
/* 464 */,
/* 465 */,
/* 466 */,
/* 467 */,
/* 468 */,
/* 469 */,
/* 470 */,
/* 471 */,
/* 472 */,
/* 473 */,
/* 474 */,
/* 475 */,
/* 476 */,
/* 477 */,
/* 478 */,
/* 479 */,
/* 480 */,
/* 481 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
var _react = __webpack_require__(1);
var _react2 = _interopRequireDefault(_react);
var _reactFontawesome = __webpack_require__(6);
var _reactFontawesome2 = _interopRequireDefault(_reactFontawesome);
var _Icon = __webpack_require__(21);
var _Icon2 = _interopRequireDefault(_Icon);
var _helpers = __webpack_require__(2);
var helpers = _interopRequireWildcard(_helpers);
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
var InitialSetupModal = function (_React$Component) {
_inherits(InitialSetupModal, _React$Component);
function InitialSetupModal(props) {
_classCallCheck(this, InitialSetupModal);
var _this = _possibleConstructorReturn(this, (InitialSetupModal.__proto__ || Object.getPrototypeOf(InitialSetupModal)).call(this, props));
_this.state = {
username: 'Anonymous'
};
return _this;
}
_createClass(InitialSetupModal, [{
key: 'handleSubmit',
value: function handleSubmit(e) {
// save and falseify show_initial_setup
}
}, {
key: 'handleCancel',
value: function handleCancel(e) {
// falseify show_initial_setup
}
}, {
key: 'render',
value: function render() {
var _this2 = this;
return _react2.default.createElement(
'div',
null,
_react2.default.createElement(
'h1',
null,
'Welcome to Iris'
),
_react2.default.createElement(
'form',
{ onSubmit: function onSubmit(e) {
return _this2.handleSubmit(e);
} },
_react2.default.createElement(
'div',
{ className: 'field' },
_react2.default.createElement(
'div',
{ className: 'name' },
'Username'
),
_react2.default.createElement(
'div',
{ className: 'input' },
_react2.default.createElement('input', {
type: 'text',
onChange: function onChange(e) {
return _this2.setState({ username: e.target.value.replace(/\W/g, '') });
},
value: this.state.username }),
_react2.default.createElement(
'div',
{ className: 'description' },
'A non-unique string used to identify this client (no special characters)'
)
)
),
_react2.default.createElement(
'div',
{ className: 'actions centered-text' },
_react2.default.createElement(
'button',
{ className: 'large', onClick: function onClick(e) {
return _this2.handleCancel(e);
} },
'Skip'
),
_react2.default.createElement(
'button',
{ className: 'primary large', onClick: function onClick(e) {
return _this2.handleSubmit(e);
} },
'Save'
)
)
)
);
}
}]);
return InitialSetupModal;
}(_react2.default.Component);
exports.default = InitialSetupModal;
/***/ })
/******/ ]);
//# sourceMappingURL=app.js.map

File diff suppressed because one or more lines are too long

View File

@ -1,4 +1,84 @@
@charset "UTF-8";
.input-range__slider {
appearance: none;
background: #3f51b5;
border: 1px solid #3f51b5;
border-radius: 100%;
cursor: pointer;
display: block;
height: 1rem;
margin-left: -0.5rem;
margin-top: -0.65rem;
outline: none;
position: absolute;
top: 50%;
transition: transform 0.3s ease-out, box-shadow 0.3s ease-out;
width: 1rem; }
.input-range__slider:active {
transform: scale(1.3); }
.input-range__slider:focus {
box-shadow: 0 0 0 5px rgba(63, 81, 181, 0.2); }
.input-range--disabled .input-range__slider {
background: #cccccc;
border: 1px solid #cccccc;
box-shadow: none;
transform: none; }
.input-range__slider-container {
transition: left 0.3s ease-out; }
.input-range__label {
color: #aaaaaa;
font-family: "Helvetica Neue", san-serif;
font-size: 0.8rem;
transform: translateZ(0);
white-space: nowrap; }
.input-range__label--min,
.input-range__label--max {
bottom: -1.4rem;
position: absolute; }
.input-range__label--min {
left: 0; }
.input-range__label--max {
right: 0; }
.input-range__label--value {
position: absolute;
top: -1.8rem; }
.input-range__label-container {
left: -50%;
position: relative; }
.input-range__label--max .input-range__label-container {
left: 50%; }
.input-range__track {
background: #eeeeee;
border-radius: 0.3rem;
cursor: pointer;
display: block;
height: 0.3rem;
position: relative;
transition: left 0.3s ease-out, width 0.3s ease-out; }
.input-range--disabled .input-range__track {
background: #eeeeee; }
.input-range__track--background {
left: 0;
margin-top: -0.15rem;
position: absolute;
right: 0;
top: 50%; }
.input-range__track--active {
background: #3f51b5; }
.input-range {
height: 1rem;
position: relative;
width: 100%; }@charset "UTF-8";
/*!
* Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome
* License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License)
@ -2360,13 +2440,16 @@
@keyframes slideloader {
0% {
left: 0%;
right: 100%; }
right: 100%;
border-radius: 3px 0 0 3px; }
50% {
left: 0%;
right: 0%; }
right: 0%;
border-radius: 3px; }
100% {
left: 100%;
right: 0%; } }
right: 0%;
border-radius: 0 3px 3px 0; } }
/**
* Grid layout
@ -2763,7 +2846,7 @@ footer {
.flag.warning {
background: #f16f19;
color: #FFFFFF; }
.flag.bad {
.flag.error, .flag.bad {
background: #cf2d2d;
color: #FFFFFF; }
.flag.dark {
@ -2777,6 +2860,25 @@ footer {
line-height: 1.4em;
vertical-align: middle; }
.message {
padding: 10px 12px 8px;
background: #FFF39C;
color: #181818;
clear: both;
font-size: 14px;
border-radius: 3px; }
.message.info {
background: #FFF39C; }
.message.notice {
background: #32b5f2;
color: #FFFFFF; }
.message.warning {
background: #f16f19;
color: #FFFFFF; }
.message.error, .message.bad {
background: #cf2d2d;
color: #FFFFFF; }
@media (max-width: 800px) {
h1 {
font-size: 1.8rem;
@ -2935,6 +3037,22 @@ input[type="submit"] {
background: #f2f2f2;
margin-top: 1px;
margin-bottom: 9px; }
.button.small,
button.small,
input[type="button"].small,
input[type="submit"].small {
padding: 6px 18px 4px; }
.button.large,
button.large,
input[type="button"].large,
input[type="submit"].large {
padding: 16px 50px 14px; }
.button.pull-right,
button.pull-right,
input[type="button"].pull-right,
input[type="submit"].pull-right {
margin-right: 0;
margin-left: 30px; }
.button.primary,
button.primary,
input[type="button"].primary,
@ -3011,7 +3129,9 @@ input[type="submit"] {
input[type="button"].working,
input[type="submit"].working {
position: relative;
cursor: wait !important; }
cursor: wait !important;
background: #383734;
color: #a3a19f; }
.button.working:after,
button.working:after,
input[type="button"].working:after,
@ -3021,7 +3141,7 @@ input[type="submit"] {
display: block;
content: '';
animation: slideloader 1s infinite;
background: rgba(200, 200, 200, 0.4);
background: rgba(200, 200, 200, 0.3);
top: 0;
left: 0;
bottom: 0;
@ -3094,7 +3214,7 @@ input[type="submit"] {
display: inline-block;
float: none;
width: auto;
padding: 5px 30px 5px 30px;
padding: 5px 5px 5px 26px;
position: relative;
cursor: pointer; }
.field.radio input + .label:before {
@ -3125,6 +3245,37 @@ input[type="submit"] {
left: 0; }
.field.checkbox input:checked + .label:before {
content: '\F00C'; }
.field.range {
position: relative; }
.field.range .input-range {
height: auto; }
.field.range .label {
padding-bottom: 6px; }
.field.range .input-range__track,
.field.range .input-range__slider-container {
transition: none;
height: 6px;
border-radius: 3px;
cursor: col-resize; }
.field.range .input-range__track--background {
margin: 0;
background: rgba(100, 100, 100, 0.5); }
.field.range .input-range__track--active {
background: #32b5f2; }
.field.range .input-range__slider-container .input-range__slider {
cursor: col-resize;
background: #32b5f2;
border: none;
top: 0;
height: 6px;
width: 20px;
margin: -6px 0 0 0;
border-radius: 3px 0 0 3px; }
.field.range .input-range__slider-container:last-child .input-range__slider {
border-radius: 0 3px 3px 0;
margin-left: -12px; }
.field.sub-field {
padding: 0; }
.field .description {
display: inline-block;
padding: 10px 14px;
@ -3413,6 +3564,9 @@ input[type="submit"] {
background: -moz-linear-gradient(to bottom right, rgba(255, 255, 255, 0.75), black);
background: -o-linear-gradient(to bottom right, rgba(255, 255, 255, 0.75), black);
background: linear-gradient(to bottom right, rgba(255, 255, 255, 0.75), black); }
h4 .context-menu-trigger,
h4 .context-menu-trigger:active {
margin: -8px 0 0 10px; }
.context-menu-trigger .dot {
width: 6px;
height: 6px;
@ -4986,19 +5140,21 @@ main header {
font-family: "Overpass";
position: relative;
display: inline-block; }
.dropdown-field .label {
.dropdown-field .label:not(.button) {
-webkit-transition: all 0.2s ease-in-out;
-moz-transition: all 0.2s ease-in-out;
-o-transition: all 0.2s ease-in-out;
transition: all 0.2s ease-in-out;
padding: 14px 10px;
cursor: pointer; }
.notouch .dropdown-field .label:hover {
.notouch .dropdown-field .label:not(.button):hover {
color: #32b5f2; }
.dropdown-field.expanded .label {
.dropdown-field.expanded .label:not(.button) {
color: #32b5f2; }
.dropdown-field.expanded .options {
display: block; }
.dropdown-field.no-status-icon .options .option {
padding-left: 12px; }
.dropdown-field .options {
color: #000000;
position: absolute;
@ -5226,6 +5382,8 @@ main header {
.has-tooltip:hover .tooltip {
opacity: 1;
bottom: 35px; }
.has-tooltip.info .fa {
opacity: 0.3; }
.has-tooltip.large-tooltip .tooltip {
padding: 8px 12px;
font-size: 12px;
@ -5690,53 +5848,106 @@ main .track-view {
color: #FFFFFF; }
.discover-view .intro .liner h2 {
padding-bottom: 8px; }
.discover-view .intro .liner h3 {
padding-bottom: 40px;
opacity: 0.5; }
.discover-view .seeds {
padding-top: 40px; }
.discover-view .seeds .seed {
display: inline-block;
background: #32b5f2;
color: #FFFFFF;
padding: 8px 26px 8px 14px;
.discover-view .parameters {
padding-top: 50px; }
.discover-view .parameters::after {
content: '';
clear: both;
display: block; }
.discover-view .parameters .seeds,
.discover-view .parameters .tunabilities {
float: left; }
.discover-view .seeds .seed {
color: #FFFFFF;
padding: 0 30px 30px 0;
float: left; }
.discover-view .seeds .seed .thumbnail-wrapper {
float: left;
width: 44px;
border: 0 !important; }
.discover-view .seeds .seed .label .remove {
-webkit-transition: all 0.2s ease-in-out;
-moz-transition: all 0.2s ease-in-out;
-o-transition: all 0.2s ease-in-out;
transition: all 0.2s ease-in-out;
cursor: pointer;
font-size: 10px;
padding: 4px;
margin-left: 4px; }
.discover-view .seeds .seed .label .remove:hover {
color: #cf2d2d; }
.discover-view .seeds .seed .name {
font-size: 16px;
position: relative;
vertical-align: top;
margin: 0 10px 10px 0;
border-radius: 3px; }
.discover-view .seeds .seed .type {
display: inline-block;
font-size: 12px;
opacity: 0.5;
padding: 0 10px 0 5px; }
.discover-view .seeds .seed .remove {
font-size: 10px;
padding: 12px;
position: absolute;
top: 0;
right: 0;
cursor: pointer; }
padding-top: 3px; }
.discover-view .seeds .seed.has-thumbnail .label,
.discover-view .seeds .seed.has-thumbnail .name {
padding-left: 52px; }
.discover-view .autocomplete-field {
width: 250px;
display: inline-block;
vertical-align: top;
padding: 0; }
.discover-view .autocomplete-field .input {
.discover-view .tunabilities::after {
content: '';
clear: both;
display: block; }
.discover-view .tunabilities .tunability {
width: 200px;
float: left;
box-sizing: border-box;
padding: 0 30px 30px 0;
position: relative; }
.discover-view .tunabilities .tunability .input {
float: none;
margin: 0 10px 10px 0; }
.discover-view .autocomplete-field .input input {
background: rgba(100, 100, 100, 0.5);
color: #FFFFFF;
padding: 7px 13px;
font-size: 16px;
border: 1px solid transparent; }
.discover-view .autocomplete-field .input input:focus, .discover-view .autocomplete-field .input input:active {
border-color: #32b5f2; }
.discover-view .autocomplete-field .input:after {
background: #32b5f2; }
width: 100%;
padding-top: 4px; }
.discover-view .tunabilities .tunability .input-range .input-range__label-container {
display: none; }
.discover-view .tunabilities .tunability .remove {
-webkit-transition: all 0.2s ease-in-out;
-moz-transition: all 0.2s ease-in-out;
-o-transition: all 0.2s ease-in-out;
transition: all 0.2s ease-in-out;
cursor: pointer;
font-size: 10px;
padding: 4px;
margin-left: 4px; }
.discover-view .tunabilities .tunability .remove:hover {
color: #cf2d2d; }
.discover-view .actions {
padding-top: 40px; }
.discover-view .actions .autocomplete-field {
width: 250px;
display: inline-block;
vertical-align: top;
padding: 0;
margin-right: 1px; }
.discover-view .actions .autocomplete-field .input {
float: none;
margin: 0 10px 10px 0; }
.discover-view .actions .autocomplete-field .input input {
background: rgba(100, 100, 100, 0.5);
color: #FFFFFF;
padding: 15px 13px 10px;
font-size: 16px;
border: 1px solid transparent;
border-top-right-radius: 0;
border-bottom-right-radius: 0; }
.discover-view .actions .autocomplete-field .input input:focus, .discover-view .actions .autocomplete-field .input input:active {
border-color: #32b5f2; }
.discover-view .actions .autocomplete-field .input:after {
background: #32b5f2; }
.discover-view .actions .autocomplete-field .results {
top: 49px; }
.discover-view .actions .dropdown-field .button {
background: rgba(100, 100, 100, 0.5);
color: #FFFFFF;
padding: 16px 20px 14px 20px;
border-top-left-radius: 0;
border-bottom-left-radius: 0; }
.discover-view .actions .dropdown-field .options {
top: 50px;
right: 20px; }
.discover-view section .grid {
padding-bottom: 20px; }
@ -5744,8 +5955,22 @@ main .track-view {
@media (max-width: 800px) {
.discover-view .intro {
padding: 40px 20px; }
.discover-view .seeds {
padding-top: 20px; } }
.discover-view .parameters .seeds {
padding-top: 20px;
float: none; }
.discover-view .parameters .seeds .seed {
float: none; }
.discover-view .parameters .tunabilities {
float: none; }
.discover-view .parameters .tunabilities .tunability {
width: 100%;
float: none; }
.discover-view .parameters .add .add-properties {
margin-right: 0; }
.discover-view .col.tracks {
width: 100%; }
.discover-view .col.others, .discover-view .col.w5 {
display: none; } }
.discover-featured-view .intro,
.discover-new-releases-view .intro {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -38,7 +38,7 @@
// Release details
// These are automatically injected to built HTML
var build = "1517341290";
var build = "1517428265";
var version = "3.11.0";
// Construct the script tag

4
src/js/bootstrap.js vendored
View File

@ -41,7 +41,9 @@ var initialState = {
artists: {},
playlists: {},
users: {},
tracks: {}
tracks: {},
http_streaming_enabled: false,
http_streaming_url: "http://"+window.location.hostname+":8000/mopidy"
},
ui: {
show_initial_setup: true,

View File

@ -70,6 +70,10 @@ class PlaybackControls extends React.Component{
return (
<div className={(this.state.expanded ? "expanded playback-controls" : "playback-controls")}>
{this.props.http_streaming ? <audio className="http-streaming" autoPlay>
<source src={this.props.http_streaming} type="audio/mpeg" />
</audio> : null}
<div className="current-track">
<div className="thumbnail-wrapper" onClick={e => this.handleThumbnailClick(e)}>
@ -135,6 +139,7 @@ class PlaybackControls extends React.Component{
const mapStateToProps = (state, ownProps) => {
return {
http_streaming: (state.core.http_streaming_enabled && state.core.http_streaming_url ? state.core.http_streaming_url : ''),
current_track: (state.core.current_track && state.core.tracks[state.core.current_track.uri] !== undefined ? state.core.tracks[state.core.current_track.uri] : null),
radio_enabled: (state.ui.radio && state.ui.radio.enabled ? true : false),
play_state: state.mopidy.play_state,

View File

@ -331,7 +331,38 @@ class Settings extends React.Component {
</div>
</div>
{this.renderApplyButton()}
</form>
</form>
<h4 className="underline">Streaming</h4>
<div className="field checkbox">
<div className="name">Enable</div>
<div className="input">
<label>
<input
type="checkbox"
name="ssl"
checked={this.props.core.http_streaming_enabled}
onChange={e => this.props.coreActions.set({http_streaming_enabled: !this.props.core.http_streaming_enabled})} />
<span className="label has-tooltip">
Enable HTTP streaming
<span className="tooltip">Requires streaming service like Icecast2</span>
</span>
</label>
</div>
</div>
<div className="field">
<div className="name">Stream location</div>
<div className="input">
<input
type="text"
onChange={e => this.props.coreActions.set({http_streaming_url: e.target.value})}
value={this.props.core.http_streaming_url} />
<div className="description">
The full URL to your stream endpoint
</div>
</div>
</div>
<h4 className="underline">Localization</h4>