Skip to content

Commit

Permalink
Merge branch 'master' into LB-1693
Browse files Browse the repository at this point in the history
  • Loading branch information
MonkeyDo authored Dec 5, 2024
2 parents 53edec6 + f79a428 commit cd2eebb
Show file tree
Hide file tree
Showing 43 changed files with 1,512 additions and 255 deletions.
4 changes: 2 additions & 2 deletions admin/sql/create_indexes.sql
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ CREATE UNIQUE INDEX user_id_event_type_event_id_ndx_hide_user_timeline_event ON

CREATE INDEX user_id_ndx_pinned_recording ON pinned_recording (user_id);

CREATE INDEX release_mbid_ndx_release_color ON release_color (release_mbid);
CREATE UNIQUE INDEX caa_id_ndx_release_color ON release_color (caa_id);
CREATE UNIQUE INDEX release_mbid_ndx_release_color ON release_color (release_mbid);
CREATE UNIQUE INDEX caa_id_release_mbid_ndx_release_color ON release_color (caa_id, release_mbid);

CREATE UNIQUE INDEX user_id_ndx_user_setting ON user_setting (user_id);

Expand Down
8 changes: 5 additions & 3 deletions consul_config.py.ctmpl
Original file line number Diff line number Diff line change
Expand Up @@ -178,10 +178,12 @@ EXTERNAL_SERVICES_SPOTIFY_CACHE_QUEUE = '''{{template "KEY" "external_services_s
EXTERNAL_SERVICES_APPLE_CACHE_QUEUE = '''{{template "KEY" "external_services_apple_cache"}}'''
EXTERNAL_SERVICES_SOUNDCLOUD_CACHE_QUEUE = '''{{template "KEY" "external_services_soundcloud_cache"}}'''

MUSICBRAINZ_CLIENT_ID = '''{{template "KEY" "musicbrainz/client_id"}}'''
MUSICBRAINZ_CLIENT_SECRET = '''{{template "KEY" "musicbrainz/client_secret"}}'''
MUSICBRAINZ_BASE_URL = "https://musicbrainz.org"
MUSICBRAINZ_OAUTH_URL = f"{MUSICBRAINZ_BASE_URL}/oauth2/userinfo"
OAUTH_CLIENT_ID = '''{{template "KEY" "oauth/client_id"}}'''
OAUTH_CLIENT_SECRET = '''{{template "KEY" "oauth/client_secret"}}'''
OAUTH_AUTHORIZE_URL = f"{MUSICBRAINZ_BASE_URL}/new-oauth2/authorize"
OAUTH_TOKEN_URL = f"{MUSICBRAINZ_BASE_URL}/new-oauth2/token"
OAUTH_INTROSPECTION_URL = f"{MUSICBRAINZ_BASE_URL}/new-oauth2/introspect"

LASTFM_API_URL = '''{{template "KEY" "lastfm_api_url"}}'''
LASTFM_API_KEY = '''{{template "KEY" "lastfm_api_key"}}'''
Expand Down
10 changes: 5 additions & 5 deletions docs/developers/devel-env.rst
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@ option to `register`_ your application. Fill out the form with the following dat
After entering this information, you'll have an OAuth client ID and OAuth client
secret. You'll use these for configuring ListenBrainz.

.. _MusicBrainz applications page: https://musicbrainz.org/account/applications
.. _register: https://musicbrainz.org/account/applications/register
.. _MusicBrainz applications page: https://musicbrainz.org/new-oauth2/client/list
.. _register: https://musicbrainz.org/new-oauth2/client/create


Update config.py
Expand All @@ -80,15 +80,15 @@ text editor and look for this section.
.. code-block:: yaml
# MusicBrainz OAuth
MUSICBRAINZ_CLIENT_ID = "CLIENT_ID"
MUSICBRAINZ_CLIENT_SECRET = "CLIENT_SECRET"
OAUTH_CLIENT_ID = "CLIENT_ID"
OAUTH_CLIENT_SECRET = "CLIENT_SECRET"
Update the strings with your client ID and secret. After doing this, your
ListenBrainz development environment is able to authenticate and log in from
your MusicBrainz login.

.. note::
Make sure the ``MUSICBRAINZ_CLIENT_ID`` and ``MUSICBRAINZ_CLIENT_SECRET`` parameters are set properly,
Make sure the ``OAUTH_CLIENT_ID`` and ``OAUTH_CLIENT_SECRET`` parameters are set properly,
failing to do so will result in a basic browser auth popup like the one below:
.. image:: ../images/auth-popup.png
:width: 200
Expand Down
5 changes: 4 additions & 1 deletion frontend/css/listens-page.less
Original file line number Diff line number Diff line change
Expand Up @@ -486,9 +486,12 @@
#navigation.pager {
display: flex;
flex-wrap: wrap;
& > .date-time-picker {
& > .feed-button-and-date-time-picker {
flex: 1;
order: 0;
display: flex;
align-items: center;
justify-content: center;

@media (max-width: @screen-phone) {
// on phones, put the datepicker below the arrow nav
Expand Down
9 changes: 9 additions & 0 deletions frontend/css/main.less
Original file line number Diff line number Diff line change
Expand Up @@ -410,3 +410,12 @@ pre code.hljs {
transform: rotate(90deg);
}
}

.atom-button {
width: 1.4em;
height: 1.4em;
aspect-ratio: 1;
padding: 0.25em;
margin: 0;
margin-left: 5px;
}
3 changes: 3 additions & 0 deletions frontend/css/recommendation-page.less
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,9 @@
}
.buttons {
padding: 0.7em 0;
.btn {
margin-left: 5px;
}
}
}
}
28 changes: 16 additions & 12 deletions frontend/js/src/album/AlbumPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,14 @@ export default function AlbumPage(): JSX.Element {
</div>
);
}
const artistsRadioPrompt: string =
artist.artists
?.map((a) => `artist:(${a.artist_mbid ?? a.name})`)
.join(" ") ?? `artist:(${encodeURIComponent(artist.name)})`;
const artistsRadioPromptNoSim: string =
artist.artists
?.map((a) => `artist:(${a.artist_mbid ?? a.name})::nosim`)
.join(" ") ?? `artist:(${encodeURIComponent(artist.name)})::nosim`;

return (
<div
Expand Down Expand Up @@ -315,11 +323,10 @@ export default function AlbumPage(): JSX.Element {
<Link
type="button"
className="btn btn-info"
to={`/explore/lb-radio/?prompt=artist:(${encodeURIComponent(
artistName
)})&mode=easy`}
to={`/explore/lb-radio/?prompt=${artistsRadioPrompt}&mode=easy`}
>
<FontAwesomeIcon icon={faPlayCircle} /> Artist Radio
<FontAwesomeIcon icon={faPlayCircle} /> Artist
{artist.artists?.length > 1 && "s"} Radio
</Link>
<button
type="button"
Expand All @@ -334,20 +341,17 @@ export default function AlbumPage(): JSX.Element {
<ul className="dropdown-menu">
<li>
<Link
to={`/explore/lb-radio/?prompt=artist:(${encodeURIComponent(
artistName
)})::nosim&mode=easy`}
to={`/explore/lb-radio/?prompt=${artistsRadioPrompt}&mode=easy`}
>
This artist
Artist{artist.artists?.length > 1 && "s"} radio
</Link>
</li>
<li>
<Link
to={`/explore/lb-radio/?prompt=artist:(${encodeURIComponent(
artistName
)})&mode=easy`}
to={`/explore/lb-radio/?prompt=${artistsRadioPromptNoSim}&mode=easy`}
>
Similar artists
{artist.artists?.length > 1 ? "These artists" : "This artist"}{" "}
only
</Link>
</li>
{Boolean(filteredTags?.length) && (
Expand Down
16 changes: 5 additions & 11 deletions frontend/js/src/artist/ArtistPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -354,9 +354,7 @@ export default function ArtistPage(): JSX.Element {
<Link
type="button"
className="btn btn-info"
to={`/explore/lb-radio/?prompt=artist:(${encodeURIComponent(
artist?.name
)})&mode=easy`}
to={`/explore/lb-radio/?prompt=artist:(${artistMBID})&mode=easy`}
>
<FontAwesomeIcon icon={faPlayCircle} /> Radio
</Link>
Expand All @@ -373,20 +371,16 @@ export default function ArtistPage(): JSX.Element {
<ul className="dropdown-menu">
<li>
<Link
to={`/explore/lb-radio/?prompt=artist:(${encodeURIComponent(
artist?.name
)})::nosim&mode=easy`}
to={`/explore/lb-radio/?prompt=artist:(${artistMBID})&mode=easy`}
>
This artist
Artist radio
</Link>
</li>
<li>
<Link
to={`/explore/lb-radio/?prompt=artist:(${encodeURIComponent(
artist?.name
)})&mode=easy`}
to={`/explore/lb-radio/?prompt=artist:(${artistMBID})::nosim&mode=easy`}
>
Similar artists
This artist only
</Link>
</li>
{Boolean(filteredTags?.length) && (
Expand Down
195 changes: 195 additions & 0 deletions frontend/js/src/components/SyndicationFeedModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
import NiceModal, { useModal } from "@ebay/nice-modal-react";
import * as React from "react";
import { useState } from "react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faCircleQuestion,
faRssSquare,
} from "@fortawesome/free-solid-svg-icons";
import Tooltip from "react-tooltip";

type BaseOptionProps = {
label: string;
key: string;
tooltip?: string;
};

type DropdownOption = BaseOptionProps & {
type: "dropdown";
values: { id: string; value: string; displayValue?: string }[];
defaultIndex?: number;
};

type NumberOption = BaseOptionProps & {
type: "number";
min?: number;
max?: number;
defaultValue: number;
};

export type SyndicationFeedModalProps = {
feedTitle: string;
options: (DropdownOption | NumberOption)[];
baseUrl: string;
};

type SelectedOptions = {
[key: string]: string;
};

export default NiceModal.create((props: SyndicationFeedModalProps) => {
const modal = useModal();
const closeModal = React.useCallback(() => {
modal.hide();
document?.body?.classList?.remove("modal-open");
setTimeout(modal.remove, 200);
}, [modal]);

const { feedTitle, options, baseUrl } = props;

const initialSelectedOptions: SelectedOptions = options.reduce(
(acc: SelectedOptions, option) => {
if (option.type === "number") {
acc[option.key] = option.defaultValue.toString();
} else {
const defaultIndex = option.defaultIndex ?? 0;
acc[option.key] = option.values[defaultIndex].value;
}
return acc;
},
{}
);

const [selectedOptions, setSelectedOptions] = React.useState<SelectedOptions>(
initialSelectedOptions
);

const [copyButtonText, setCopyButtonText] = useState("Copy");

const handleOptionChange = (key: string, value: string) => {
setSelectedOptions((prevSelectedOptions) => ({
...prevSelectedOptions,
[key]: value,
}));
};

const buildLink = () => {
const queryParams = new URLSearchParams(selectedOptions).toString();
return queryParams ? `${baseUrl}?${queryParams}` : baseUrl;
};

const handleCopyClick = () => {
navigator.clipboard.writeText(buildLink()).then(() => {
setCopyButtonText("Copied");

setTimeout(() => {
setCopyButtonText("Copy");
}, 2000);
});
};

return (
<div
id="SyndicationFeedModal"
className={`modal fade ${modal.visible ? "in" : ""}`}
tabIndex={-1}
role="dialog"
aria-labelledby="syndicationFeedModalLabel"
data-backdrop="static"
>
<div
className="modal-dialog"
role="document"
style={{ maxWidth: "800px" }}
>
<div className="modal-content">
<div className="modal-header">
<button
type="button"
className="close"
data-dismiss="modal"
aria-label="Close"
onClick={closeModal}
>
<span aria-hidden="true">&times;</span>
</button>
<h4 className="modal-title" id="syndicationFeedModalLabel">
<FontAwesomeIcon icon={faRssSquare} />
&nbsp; Syndication feed: {feedTitle}
</h4>
</div>

<div className="modal-body">
{options.map((option) => (
<div className="form-group" key={option.key}>
<label htmlFor={option.key}>
{option.label}
{option.tooltip && (
<>
&nbsp;
<FontAwesomeIcon
icon={faCircleQuestion}
data-tip={option.tooltip}
/>
<Tooltip place="right" type="dark" effect="solid" />
</>
)}
</label>
{option.type === "dropdown" && (
<select
className="form-control"
id={option.key}
onChange={(e) =>
handleOptionChange(option.key, e.target.value)
}
defaultValue={selectedOptions[option.key]}
>
{option.values.map((value) => (
<option key={value.id} value={value.value}>
{value.displayValue || value.value}
</option>
))}
</select>
)}
{option.type === "number" && (
<input
type="number"
className="form-control"
id={option.key}
value={selectedOptions[option.key]}
min={option.min}
max={option.max}
onChange={(e) =>
handleOptionChange(option.key, e.target.value)
}
/>
)}
</div>
))}
<div className="form-group">
<label htmlFor="feedLink">Subscription URL</label>
<div style={{ display: "flex", alignItems: "center" }}>
<input
type="text"
className="form-control"
id="feedLink"
value={buildLink()}
readOnly
style={{ marginRight: "10px", flexGrow: 1 }}
/>
<button
type="button"
className="btn btn-info btn-sm"
onClick={handleCopyClick}
style={{ minWidth: "100px", height: "34px" }}
>
{copyButtonText}
</button>
</div>
</div>
</div>
</div>
</div>
</div>
);
});
Loading

0 comments on commit cd2eebb

Please sign in to comment.