Merge branch 'master' into develop

This commit is contained in:
James Barnsley
2021-04-14 20:24:07 +12:00
19 changed files with 218 additions and 266 deletions

View File

@ -1 +1 @@
3.57.5
3.57.6

View File

@ -4,6 +4,7 @@ Iris is an extension for the Mopidy music server. With support for Spotify, Last
Built and maintained by James Barnsley.
[![Downloads](https://static.pepy.tech/personalized-badge/mopidy-iris?period=total&units=international_system&left_color=grey&right_color=green&left_text=Downloads)](https://pepy.tech/project/mopidy-iris)
[![npm](https://img.shields.io/npm/v/mopidy-iris.svg?style=flat-square)]()
![badge](https://img.shields.io/pypi/v/mopidy-iris.svg?style=flat-square)
![badge](https://img.shields.io/badge/unique_monthly_users-4,000+-brightgreen.svg?style=flat-square)

View File

@ -3,7 +3,7 @@ import pathlib
from mopidy import config, ext
__version__ = "3.57.5"
__version__ = "3.57.6"
logger = logging.getLogger(__name__)

View File

@ -135160,7 +135160,8 @@ var Button = function Button(_ref) {
onClickProp = _ref.onClick,
to = _ref.to,
uri = _ref.uri,
rest = _objectWithoutProperties(_ref, ["className", "primary", "type", "size", "icon", "pullRight", "colour", "confirming", "timingOut", "destructiveHover", "discrete", "working", "disabled", "noHover", "submit", "tracking", "href", "onClick", "to", "uri"]);
uriType = _ref.uriType,
rest = _objectWithoutProperties(_ref, ["className", "primary", "type", "size", "icon", "pullRight", "colour", "confirming", "timingOut", "destructiveHover", "discrete", "working", "disabled", "noHover", "submit", "tracking", "href", "onClick", "to", "uri", "uriType"]);
var classNames = [];
@ -135183,8 +135184,7 @@ var Button = function Button(_ref) {
if (noHover) classNames.push('no-hover');
var Element = 'button';
if (to) Element = _Link__WEBPACK_IMPORTED_MODULE_1__["default"];
if (uri) Element = _URILink__WEBPACK_IMPORTED_MODULE_2__["default"]; // MISSING URI TYPE
if (uri) Element = _URILink__WEBPACK_IMPORTED_MODULE_2__["default"];
if (href) Element = 'a';
var onClick = function onClick(e) {
@ -135199,7 +135199,7 @@ var Button = function Button(_ref) {
};
return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(Element, _extends({
type: submit ? 'submit' : 'button',
type: uri ? uriType : submit ? 'submit' : 'button',
className: "button ".concat(className, " ").concat(classNames.map(function (s) {
return " button--".concat(s);
}).join(' ')),
@ -139066,25 +139066,6 @@ __webpack_require__.r(__webpack_exports__);
/* harmony import */ var _locale__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ../../locale */ "./src/js/locale/index.js");
/* harmony import */ var _Button__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ../Button */ "./src/js/components/Button.js");
/* harmony import */ var _util_selectors__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ../../util/selectors */ "./src/js/util/selectors.js");
function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); }
function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; }
function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); }
function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }
function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); return true; } catch (e) { return false; } }
function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
@ -139095,106 +139076,77 @@ function _defineProperty(obj, key, value) { if (key in obj) { Object.definePrope
var FollowButton = function FollowButton(_ref) {
var following = _ref.spotifyActions.following,
createNotification = _ref.uiActions.createNotification,
uri = _ref.uri,
addText = _ref.addText,
removeText = _ref.removeText,
spotify_authorized = _ref.spotify_authorized,
is_following = _ref.is_following,
loading = _ref.loading;
var FollowButton = /*#__PURE__*/function (_React$Component) {
_inherits(FollowButton, _React$Component);
var remove = function remove() {
return following(uri, 'DELETE');
};
var _super = _createSuper(FollowButton);
var add = function add() {
return following(uri, 'PUT');
};
function FollowButton() {
var _this;
_classCallCheck(this, FollowButton);
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
_this = _super.call.apply(_super, [this].concat(args));
_defineProperty(_assertThisInitialized(_this), "remove", function () {
var _this$props = _this.props,
actions = _this$props.spotifyActions,
uri = _this$props.uri;
actions.following(uri, 'DELETE');
var unauthorized = function unauthorized() {
return createNotification({
content: Object(_locale__WEBPACK_IMPORTED_MODULE_6__["i18n"])('errors.authorization_required', {
provider: Object(_locale__WEBPACK_IMPORTED_MODULE_6__["i18n"])('services.spotify.title')
}),
level: 'warning'
});
};
_defineProperty(_assertThisInitialized(_this), "add", function () {
var _this$props2 = _this.props,
actions = _this$props2.spotifyActions,
uri = _this$props2.uri;
actions.following(uri, 'PUT');
});
if (!uri) return null;
_defineProperty(_assertThisInitialized(_this), "unauthorized", function () {
var createNotification = _this.props.uiActions.createNotification;
createNotification({
content: Object(_locale__WEBPACK_IMPORTED_MODULE_6__["i18n"])('errors.authorization_required', {
provider: Object(_locale__WEBPACK_IMPORTED_MODULE_6__["i18n"])('services.spotify.title')
}),
level: 'warning'
});
});
_defineProperty(_assertThisInitialized(_this), "render", function () {
var _this$props3 = _this.props,
uri = _this$props3.uri,
addText = _this$props3.addText,
removeText = _this$props3.removeText,
spotify_authorized = _this$props3.spotify_authorized,
is_following = _this$props3.is_following,
loading = _this$props3.loading;
if (!uri) return null;
if (!spotify_authorized) {
return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_Button__WEBPACK_IMPORTED_MODULE_7__["Button"], {
disabled: true,
working: loading,
onClick: _this.unauthorized,
tracking: {
category: 'FollowButton',
action: 'Add (disabled)'
},
"data-qa-node": "Button",
"data-qa-file": "FollowButton"
}, addText || Object(_locale__WEBPACK_IMPORTED_MODULE_6__["i18n"])('actions.add_to_library'));
}
if (is_following === true) {
return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_Button__WEBPACK_IMPORTED_MODULE_7__["Button"], {
type: "destructive",
working: loading,
onClick: _this.remove,
tracking: {
category: 'FollowButton',
action: 'Remove'
},
"data-qa-node": "Button",
"data-qa-file": "FollowButton"
}, removeText || Object(_locale__WEBPACK_IMPORTED_MODULE_6__["i18n"])('actions.remove_from_library'));
}
return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_Button__WEBPACK_IMPORTED_MODULE_7__["Button"], {
onClick: _this.add,
working: loading,
tracking: {
category: 'FollowButton',
action: 'Add'
},
"data-qa-node": "Button",
"data-qa-file": "FollowButton"
}, addText || Object(_locale__WEBPACK_IMPORTED_MODULE_6__["i18n"])('actions.add_to_library'));
});
return _this;
if (!spotify_authorized) {
return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_Button__WEBPACK_IMPORTED_MODULE_7__["Button"], {
disabled: true,
working: loading,
onClick: unauthorized,
tracking: {
category: 'FollowButton',
action: 'Add (disabled)'
},
"data-qa-node": "Button",
"data-qa-file": "FollowButton"
}, addText || Object(_locale__WEBPACK_IMPORTED_MODULE_6__["i18n"])('actions.add_to_library'));
}
return FollowButton;
}(react__WEBPACK_IMPORTED_MODULE_0___default.a.Component);
if (is_following === true) {
return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_Button__WEBPACK_IMPORTED_MODULE_7__["Button"], {
type: "destructive",
working: loading,
onClick: remove,
tracking: {
category: 'FollowButton',
action: 'Remove'
},
"data-qa-node": "Button",
"data-qa-file": "FollowButton"
}, removeText || Object(_locale__WEBPACK_IMPORTED_MODULE_6__["i18n"])('actions.remove_from_library'));
}
var mapStateToProps = function mapStateToProps(state, ownProps) {
var uri = ownProps.uri;
var loadingSelector = Object(_util_selectors__WEBPACK_IMPORTED_MODULE_8__["makeLoadingSelector"])(['spotify_me/tracks?', 'spotify_me/albums?', 'spotify_me/following?', "spotify_playlists/".concat(Object(_util_helpers__WEBPACK_IMPORTED_MODULE_5__["getFromUri"])('playlistid', uri), "/followers?")]);
return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_Button__WEBPACK_IMPORTED_MODULE_7__["Button"], {
onClick: add,
working: loading,
tracking: {
category: 'FollowButton',
action: 'Add'
},
"data-qa-node": "Button",
"data-qa-file": "FollowButton"
}, addText || Object(_locale__WEBPACK_IMPORTED_MODULE_6__["i18n"])('actions.add_to_library'));
};
var mapStateToProps = function mapStateToProps(state) {
var loadingSelector = Object(_util_selectors__WEBPACK_IMPORTED_MODULE_8__["makeLoadingSelector"])(['(.*)(follow)|(me\/albums)(.*)']);
return {
loading: loadingSelector(state),
spotify_authorized: state.spotify.authorization
@ -145155,7 +145107,8 @@ var SearchResults = function SearchResults(_ref) {
"data-qa-file": "SearchResults"
}, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_URILink__WEBPACK_IMPORTED_MODULE_3__["default"], {
uri: "iris:search:all:".concat(encodedTerm),
type: "search",
uriType: "search",
unencoded: true,
"data-qa-node": "URILink",
"data-qa-file": "SearchResults"
}, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_locale__WEBPACK_IMPORTED_MODULE_7__["I18n"], {
@ -145173,7 +145126,8 @@ var SearchResults = function SearchResults(_ref) {
"data-qa-file": "SearchResults"
})), all && /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_URILink__WEBPACK_IMPORTED_MODULE_3__["default"], {
uri: "iris:search:".concat(type, ":").concat(encodedTerm),
type: "search",
uriType: "search",
unencoded: true,
"data-qa-node": "URILink",
"data-qa-file": "SearchResults"
}, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_locale__WEBPACK_IMPORTED_MODULE_7__["I18n"], {
@ -145210,7 +145164,8 @@ var SearchResults = function SearchResults(_ref) {
"data-qa-file": "SearchResults"
}), resultsCount > results.length && /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_Button__WEBPACK_IMPORTED_MODULE_8__["default"], {
uri: "iris:search:".concat(type, ":").concat(encodedTerm),
debug: true,
uriType: "search",
unencoded: true,
"data-qa-node": "Button",
"data-qa-file": "SearchResults"
}, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_locale__WEBPACK_IMPORTED_MODULE_7__["I18n"], {
@ -150609,15 +150564,16 @@ __webpack_require__.r(__webpack_exports__);
/* harmony default export */ __webpack_exports__["default"] = (/*#__PURE__*/Object(react__WEBPACK_IMPORTED_MODULE_0__["memo"])(function (props) {
/* harmony default export */ __webpack_exports__["default"] = (/*#__PURE__*/Object(react__WEBPACK_IMPORTED_MODULE_0__["memo"])(function (_ref) {
var type = _ref.type,
rawUri = _ref.uri,
className = _ref.className,
handleContextMenu = _ref.handleContextMenu,
children = _ref.children,
unencoded = _ref.unencoded;
var to = null;
var uri = props.uri,
type = props.type;
var uriType = type || Object(_util_helpers__WEBPACK_IMPORTED_MODULE_2__["uriType"])(uri);
if (!props.unencoded) {
uri = Object(_util_format__WEBPACK_IMPORTED_MODULE_3__["encodeUri"])(uri);
}
var uriType = type || Object(_util_helpers__WEBPACK_IMPORTED_MODULE_2__["uriType"])(rawUri);
var uri = !unencoded ? Object(_util_format__WEBPACK_IMPORTED_MODULE_3__["encodeUri"])(rawUri) : rawUri;
switch (uriType) {
case 'playlist':
@ -150649,7 +150605,7 @@ __webpack_require__.r(__webpack_exports__);
break;
case 'search':
var exploded = uri.split('%3A');
var exploded = uri.split(':');
to = "/search/".concat(exploded[2], "/").concat(exploded[3]);
break;
@ -150659,21 +150615,19 @@ __webpack_require__.r(__webpack_exports__);
if (uri) {
return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_Link__WEBPACK_IMPORTED_MODULE_1__["default"], {
className: props.className ? props.className : null,
className: className,
to: to,
onContextMenu: function onContextMenu(e) {
return props.handleContextMenu ? props.handleContextMenu(e) : null;
},
onContextMenu: handleContextMenu,
"data-qa-node": "Link",
"data-qa-file": "URILink"
}, props.children);
}, children);
}
return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("span", {
className: props.className ? props.className : null,
className: className,
"data-qa-node": "span",
"data-qa-file": "URILink"
}, props.children);
}, children);
}));
/***/ }),
@ -152356,7 +152310,10 @@ var CoreMiddleware = function () {
})));
}
store.dispatch(coreActions.removeItem(action.itemUri));
store.dispatch(coreActions.itemLoaded({
uri: action.itemUri,
in_library: false
}));
localforage__WEBPACK_IMPORTED_MODULE_2___default.a.removeItem(action.itemUri);
next(action);
break;
@ -159612,7 +159569,7 @@ function following(uri) {
content: /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("span", {
"data-qa-node": "span",
"data-qa-file": "actions"
}, "Removed", /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_components_URILink__WEBPACK_IMPORTED_MODULE_4__["default"], {
}, "Removed", ' ', /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_components_URILink__WEBPACK_IMPORTED_MODULE_4__["default"], {
type: type,
uri: uri,
"data-qa-node": "URILink",
@ -159625,7 +159582,7 @@ function following(uri) {
content: /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("span", {
"data-qa-node": "span",
"data-qa-file": "actions"
}, "Added", /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_components_URILink__WEBPACK_IMPORTED_MODULE_4__["default"], {
}, "Added", ' ', /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_components_URILink__WEBPACK_IMPORTED_MODULE_4__["default"], {
type: type,
uri: uri,
"data-qa-node": "URILink",
@ -165470,7 +165427,7 @@ var Album = /*#__PURE__*/function (_React$Component) {
var mapStateToProps = function mapStateToProps(state, ownProps) {
var uri = Object(_util_format__WEBPACK_IMPORTED_MODULE_26__["decodeUri"])(ownProps.match.params.uri);
var itemSelector = Object(_util_selectors__WEBPACK_IMPORTED_MODULE_23__["makeItemSelector"])(uri);
var loadingSelector = Object(_util_selectors__WEBPACK_IMPORTED_MODULE_23__["makeLoadingSelector"])(["(.*)".concat(uri, "(.*)"), '^((?!contains).)*$', '^((?!me\/albums).)*$']);
var loadingSelector = Object(_util_selectors__WEBPACK_IMPORTED_MODULE_23__["makeLoadingSelector"])(["(.*)".concat(uri, "(.*)"), '^((?!contains).)*$', '^((?!me\/albums).)*$', '^((?!followers).)*$']);
return {
uri: uri,
slim_mode: state.ui.slim_mode,
@ -166363,7 +166320,7 @@ var Artist = /*#__PURE__*/function (_React$Component) {
var mapStateToProps = function mapStateToProps(state, props) {
var uri = Object(_util_format__WEBPACK_IMPORTED_MODULE_21__["decodeUri"])(props.match.params.uri);
var loadingSelector = Object(_util_selectors__WEBPACK_IMPORTED_MODULE_26__["makeLoadingSelector"])(["(.*)".concat(uri, "(.*)"), '^((?!contains).)*$', '^((?!albums).)*$', '^((?!related-artists).)*$', '^((?!top-tracks).)*$']);
var loadingSelector = Object(_util_selectors__WEBPACK_IMPORTED_MODULE_26__["makeLoadingSelector"])(["(.*)".concat(uri, "(.*)"), '^((?!contains).)*$', '^((?!albums).)*$', '^((?!related-artists).)*$', '^((?!top-tracks).)*$', '^((?!following).)*$']);
var artistSelector = Object(_util_selectors__WEBPACK_IMPORTED_MODULE_26__["makeItemSelector"])(uri);
var artist = artistSelector(state);
var albums = null;
@ -167850,7 +167807,7 @@ var mapStateToProps = function mapStateToProps(state, ownProps) {
var local_library_playlists = _state$mopidy.library_playlists;
var uri = Object(_util_format__WEBPACK_IMPORTED_MODULE_26__["decodeUri"])(ownProps.match.params.uri);
var itemSelector = Object(_util_selectors__WEBPACK_IMPORTED_MODULE_24__["makeItemSelector"])(uri);
var loadingSelector = Object(_util_selectors__WEBPACK_IMPORTED_MODULE_24__["makeLoadingSelector"])(["(.*)".concat(uri, "(.*)"), '^((?!contains).)*$', '^((?!tracks).)*$']);
var loadingSelector = Object(_util_selectors__WEBPACK_IMPORTED_MODULE_24__["makeLoadingSelector"])(["(.*)".concat(uri, "(.*)"), '^((?!contains).)*$', '^((?!tracks).)*$', '^((?!followers).)*$']);
var loadingTracksSelector = Object(_util_selectors__WEBPACK_IMPORTED_MODULE_24__["makeLoadingSelector"])(["(.*)".concat(uri, "(.*)tracks(.*)")]);
return {
uri: uri,

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -106,8 +106,8 @@
// Release details
// These are automatically injected to built HTML
var build = "1617608354";
var version = "3.57.5";
var build = "1618219813";
var version = "3.57.6";
// Construct the script tag
var js = document.createElement("script");

View File

@ -1,5 +1,5 @@
{
"manifest_version": "3.57.5",
"manifest_version": "3.57.6",
"short_name": "Iris",
"name": "Iris",
"icons": [

View File

@ -1,6 +1,6 @@
{
"name": "mopidy-iris",
"version": "3.57.5",
"version": "3.57.6",
"description": "Mopidy HTTP interface",
"repository": "https://github.com/jaedb/iris",
"author": "James Barnsley <james@barnsley.nz>",

View File

@ -1,6 +1,6 @@
[metadata]
name = Mopidy-Iris
version = 3.57.5
version = 3.57.6
url = https://github.com/jaedb/iris
author = James Barnsley
author_email = james@barnsley.nz

View File

@ -24,6 +24,7 @@ const Button = ({
onClick: onClickProp,
to,
uri,
uriType,
...rest
}) => {
const classNames = [];
@ -46,7 +47,7 @@ const Button = ({
let Element = 'button';
if (to) Element = Link;
if (uri) Element = URILink; // MISSING URI TYPE
if (uri) Element = URILink;
if (href) Element = 'a';
const onClick = (e) => {
@ -63,7 +64,7 @@ const Button = ({
return (
<Element
type={submit ? 'submit' : 'button'}
type={uri ? uriType : (submit ? 'submit' : 'button')}
className={`button ${className} ${classNames.map((s) => ` button--${s}`).join(' ')}`}
disabled={disabled}
to={to}

View File

@ -3,89 +3,70 @@ import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as uiActions from '../../services/ui/actions';
import * as spotifyActions from '../../services/spotify/actions';
import { isLoading, getFromUri } from '../../util/helpers';
import { getFromUri } from '../../util/helpers';
import { i18n } from '../../locale';
import { Button } from '../Button';
import { makeLoadingSelector } from '../../util/selectors';
class FollowButton extends React.Component {
remove = () => {
const { spotifyActions: actions, uri } = this.props;
actions.following(uri, 'DELETE');
}
const FollowButton = ({
spotifyActions: {
following,
},
uiActions: {
createNotification,
},
uri,
addText,
removeText,
spotify_authorized,
is_following,
loading,
}) => {
const remove = () => following(uri, 'DELETE');
const add = () => following(uri, 'PUT');
const unauthorized = () => createNotification({
content: i18n('errors.authorization_required', { provider: i18n('services.spotify.title') }),
level: 'warning',
});
add = () => {
const { spotifyActions: actions, uri } = this.props;
actions.following(uri, 'PUT');
}
if (!uri) return null;
unauthorized = () => {
const { uiActions: { createNotification } } = this.props;
createNotification({
content: i18n('errors.authorization_required', { provider: i18n('services.spotify.title') }),
level: 'warning',
});
}
render = () => {
const {
uri,
addText,
removeText,
spotify_authorized,
is_following,
loading,
} = this.props;
if (!uri) return null;
if (!spotify_authorized) {
return (
<Button
disabled
working={loading}
onClick={this.unauthorized}
tracking={{ category: 'FollowButton', action: 'Add (disabled)' }}
>
{addText || i18n('actions.add_to_library')}
</Button>
);
} if (is_following === true) {
return (
<Button
type="destructive"
working={loading}
onClick={this.remove}
tracking={{ category: 'FollowButton', action: 'Remove' }}
>
{removeText || i18n('actions.remove_from_library')}
</Button>
);
}
if (!spotify_authorized) {
return (
<Button
onClick={this.add}
disabled
working={loading}
tracking={{ category: 'FollowButton', action: 'Add' }}
onClick={unauthorized}
tracking={{ category: 'FollowButton', action: 'Add (disabled)' }}
>
{addText || i18n('actions.add_to_library')}
</Button>
);
} if (is_following === true) {
return (
<Button
type="destructive"
working={loading}
onClick={remove}
tracking={{ category: 'FollowButton', action: 'Remove' }}
>
{removeText || i18n('actions.remove_from_library')}
</Button>
);
}
return (
<Button
onClick={add}
working={loading}
tracking={{ category: 'FollowButton', action: 'Add' }}
>
{addText || i18n('actions.add_to_library')}
</Button>
);
}
const mapStateToProps = (state, ownProps) => {
const {
uri,
} = ownProps;
const loadingSelector = makeLoadingSelector([
'spotify_me/tracks?',
'spotify_me/albums?',
'spotify_me/following?',
`spotify_playlists/${getFromUri('playlistid', uri)}/followers?`,
]);
const mapStateToProps = (state) => {
const loadingSelector = makeLoadingSelector(['(.*)(follow)|(me\/albums)(.*)']);
return {
loading: loadingSelector(state),

View File

@ -56,7 +56,7 @@ const SearchResults = ({
<h4>
{!all && (
<span>
<URILink uri={`iris:search:all:${encodedTerm}`} type="search">
<URILink uri={`iris:search:all:${encodedTerm}`} uriType="search" unencoded>
<I18n path="search.title" />
</URILink>
{' '}
@ -66,7 +66,7 @@ const SearchResults = ({
</span>
)}
{all && (
<URILink uri={`iris:search:${type}:${encodedTerm}`} type="search">
<URILink uri={`iris:search:${type}:${encodedTerm}`} uriType="search" unencoded>
<I18n path={`search.${type}.title`} />
</URILink>
)}
@ -79,7 +79,7 @@ const SearchResults = ({
{/* <LazyLoadListener enabled={this.props.artists_more && spotify_search_enabled} loadMore={loadMore} /> */}
{resultsCount > results.length && (
<Button uri={`iris:search:${type}:${encodedTerm}`} debug>
<Button uri={`iris:search:${type}:${encodedTerm}`} uriType="search" unencoded>
<I18n path={`search.${type}.more`} count={resultsCount} />
</Button>
)}
@ -89,7 +89,12 @@ const SearchResults = ({
};
const mapStateToProps = (state, ownProps) => {
const { query: { term }, type } = ownProps;
const {
query: {
term,
},
type,
} = ownProps;
const {
ui: {
uri_schemes_priority = [],

View File

@ -3,13 +3,17 @@ import Link from './Link';
import { uriType as uriTypeHelper } from '../util/helpers';
import { encodeUri } from '../util/format';
export default memo((props) => {
export default memo(({
type,
uri: rawUri,
className,
handleContextMenu,
children,
unencoded,
}) => {
let to = null;
let { uri, type } = props;
const uriType = type || uriTypeHelper(uri);
if (!props.unencoded) {
uri = encodeUri(uri);
}
const uriType = type || uriTypeHelper(rawUri);
const uri = (!unencoded) ? encodeUri(rawUri) : rawUri;
switch (uriType) {
case 'playlist':
@ -41,7 +45,7 @@ export default memo((props) => {
break;
case 'search':
var exploded = uri.split('%3A');
var exploded = uri.split(':');
to = `/search/${exploded[2]}/${exploded[3]}`;
break;
@ -52,17 +56,17 @@ export default memo((props) => {
if (uri) {
return (
<Link
className={props.className ? props.className : null}
className={className}
to={to}
onContextMenu={(e) => (props.handleContextMenu ? props.handleContextMenu(e) : null)}
onContextMenu={handleContextMenu}
>
{props.children}
{children}
</Link>
);
}
return (
<span className={props.className ? props.className : null}>
{props.children}
<span className={className}>
{children}
</span>
);
});

View File

@ -628,10 +628,11 @@ const CoreMiddleware = (function () {
items_uris,
}));
}
store.dispatch(coreActions.removeItem(action.itemUri));
store.dispatch(coreActions.itemLoaded({
uri: action.itemUri,
in_library: false,
}));
localForage.removeItem(action.itemUri);
next(action);
break;
}

View File

@ -809,21 +809,23 @@ export function following(uri, method = 'GET') {
if (method === 'DELETE') {
dispatch(coreActions.removeFromLibrary(`spotify:library:${type}s`, uri));
dispatch(uiActions.createNotification({
content: <span>
Removed
<URILink type={type} uri={uri}>{asset ? asset.name : type}</URILink>
{' '}
from library
content: <span>
Removed
{' '}
<URILink type={type} uri={uri}>{asset ? asset.name : type}</URILink>
{' '}
from library
</span>,
}));
} else if (method === 'PUT' || method === 'POST') {
dispatch(coreActions.addToLibrary(`spotify:library:${type}s`, asset));
dispatch(uiActions.createNotification({
content: <span>
Added
<URILink type={type} uri={uri}>{asset ? asset.name : type}</URILink>
{' '}
to library
content: <span>
Added
{' '}
<URILink type={type} uri={uri}>{asset ? asset.name : type}</URILink>
{' '}
to library
</span>,
}));
}
@ -929,11 +931,11 @@ export function getArtist(uri, { full, forceRefetch } = {}) {
);
// Do we want a full artist, with all supporting material?
if (full) {
if (full) {
// All albums (gets all pages, may take some time to iterate them all)
let albums = [];
const fetchAlbums = (endpoint) => request({
dispatch, getState, endpoint, uri,
const fetchAlbums = (endpoint) => request({
dispatch, getState, endpoint, uri,
})
.then((response) => {
albums = [...albums, ...formatAlbums(response.items)];
@ -953,8 +955,8 @@ export function getArtist(uri, { full, forceRefetch } = {}) {
let tracksEndpoint = `artists/${getFromUri('artistid', uri)}`;
tracksEndpoint += `/top-tracks?country=${getState().spotify.country}`;
if (forceRefetch) tracksEndpoint += `&refetch=${Date.now()}`;
request({
dispatch, getState, endpoint: tracksEndpoint, uri,
request({
dispatch, getState, endpoint: tracksEndpoint, uri,
})
.then(
(response) => {
@ -968,8 +970,8 @@ export function getArtist(uri, { full, forceRefetch } = {}) {
// Related artists
let relatedEndpoint = `artists/${getFromUri('artistid', uri)}/related-artists`;
if (forceRefetch) relatedEndpoint += `?refetch=${Date.now()}`;
request({
dispatch, getState, endpoint: relatedEndpoint, uri,
request({
dispatch, getState, endpoint: relatedEndpoint, uri,
})
.then(
(response) => {
@ -1049,8 +1051,8 @@ export function getUser(uri, { full, forceRefetch } = {}) {
if (full) {
let playlists = [];
const fetchPlaylists = (endpoint) => request({
dispatch, getState, endpoint, uri,
const fetchPlaylists = (endpoint) => request({
dispatch, getState, endpoint, uri,
})
.then((response) => {
playlists = [...playlists, ...formatPlaylists(response.items)];
@ -1115,7 +1117,7 @@ export function getAlbum(uri, { full, forceRefetch } = {}) {
tracks,
}));
}
},
},
);
if (response.tracks.next) {
@ -1276,8 +1278,8 @@ export function getPlaylistTracks(uri, { forceRefetch, callbackAction } = {}) {
let tracks = [];
const fetchTracks = (endpoint) => request({
dispatch, getState, endpoint, uri,
const fetchTracks = (endpoint) => request({
dispatch, getState, endpoint, uri,
})
.then((response) => {
tracks = [...tracks, ...formatTracks(response.items)];
@ -1333,8 +1335,8 @@ export function getPlaylist(uri, options = {}) {
endpoint += `?market=${getState().spotify.country}`;
if (forceRefetch) endpoint += `&refetch=${Date.now()}`;
request({
dispatch, getState, endpoint, uri,
request({
dispatch, getState, endpoint, uri,
})
.then(
(response) => {

View File

@ -348,7 +348,7 @@ class Album extends React.Component {
const mapStateToProps = (state, ownProps) => {
const uri = decodeUri(ownProps.match.params.uri);
const itemSelector = makeItemSelector(uri);
const loadingSelector = makeLoadingSelector([`(.*)${uri}(.*)`, '^((?!contains).)*$', '^((?!me\/albums).)*$']);
const loadingSelector = makeLoadingSelector([`(.*)${uri}(.*)`, '^((?!contains).)*$', '^((?!me\/albums).)*$', '^((?!followers).)*$']);
return {
uri,
slim_mode: state.ui.slim_mode,

View File

@ -602,7 +602,7 @@ class Artist extends React.Component {
const mapStateToProps = (state, props) => {
const uri = decodeUri(props.match.params.uri);
const loadingSelector = makeLoadingSelector([`(.*)${uri}(.*)`, '^((?!contains).)*$', '^((?!albums).)*$', '^((?!related-artists).)*$', '^((?!top-tracks).)*$']);
const loadingSelector = makeLoadingSelector([`(.*)${uri}(.*)`, '^((?!contains).)*$', '^((?!albums).)*$', '^((?!related-artists).)*$', '^((?!top-tracks).)*$', '^((?!following).)*$']);
const artistSelector = makeItemSelector(uri);
const artist = artistSelector(state);
let albums = null;

View File

@ -496,7 +496,7 @@ const mapStateToProps = (state, ownProps) => {
const uri = decodeUri(ownProps.match.params.uri);
const itemSelector = makeItemSelector(uri);
const loadingSelector = makeLoadingSelector([`(.*)${uri}(.*)`, '^((?!contains).)*$', '^((?!tracks).)*$']);
const loadingSelector = makeLoadingSelector([`(.*)${uri}(.*)`, '^((?!contains).)*$', '^((?!tracks).)*$', '^((?!followers).)*$']);
const loadingTracksSelector = makeLoadingSelector([`(.*)${uri}(.*)tracks(.*)`]);
return {