From 65c8808d24ec8190484a7b4f4a103dfe48b33c46 Mon Sep 17 00:00:00 2001 From: anshg1214 Date: Thu, 8 Feb 2024 10:16:34 +0000 Subject: [PATCH 001/104] feat: Add About pages --- frontend/js/src/about/About.tsx | 113 +++++++ frontend/js/src/about/add-data/AddData.tsx | 307 ++++++++++++++++++ .../about/current-status/CurrentStatus.tsx | 119 +++++++ frontend/js/src/about/data/Data.tsx | 50 +++ frontend/js/src/about/layout.tsx | 76 +++++ frontend/js/src/about/routes/index.tsx | 44 +++ .../about/terms-of-service/TermsOfService.tsx | 88 +++++ frontend/js/src/index.tsx | 49 +++ .../webserver/templates/index/about.html | 99 ------ .../webserver/templates/index/add-data.html | 84 ----- .../templates/index/current-status.html | 84 ----- .../webserver/templates/index/data.html | 24 -- .../templates/index/terms-of-service.html | 38 --- 13 files changed, 846 insertions(+), 329 deletions(-) create mode 100644 frontend/js/src/about/About.tsx create mode 100644 frontend/js/src/about/add-data/AddData.tsx create mode 100644 frontend/js/src/about/current-status/CurrentStatus.tsx create mode 100644 frontend/js/src/about/data/Data.tsx create mode 100644 frontend/js/src/about/layout.tsx create mode 100644 frontend/js/src/about/routes/index.tsx create mode 100644 frontend/js/src/about/terms-of-service/TermsOfService.tsx create mode 100644 frontend/js/src/index.tsx delete mode 100644 listenbrainz/webserver/templates/index/about.html delete mode 100644 listenbrainz/webserver/templates/index/add-data.html delete mode 100644 listenbrainz/webserver/templates/index/current-status.html delete mode 100644 listenbrainz/webserver/templates/index/data.html delete mode 100644 listenbrainz/webserver/templates/index/terms-of-service.html diff --git a/frontend/js/src/about/About.tsx b/frontend/js/src/about/About.tsx new file mode 100644 index 0000000000..20bbe9d290 --- /dev/null +++ b/frontend/js/src/about/About.tsx @@ -0,0 +1,113 @@ +import * as React from "react"; + +export default function About() { + return ( + <> +

Goals

+

The ListenBrainz project has a number of goals:

+
    +
  1. + A public store of your listen history. We feel that a listen + history has a tremendous amount of personal value and in aggregate has + a huge amount of value for developers who wish to create better music + technologies, like recommendation systems. +
  2. +
  3. + A permanent store of your listen history. MetaBrainz, the + non-profit that runs MusicBrainz{" "} + and ListenBrainz has a long history of curating and making data + available to the public in a useful and meaningful manner. We promise + to safeguard your listen history permanently. +
  4. +
  5. + + To make data dumps of this listen history available for download. + {" "} + We want everyone who is interested in this data to have access to the + data and to use it in any manner they wish. +
  6. +
  7. + To share listen histories in a distributed fashion. We plan to + allow anyone to connect to ListenBrainz and to tap into a live feed of + listen data as we receive it. We hope that Last.fm will work with us + to make an interconnection with Last.fm possible. We welcome anyone + scrobbling to us and we plan to share the listens shared with us to + anyone else who wants them. We envision smaller music communities with + a specific focus to install their own ListenBrainz server to collect + listen data for their specific focus. We hope that these smaller + communities will also share their data in the same manner in which we + share our data. +
  8. +
+

Anti-goals

+

+ The project also has a number of anti-goals (things it doesn't try + to be): +

+
    +
  1. + A store for people's private listen history. The point of + this project is to build a public, shareable store of listen data. As + we build out our sharing features, building a private listen store + will become possible, but that is not part of our goals. +
  2. +
  3. + A closed platform. We aim to make everything open and to + encourage a community of sharing and participation. +
  4. +
+

Roadmap

+ We've put together a very rough roadmap for this project: +

Short term

+ +

Medium term

+ +

Long term

+ +
+

+ If you have any ideas that should be on our roadmap, please{" "} + let us know! +

+

Contributing to ListenBrainz

+

Donating

+

+ Listenbrainz is a free open source project that is not run for profit. + If you would like to help the project out financially, consider{" "} + donating to the MetaBrainz + Foundation. +

+

Developers

+

+ ListenBrainz is in its infancy and we need a lot of help to implement + more features and to debug the existing features. If you feel like + helping out and have experience with Python, Postgres and Redis, + we'd love some help. +

+

+ Have a look at the{" "} + + GitHub repository + {" "} + for this project to get started. You may also consider heading to our + IRC channel #metabrainz on irc.libera.chat and asking people there what + should be worked on next. Finally, we also have a bug tracker that keeps + track of our{" "} + current issues. +

+ + ); +} diff --git a/frontend/js/src/about/add-data/AddData.tsx b/frontend/js/src/about/add-data/AddData.tsx new file mode 100644 index 0000000000..017d88d560 --- /dev/null +++ b/frontend/js/src/about/add-data/AddData.tsx @@ -0,0 +1,307 @@ +import * as React from "react"; + +export default function AddData() { + return ( + <> +

Adding your data to Listenbrainz

+

Submitting Listens

+

+ There are many ways to submit your listening history to ListenBrainz: +

+

Music players

+ + +

Standalone programs/streaming servers

+ + +

Browser extensions

+ + +

Mobile devices

+ + +

Submitting via Spotify

+

ListenBrainz can automatically record listens from Spotify.

+

+ Importing the same listens from two different sources such as Last.FM + and Spotify may cause the creation of duplicates in your listen history. + If you opt into our automatic Spotify import, you may notice + duplications in the last 50 listens on Spotify.This is a temporary issue + while we find better ways to deduplicate listens. +

+

+ + Connect your Spotify account to ListenBrainz. + +

+ +

Playlist submissions and tools

+

+ Playlists can also be submitted and stored on your ListenBrainz account. +

+

Tools

+ + +

For advanced users

+

+ Developers are able to submit their listens to Listenbrainz using the + Listenbrainz API. Information on how to do this can be found in the{" "} + API docs +

+ + ); +} diff --git a/frontend/js/src/about/current-status/CurrentStatus.tsx b/frontend/js/src/about/current-status/CurrentStatus.tsx new file mode 100644 index 0000000000..5d276feb14 --- /dev/null +++ b/frontend/js/src/about/current-status/CurrentStatus.tsx @@ -0,0 +1,119 @@ +import React from "react"; +import { useLoaderData } from "react-router-dom"; + +type CurrentStatusLoaderData = { + listenCount: number; + listenCountsPerDay: { + date: string; + listenCount: number; + label: string; + }[]; + userCount: number; + load: string; +}; + +export default function CurrentStatus() { + const { + userCount, + listenCount, + listenCountsPerDay, + load, + } = useLoaderData() as CurrentStatusLoaderData; + + return ( + <> +

Current status

+ +
+
+

ListenBrainz Stats

+ + + + + + + + + {userCount && ( + + + + + )} + {listenCount && ( + + + + + )} + {listenCountsPerDay && + listenCountsPerDay.map((data, index) => ( + + + + + ))} + +
DescriptionNumber
Number of users{userCount}
Number of listens{listenCount}
+ Number of listens submitted {data.label} ({data.date}) + {data.listenCount}
+ +

+ If you are curious about the state of our Listen ingestion + pipelines, you can create yourself a free account on our{" "} + + infrastructure statistics site + + . In particular, the{" "} + + RabbitMQ ListenBrainz view + {" "} + shows how many listens we are currently processing, and the number + of incoming listens currently queued for processing. +

+ +

+ Something isn't updating? Stay calm and check the{" "} + + Expected Data Update Intervals + {" "} + doc. +

+ +

load average

+ +

Current server load average

+
{load}
+
+ +
+

+ Selfie +

+ +

+ Our server doesn't have a selfie. :(
+ Have a monkey selfie instead! +

+
+
+ + ); +} + +export async function CurrentStatusLoader({ request }: { request: Request }) { + const response = await fetch(request.url, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + }); + const data = await response.json(); + return data; +} diff --git a/frontend/js/src/about/data/Data.tsx b/frontend/js/src/about/data/Data.tsx new file mode 100644 index 0000000000..208af3428d --- /dev/null +++ b/frontend/js/src/about/data/Data.tsx @@ -0,0 +1,50 @@ +import * as React from "react"; + +export default function Data() { + return ( + <> +

Data Downloads

+

+ You can download the ListenBrainz data snapshots from the following + sites: +

+ +

Available dump types

+

+ fullexport: Dumps of the full ListenBrainz database, updated + every two weeks on or about the 1st and the 15th of each month. +
+ incremental: Daily incremental dumps based on the most recent + fullexport dump. +
+ spark: A version of the fullexport dump suitable for importing + directly into{" "} + + our spark infrastructure + + . +

+ + ); +} diff --git a/frontend/js/src/about/layout.tsx b/frontend/js/src/about/layout.tsx new file mode 100644 index 0000000000..2b135b88e5 --- /dev/null +++ b/frontend/js/src/about/layout.tsx @@ -0,0 +1,76 @@ +import { faArrowUpRightFromSquare } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import * as React from "react"; +import { Link, NavLink, Outlet, useLocation } from "react-router-dom"; + +type Section = { + to: string; + label: string; +}; + +const sections: Section[] = [ + { to: "about/", label: "About" }, + { to: "current-status/", label: "Site status" }, + { to: "add-data/", label: "Submitting data" }, + { to: "data/", label: "Using our data" }, + { to: "terms-of-service/", label: "Terms of service" }, +]; + +function AboutLayout() { + const location = useLocation(); + const [activeLabel, setActiveLabel] = React.useState(""); + + const getActiveLabel = React.useCallback((path: string) => { + const newActiveLabel = sections.find((link) => path.includes(link.to)) + ?.label; + return newActiveLabel; + }, []); + + React.useEffect(() => { + const newActiveLabel = getActiveLabel(location.pathname); + setActiveLabel(newActiveLabel || ""); + }, [location.pathname, getActiveLabel]); + + return ( + <> +
+
    +
  1. + About +
  2. + {activeLabel && activeLabel !== "About" && ( +
  3. {activeLabel}
  4. + )} +
+
+ +
+
+ ListenBrainz +
    + {sections.map((link) => ( +
  • + {link.label} +
  • + ))} +
  • + + API docs{" "} + + +
  • +
+
+
+ +
+
+ + ); +} + +export default AboutLayout; diff --git a/frontend/js/src/about/routes/index.tsx b/frontend/js/src/about/routes/index.tsx new file mode 100644 index 0000000000..aa39fca51d --- /dev/null +++ b/frontend/js/src/about/routes/index.tsx @@ -0,0 +1,44 @@ +import * as React from "react"; +import AboutLayout from "../layout"; +import About from "../About"; +import AddData from "../add-data/AddData"; +import CurrentStatus, { + CurrentStatusLoader, +} from "../current-status/CurrentStatus"; +import Data from "../data/Data"; +import TermsOfService from "../terms-of-service/TermsOfService"; + +const getAboutRoutes = () => { + const routes = [ + { + path: "/", + element: , + children: [ + { + path: "about/", + element: , + }, + { + path: "add-data/", + element: , + }, + { + path: "current-status/", + loader: CurrentStatusLoader, + element: , + }, + { + path: "data/", + element: , + }, + { + path: "terms-of-service/", + element: , + }, + ], + }, + ]; + return routes; +}; + +export default getAboutRoutes; diff --git a/frontend/js/src/about/terms-of-service/TermsOfService.tsx b/frontend/js/src/about/terms-of-service/TermsOfService.tsx new file mode 100644 index 0000000000..74d7795263 --- /dev/null +++ b/frontend/js/src/about/terms-of-service/TermsOfService.tsx @@ -0,0 +1,88 @@ +import * as React from "react"; + +export default function TermsOfService() { + return ( + <> +

Terms of Service

+ +

+ As one of the projects of the{" "} + MetaBrainz Foundation, + ListenBrainz' terms of service are defined by the social contract + and privacy policies of the Foundation. You will find these detailed on + the MetaBrainz website: +

+ + +

Third party resources

+

+ Additionally, we use the following third party resources to enable you + to play music on ListenBrainz: +

+ +

+ We use the YouTube API Services to search for and play music directly on + ListenBrainz. By using ListenBrainz to play music, you agree to be bound + by the YouTube Terms of Service. See their ToS and privacy policy below: +

+ + + ); +} diff --git a/frontend/js/src/index.tsx b/frontend/js/src/index.tsx new file mode 100644 index 0000000000..e75d16a548 --- /dev/null +++ b/frontend/js/src/index.tsx @@ -0,0 +1,49 @@ +import * as React from "react"; + +import NiceModal from "@ebay/nice-modal-react"; +import * as Sentry from "@sentry/react"; +import { Integrations } from "@sentry/tracing"; +import { createRoot } from "react-dom/client"; +import { createBrowserRouter, RouterProvider } from "react-router-dom"; +import { ToastContainer } from "react-toastify"; +import ErrorBoundary from "./utils/ErrorBoundary"; +import GlobalAppContext from "./utils/GlobalAppContext"; +import { getPageProps } from "./utils/utils"; +import getRoutes from "./routes/routes"; + +document.addEventListener("DOMContentLoaded", async () => { + const { domContainer, globalAppContext, sentryProps } = await getPageProps(); + const { sentry_dsn, sentry_traces_sample_rate } = sentryProps; + + if (sentry_dsn) { + Sentry.init({ + dsn: sentry_dsn, + integrations: [new Integrations.BrowserTracing()], + tracesSampleRate: sentry_traces_sample_rate, + }); + } + + const routes = getRoutes(); + const router = createBrowserRouter(routes); + + const renderRoot = createRoot(domContainer!); + renderRoot.render( + + + + + + + + + ); +}); diff --git a/listenbrainz/webserver/templates/index/about.html b/listenbrainz/webserver/templates/index/about.html deleted file mode 100644 index 1b0e2b7789..0000000000 --- a/listenbrainz/webserver/templates/index/about.html +++ /dev/null @@ -1,99 +0,0 @@ -{%- extends 'index/base-about.html' -%} -{%- block about_content -%} -

Goals

- -

The ListenBrainz project has a number of goals:

-
    -
  1. - A public store of your listen history. We feel that a listen history has a tremendous - amount of personal value and in aggregate has a huge amount of value for developers who wish - to create better music technologies, like recommendation systems. -
  2. -
  3. - A permanent store of your listen history. MetaBrainz, the non-profit that runs - MusicBrainz and ListenBrainz has a long - history of curating and making data available to the public in a useful and meaningful manner. We promise - to safeguard your listen history permanently. -
  4. -
  5. - To make data dumps of this listen history available for download. We want everyone who is interested - in this data to have access to the data and to use it in any manner they wish. -
  6. -
  7. - To share listen histories in a distributed fashion. We plan to allow anyone to connect to ListenBrainz - and to tap into a live feed of listen data as we receive it. We hope that Last.fm will work with us - to make an interconnection with Last.fm possible. We welcome anyone scrobbling to us and we plan to share the listens - shared with us to anyone else who wants them. We envision smaller music communities with a specific focus - to install their own ListenBrainz server to collect listen data for their specific focus. We hope that - these smaller communities will also share their data in the same manner in which we share our data. -
  8. -
- -

Anti-goals

- -

The project also has a number of anti-goals (things it doesn't try to be):

-
    -
  1. - A store for people's private listen history. The point of this project is to build a public, shareable - store of listen data. As we build out our sharing features, building a private listen store will become - possible, but that is not part of our goals. -
  2. -
  3. - A closed platform. We aim to make everything open and to encourage a community of sharing and participation. -
  4. -
-

Roadmap

- -We've put together a very rough roadmap for this project: - -

Short term

- -
    -
  • - Work to improve and extend the user data graphing features. -
  • -
- -

Medium term

- -
    -
  • - Start working on an open source recommendation engine using data from ListenBrainz, - AcousticBrainz and MusicBrainz. -
  • -
- -

Long term

-
    -
  • - Total world domination. What other goals are open source projects allowed to have? -
  • -
- -
- -

- If you have any ideas that should be on our roadmap, please let us know! -

-

Contributing to ListenBrainz

- -

Donating

-

- Listenbrainz is a free open source project that is not run for profit. If you would like to help the project out - financially, consider donating to the MetaBrainz Foundation. -

- -

Developers

-

- ListenBrainz is in its infancy and we need a lot of help to implement more features and to debug the existing - features. If you feel like helping out and have experience with Python, Postgres and Redis, - we'd love some help. -

-

- Have a look at the GitHub repository for this - project to get started. You may also consider heading to our IRC channel #metabrainz on irc.libera.chat and - asking people there what should be worked on next. Finally, we also have a bug tracker that keeps track of our - current issues. -

- -{%- endblock -%} diff --git a/listenbrainz/webserver/templates/index/add-data.html b/listenbrainz/webserver/templates/index/add-data.html deleted file mode 100644 index 5380eb2913..0000000000 --- a/listenbrainz/webserver/templates/index/add-data.html +++ /dev/null @@ -1,84 +0,0 @@ -{%- extends 'index/base-about.html' -%} -{%- block title -%}Adding Data - ListenBrainz{%- endblock -%} -{%- block about_content -%} -

Adding your data to Listenbrainz

- {# Listen submission information #} -

Submitting Listens

-

- There are many ways to submit your listening history to ListenBrainz: -

-

Music players

- - -

Standalone programs/streaming servers

-
    -
  • Rescrobbled, a universal Linux scrobbler for MPRIS enabled players
  • -
  • mpris-scrobbler, a minimalistic unix scrobbler for MPRIS enabled players
  • -
  • Multi-scrobbler, a powerful javascript server application for all platforms, with support for many sources
  • -
  • SmashTunes, a Mac menu bar utility for displaying the current track. Submits Apple Music and Spotify listens
  • -
  • applescript-listenbrainz, an applescript service to submit Apple Music listens
  • -
  • Eavesdrop.FM, submits Plex music listening data to ListenBrainz
  • -
  • AudioStreamerScrobbler, submit listens from hardware audiostreamers (Bluesound/BluOS, MusicCast, HEOS)
  • -
  • Funkwhale, a decentralized music sharing and listening platform with built-in support for ListenBrainz
  • -
  • Jellyfin, a free software media streaming system: jellyfin-plugin-listenbrainz
  • -
  • gonic, a free software Subsonic-compatible music server, has built-in support for ListenBrainz
  • -
  • Navidrome, a free software music server compatible with Subsonic/Airsonic
  • -
  • Kodi, a free and open source media center: ListenBrainz add-on
  • -
  • Airsonic-Advanced, a free, web-based media streamer
  • -
- -

Browser extensions

-
    -
  • Web Scrobbler, an extension for Firefox and Chrome/Chromium-based browsers
  • -
- -

Mobile devices

- - -

Submitting via Spotify

-

- ListenBrainz can automatically record listens from Spotify. -

-

- Importing the same listens from two different sources such as Last.FM and Spotify may cause the creation - of duplicates in your listen history. If you opt into our automatic Spotify import, you may notice duplications in - the last 50 listens on Spotify.This is a temporary issue while we find better ways to deduplicate listens. -

-

- Connect your Spotify account to ListenBrainz. -

- -

Playlist submissions and tools

-

- Playlists can also be submitted and stored on your ListenBrainz account. -

-

Tools

- - -

For advanced users

-

- Developers are able to submit their listens to Listenbrainz using the Listenbrainz API. Information on how to do this - can be found in the API docs -

-{%- endblock -%} diff --git a/listenbrainz/webserver/templates/index/current-status.html b/listenbrainz/webserver/templates/index/current-status.html deleted file mode 100644 index b23617610e..0000000000 --- a/listenbrainz/webserver/templates/index/current-status.html +++ /dev/null @@ -1,84 +0,0 @@ -{%- extends 'index/base-about.html' -%} -{%- block title -%}Status - ListenBrainz{%- endblock -%} -{%- block about_content -%} - -

Current status

- -
- -
-

ListenBrainz Stats

- - - - - - - - - {% if user_count %} - - - - - {% endif %} - {% if listen_count %} - - - - - {% endif %} - {% if listen_counts_per_day %} - {% for data in listen_counts_per_day %} - - - - - {% endfor %} - {% endif %} - -
DescriptionNumber
Number of users{{ user_count }}
Number of listens{{ listen_count }}
Number of listens submitted {{ data['label'] }} ({{ data['date'] }}){{ data['listen_count'] }}
- -

- If you are curious about the state of our Listen ingestion pipelines, you can - create yourself a free account on our infrastructure - statistics site. In particular, - the - - RabbitMQ ListenBrainz view shows how many listens we are currently processing, and the number of incoming listens currently queued for processing. -

- -

- Something isn't updating? Stay calm and check the Expected Data Update Intervals doc. -

- -

load average

- -

- Current server load average -

-
- {{ load }} -
- -
- -
- -

- -

- -

- Our server doesn't have a selfie. :(
- Have a monkey selfie instead! -

-

- -
- -
- -{%- endblock -%} diff --git a/listenbrainz/webserver/templates/index/data.html b/listenbrainz/webserver/templates/index/data.html deleted file mode 100644 index 9a2efa6bc8..0000000000 --- a/listenbrainz/webserver/templates/index/data.html +++ /dev/null @@ -1,24 +0,0 @@ -{%- extends 'index/base-about.html' -%} -{%- block title -%}Downloads - ListenBrainz{%- endblock -%} -{%- block about_content -%} - -

Data Downloads

-

- You can download the ListenBrainz data snapshots from the following sites: -

- -

Available dump types

-

- fullexport: Dumps of the full ListenBrainz database, updated every two weeks on or about the 1st and the 15th of each month.
- incremental: Daily incremental dumps based on the most recent fullexport dump.
- spark: A version of the fullexport dump suitable for importing directly into - our spark infrastructure. -

- - -{%- endblock -%} diff --git a/listenbrainz/webserver/templates/index/terms-of-service.html b/listenbrainz/webserver/templates/index/terms-of-service.html deleted file mode 100644 index de1727cc1d..0000000000 --- a/listenbrainz/webserver/templates/index/terms-of-service.html +++ /dev/null @@ -1,38 +0,0 @@ -{%- extends 'index/base-about.html' -%} -{%- block title -%}Terms of Service - ListenBrainz{%- endblock -%} -{%- block about_content -%} -

Terms of Service

- -

As one of the projects of the MetaBrainz Foundation, - ListenBrainz' terms of service are defined by the social contract and privacy policies of the Foundation. - You will find these detailed on the MetaBrainz website: -

- - -

Third party resources

-

- Additionally, we use the following third party resources to enable you to play music on ListenBrainz: -

- -

- We use the YouTube API Services to search for and play music directly on ListenBrainz. - By using ListenBrainz to play music, you agree to be bound by the YouTube Terms of Service. - See their ToS and privacy policy below: -

- - -{%- endblock -%} From dcc88e235413663ec55b1d002c0d7cda62926fd6 Mon Sep 17 00:00:00 2001 From: anshg1214 Date: Thu, 8 Feb 2024 10:19:11 +0000 Subject: [PATCH 002/104] feat: Add More pages to SPA --- frontend/js/src/import-data/ImportData.tsx | 27 +++++++++ frontend/js/src/lastfm-proxy/LastfmProxy.tsx | 57 +++++++++++++++++++ .../js/src/listens-offline/ListensOffline.tsx | 25 ++++++++ frontend/js/src/messybrainz/MessyBrainz.tsx | 26 +++++++++ .../MusicBrainzOffline.tsx | 22 +++++++ .../templates/index/import-data.html | 18 ------ .../templates/index/lastfm-proxy.html | 38 ------------- .../templates/index/listens_offline.html | 22 ------- .../templates/index/messybrainz.html | 18 ------ .../templates/index/musicbrainz-offline.html | 21 ------- .../templates/index/search-users.html | 51 ----------------- 11 files changed, 157 insertions(+), 168 deletions(-) create mode 100644 frontend/js/src/import-data/ImportData.tsx create mode 100644 frontend/js/src/lastfm-proxy/LastfmProxy.tsx create mode 100644 frontend/js/src/listens-offline/ListensOffline.tsx create mode 100644 frontend/js/src/messybrainz/MessyBrainz.tsx create mode 100644 frontend/js/src/musicbrainz-offline/MusicBrainzOffline.tsx delete mode 100644 listenbrainz/webserver/templates/index/import-data.html delete mode 100644 listenbrainz/webserver/templates/index/lastfm-proxy.html delete mode 100644 listenbrainz/webserver/templates/index/listens_offline.html delete mode 100644 listenbrainz/webserver/templates/index/messybrainz.html delete mode 100644 listenbrainz/webserver/templates/index/musicbrainz-offline.html delete mode 100644 listenbrainz/webserver/templates/index/search-users.html diff --git a/frontend/js/src/import-data/ImportData.tsx b/frontend/js/src/import-data/ImportData.tsx new file mode 100644 index 0000000000..14cba1eb2c --- /dev/null +++ b/frontend/js/src/import-data/ImportData.tsx @@ -0,0 +1,27 @@ +import * as React from "react"; + +export default function ImportData() { + return ( + <> +

Importing your data to Listenbrainz

+

Importing data from Last.fm

+ +

+ We encourage Last.fm users to save their listen histories to + ListenBrainz. +

+

+ To help us test this service, please import your listen history from + Last.fm. To proceed, you will need a MusicBrainz account. +

+ + + ); +} diff --git a/frontend/js/src/lastfm-proxy/LastfmProxy.tsx b/frontend/js/src/lastfm-proxy/LastfmProxy.tsx new file mode 100644 index 0000000000..93e5b5ee0c --- /dev/null +++ b/frontend/js/src/lastfm-proxy/LastfmProxy.tsx @@ -0,0 +1,57 @@ +import * as React from "react"; + +export default function LastfmProxy() { + return ( + <> +

Proxy Last.FM APIs

+ +

+ ListenBrainz supports the Last.FM API and v1.2 of the AudioScrobbler API + (used by clients like VLC and Spotify). Existing Last.FM clients can be + pointed to the{" "} + ListenBrainz proxy URL and + they should submit listens to ListenBrainz instead of Last.FM. +

+ +

Instructions

+ +

Last.FM API

+

+ Clients supporting the current Last.FM API (such as Audacious) should be + able to submit listens to ListenBrainz after some configuration as + instructed in{" "} + + the API Compatible README + + . +

+ +

AudioScrobbler API v1.2

+ +

+ Clients supporting the old version of the{" "} + + AudioScrobbler API + {" "} + (such as VLC and Spotify) can be configured to work with ListenBrainz by + making the client point to{" "} + + http://proxy.listenbrainz.org + {" "} + and using MusicBrainz ID as username and the{" "} + LB auth token as password. +

+ +

+ If the software you are using doesn't support changing where the + client submits info (like Spotify), you can edit your /etc/hosts file as + follows: +

+
+        {"       "}138.201.169.196{"    "}post.audioscrobbler.com
+        
+ {" "}138.201.169.196{" "}post2.audioscrobbler.com +
+ + ); +} diff --git a/frontend/js/src/listens-offline/ListensOffline.tsx b/frontend/js/src/listens-offline/ListensOffline.tsx new file mode 100644 index 0000000000..1ac30d817f --- /dev/null +++ b/frontend/js/src/listens-offline/ListensOffline.tsx @@ -0,0 +1,25 @@ +import * as React from "react"; + +export default function ListensOffline() { + return ( + <> +

Listens currently unavailable

+ +

+ The database that contains listens for our users is currently offline + for maintenance. Please try again in a few minutes. +

+ +

+ Please note: You may continue to submit listens during this time. + We'll save them once our database is available again. +

+ +

+ You may find out more about the current status of our services by + checking our Twitter feed + . +

+ + ); +} diff --git a/frontend/js/src/messybrainz/MessyBrainz.tsx b/frontend/js/src/messybrainz/MessyBrainz.tsx new file mode 100644 index 0000000000..0344af474d --- /dev/null +++ b/frontend/js/src/messybrainz/MessyBrainz.tsx @@ -0,0 +1,26 @@ +import * as React from "react"; + +export default function MessyBrainz() { + return ( + <> +
+ MessyBrainz +
+

MessyBrainz

+

+ MessyBrainz is a MetaBrainz project + to support unclean metadata. While{" "} + MusicBrainz is designed to link + clean metadata to stable identifiers, there is a need to identify + unclean or misspelled data as well. MessyBrainz provides identifiers to + unclean metadata, and where possible, links it to stable MusicBrainz + identifiers. +

+

+ MessyBrainz is currently used in support of ListenBrainz. Submission to + MessyBrainz is restricted, however the resulting data will be made + freely available. +

+ + ); +} diff --git a/frontend/js/src/musicbrainz-offline/MusicBrainzOffline.tsx b/frontend/js/src/musicbrainz-offline/MusicBrainzOffline.tsx new file mode 100644 index 0000000000..0ef44beb63 --- /dev/null +++ b/frontend/js/src/musicbrainz-offline/MusicBrainzOffline.tsx @@ -0,0 +1,22 @@ +import * as React from "react"; + +export default function MusicBrainzOffline() { + return ( + <> +

Login currently unavailable

+ +

+ ListenBrainz login and sign up is currently unavailable due to database + maintenance. Please try again in a few minutes. +

+ +

Please note: You may continue to submit listens during this time.

+ +

+ You may find out more about the current status of our services by + checking our Twitter feed + . +

+ + ); +} diff --git a/listenbrainz/webserver/templates/index/import-data.html b/listenbrainz/webserver/templates/index/import-data.html deleted file mode 100644 index bef3adc4a6..0000000000 --- a/listenbrainz/webserver/templates/index/import-data.html +++ /dev/null @@ -1,18 +0,0 @@ -{%- extends 'base.html' -%} -{%- block title -%}Importing Data - ListenBrainz{%- endblock -%} -{%- block content -%} -

Importing your data to Listenbrainz

- {# Last.fm import information #} -

Importing data from Last.fm

- -

- We encourage Last.fm users to save their listen histories to ListenBrainz. -

-

- To help us test this service, please import your listen history from Last.fm. To proceed, you will need a - MusicBrainz account. -

- -{%- endblock -%} diff --git a/listenbrainz/webserver/templates/index/lastfm-proxy.html b/listenbrainz/webserver/templates/index/lastfm-proxy.html deleted file mode 100644 index af8bac0643..0000000000 --- a/listenbrainz/webserver/templates/index/lastfm-proxy.html +++ /dev/null @@ -1,38 +0,0 @@ -{%- extends 'base.html' -%} -{%- block title -%}Proxy Last.FM APIs - ListenBrainz{%- endblock -%} -{%- block content -%} -

Proxy Last.FM APIs

- -

ListenBrainz supports the Last.FM API and v1.2 of the AudioScrobbler API (used by clients like VLC and Spotify). Existing Last.FM - clients can be pointed to the ListenBrainz proxy URL and they should submit listens to - ListenBrainz instead of Last.FM. -

- - -

Instructions

- -

Last.FM API

-

- Clients supporting the current Last.FM API (such as Audacious) should be able to submit listens to ListenBrainz after some - configuration as instructed in the API Compatible README. -

- -

AudioScrobbler API v1.2

- -

- Clients supporting the old version of the - AudioScrobbler API (such as VLC and Spotify) can be configured to work with ListenBrainz by making the client point - to http://proxy.listenbrainz.org and using MusicBrainz ID as username and the - LB auth token as password. -

- -

- If the software you are using doesn't support changing where the client submits info (like Spotify), you can edit your - /etc/hosts file as follows: -

-
-       138.201.169.196    post.audioscrobbler.com
-       138.201.169.196    post2.audioscrobbler.com
-    
- -{%- endblock -%} diff --git a/listenbrainz/webserver/templates/index/listens_offline.html b/listenbrainz/webserver/templates/index/listens_offline.html deleted file mode 100644 index dc675b39a4..0000000000 --- a/listenbrainz/webserver/templates/index/listens_offline.html +++ /dev/null @@ -1,22 +0,0 @@ -{%- extends 'base.html' -%} -{%- block title -%}Listens currently unavailable - ListenBrainz{%- endblock -%} -{%- block content -%} -

Listens currently unavailable

- -

- The database that contains listens for our users is currently offline - for maintenance. Please try again in a few minutes. -

- -

- Please note: You may continue to submit listens during this time. We'll - save them once our database is available again. -

- -

- You may find out more about the current status of our services by - checking our Twitter - feed. -

- -{%- endblock -%} diff --git a/listenbrainz/webserver/templates/index/messybrainz.html b/listenbrainz/webserver/templates/index/messybrainz.html deleted file mode 100644 index 8310dcda09..0000000000 --- a/listenbrainz/webserver/templates/index/messybrainz.html +++ /dev/null @@ -1,18 +0,0 @@ -{%- extends 'base.html' -%} -{%- block title -%}MessyBrainz - ListenBrainz{%- endblock -%} -{%- block content -%} -
- MessyBrainz -
-

MessyBrainz

-

- MessyBrainz is a MetaBrainz project to support unclean metadata. While - MusicBrainz is designed to link clean metadata to stable identifiers, - there is a need to identify unclean or misspelled data as well. MessyBrainz provides identifiers to unclean - metadata, and where possible, links it to stable MusicBrainz identifiers. -

-

- MessyBrainz is currently used in support of ListenBrainz. Submission to MessyBrainz is restricted, however the - resulting data will be made freely available. -

-{%- endblock -%} diff --git a/listenbrainz/webserver/templates/index/musicbrainz-offline.html b/listenbrainz/webserver/templates/index/musicbrainz-offline.html deleted file mode 100644 index ba7494a2b4..0000000000 --- a/listenbrainz/webserver/templates/index/musicbrainz-offline.html +++ /dev/null @@ -1,21 +0,0 @@ -{%- extends 'base.html' -%} -{%- block title -%}Login currently unavailable - ListenBrainz{%- endblock -%} -{%- block content -%} -

Login currently unavailable

- -

- ListenBrainz login and sign up is currently unavailable due to database maintenance. - Please try again in a few minutes. -

- -

- Please note: You may continue to submit listens during this time. -

- -

- You may find out more about the current status of our services by - checking our Twitter - feed. -

- -{%- endblock -%} diff --git a/listenbrainz/webserver/templates/index/search-users.html b/listenbrainz/webserver/templates/index/search-users.html deleted file mode 100644 index f1f00a3a66..0000000000 --- a/listenbrainz/webserver/templates/index/search-users.html +++ /dev/null @@ -1,51 +0,0 @@ -{%- extends "base.html" -%} -{%- block title -%}Username Search Results{%- endblock -%} -{%- block content -%} -
-
-

Username Search Results

-
- -
-
- - - -
-
-
- - - - - {% if current_user.is_authenticated %} - - {% endif %} - - {% for row in users %} - - - - {% if current_user.is_authenticated %} - - {% endif %} - - {% else %} - - - - {% endfor %} -
UserSimilarity to you - - -
{{ loop.index }}{{ row[0] }} - {% if current_user.musicbrainz_id == row[0] %} - 100%, we hope! - {% elif row[2] %} - {{ "{:.1f}".format(row[2] * 10) }} - {% else %} - Similarity score not available - {% endif %} -
No search results found.
-{%- endblock -%} From 910b1c6190d2158d1b46d150862d62cd71635740 Mon Sep 17 00:00:00 2001 From: anshg1214 Date: Fri, 9 Feb 2024 12:53:56 +0000 Subject: [PATCH 003/104] feat: Add Helmet --- frontend/js/src/index.tsx | 5 +++++ package-lock.json | 38 ++++++++++++++++++++++++++++++++++++++ package.json | 2 ++ 3 files changed, 45 insertions(+) diff --git a/frontend/js/src/index.tsx b/frontend/js/src/index.tsx index e75d16a548..72405a5767 100644 --- a/frontend/js/src/index.tsx +++ b/frontend/js/src/index.tsx @@ -6,6 +6,7 @@ import { Integrations } from "@sentry/tracing"; import { createRoot } from "react-dom/client"; import { createBrowserRouter, RouterProvider } from "react-router-dom"; import { ToastContainer } from "react-toastify"; +import { Helmet } from "react-helmet"; import ErrorBoundary from "./utils/ErrorBoundary"; import GlobalAppContext from "./utils/GlobalAppContext"; import { getPageProps } from "./utils/utils"; @@ -41,6 +42,10 @@ document.addEventListener("DOMContentLoaded", async () => { /> + diff --git a/package-lock.json b/package-lock.json index 3ad3e30a9a..4ce6d22d7d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -56,6 +56,7 @@ "react-datetime-picker": "^4.2.0", "react-dom": "^18.2.0", "react-draggable": "^4.4.5", + "react-helmet": "^6.1.0", "react-lazy-load-image-component": "^1.5.6", "react-loader-spinner": "^3.1.14", "react-responsive": "^8.1.0", @@ -99,6 +100,7 @@ "@types/lodash": "^4.14.149", "@types/react": "^18.0.25", "@types/react-dom": "^18.0.8", + "@types/react-helmet": "^6.1.11", "@types/react-lazy-load-image-component": "^1.5.2", "@types/react-loader-spinner": "^3.1.0", "@types/react-select": "^5.0.1", @@ -4317,6 +4319,15 @@ "@types/react": "*" } }, + "node_modules/@types/react-helmet": { + "version": "6.1.11", + "resolved": "https://registry.npmjs.org/@types/react-helmet/-/react-helmet-6.1.11.tgz", + "integrity": "sha512-0QcdGLddTERotCXo3VFlUSWO3ztraw8nZ6e3zJSgG7apwV5xt+pJUS8ewPBqT4NYB1optGLprNQzFleIY84u/g==", + "dev": true, + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@types/react-lazy-load-image-component": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/@types/react-lazy-load-image-component/-/react-lazy-load-image-component-1.5.2.tgz", @@ -13632,6 +13643,11 @@ "react-is": "^16.13.1" } }, + "node_modules/react-fast-compare": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz", + "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==" + }, "node_modules/react-fit": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/react-fit/-/react-fit-1.4.0.tgz", @@ -13649,6 +13665,20 @@ "react-dom": "^15.5.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" } }, + "node_modules/react-helmet": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/react-helmet/-/react-helmet-6.1.0.tgz", + "integrity": "sha512-4uMzEY9nlDlgxr61NL3XbKRy1hEkXmKNXhjbAIOVw5vcFrsdYbH2FEwcNyWvWinl103nXgzYNlns9ca+8kFiWw==", + "dependencies": { + "object-assign": "^4.1.1", + "prop-types": "^15.7.2", + "react-fast-compare": "^3.1.1", + "react-side-effect": "^2.1.0" + }, + "peerDependencies": { + "react": ">=16.3.0" + } + }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", @@ -13758,6 +13788,14 @@ "react": "^16.0.0 || ^17.0.0 || ^18.0.0" } }, + "node_modules/react-side-effect": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/react-side-effect/-/react-side-effect-2.1.2.tgz", + "integrity": "sha512-PVjOcvVOyIILrYoyGEpDN3vmYNLdy1CajSFNt4TDsVQC5KpTijDvWVoR+/7Rz2xT978D8/ZtFceXxzsPwZEDvw==", + "peerDependencies": { + "react": "^16.3.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/react-simple-star-rating": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/react-simple-star-rating/-/react-simple-star-rating-4.0.0.tgz", diff --git a/package.json b/package.json index 47fc5d1b4d..2e867b24cc 100644 --- a/package.json +++ b/package.json @@ -80,6 +80,7 @@ "react-datetime-picker": "^4.2.0", "react-dom": "^18.2.0", "react-draggable": "^4.4.5", + "react-helmet": "^6.1.0", "react-lazy-load-image-component": "^1.5.6", "react-loader-spinner": "^3.1.14", "react-responsive": "^8.1.0", @@ -123,6 +124,7 @@ "@types/lodash": "^4.14.149", "@types/react": "^18.0.25", "@types/react-dom": "^18.0.8", + "@types/react-helmet": "^6.1.11", "@types/react-lazy-load-image-component": "^1.5.2", "@types/react-loader-spinner": "^3.1.0", "@types/react-select": "^5.0.1", From 986603088a3c3286b11c90fbb2fb072ec32b203f Mon Sep 17 00:00:00 2001 From: anshg1214 Date: Fri, 9 Feb 2024 21:46:44 +0000 Subject: [PATCH 004/104] feat: Add RouteLoader --- .../about/current-status/CurrentStatus.tsx | 11 -- frontend/js/src/about/routes/index.tsx | 7 +- frontend/js/src/routes/index.tsx | 48 ++++++++ frontend/js/src/search/UserSearch.tsx | 103 ++++++++++++++++++ frontend/js/src/utils/Loader.ts | 17 +++ 5 files changed, 171 insertions(+), 15 deletions(-) create mode 100644 frontend/js/src/routes/index.tsx create mode 100644 frontend/js/src/search/UserSearch.tsx create mode 100644 frontend/js/src/utils/Loader.ts diff --git a/frontend/js/src/about/current-status/CurrentStatus.tsx b/frontend/js/src/about/current-status/CurrentStatus.tsx index 5d276feb14..aefbe1b625 100644 --- a/frontend/js/src/about/current-status/CurrentStatus.tsx +++ b/frontend/js/src/about/current-status/CurrentStatus.tsx @@ -106,14 +106,3 @@ export default function CurrentStatus() { ); } - -export async function CurrentStatusLoader({ request }: { request: Request }) { - const response = await fetch(request.url, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - }); - const data = await response.json(); - return data; -} diff --git a/frontend/js/src/about/routes/index.tsx b/frontend/js/src/about/routes/index.tsx index aa39fca51d..2b87c876a0 100644 --- a/frontend/js/src/about/routes/index.tsx +++ b/frontend/js/src/about/routes/index.tsx @@ -2,11 +2,10 @@ import * as React from "react"; import AboutLayout from "../layout"; import About from "../About"; import AddData from "../add-data/AddData"; -import CurrentStatus, { - CurrentStatusLoader, -} from "../current-status/CurrentStatus"; +import CurrentStatus from "../current-status/CurrentStatus"; import Data from "../data/Data"; import TermsOfService from "../terms-of-service/TermsOfService"; +import RouteLoader from "../../utils/Loader"; const getAboutRoutes = () => { const routes = [ @@ -24,7 +23,7 @@ const getAboutRoutes = () => { }, { path: "current-status/", - loader: CurrentStatusLoader, + loader: RouteLoader, element: , }, { diff --git a/frontend/js/src/routes/index.tsx b/frontend/js/src/routes/index.tsx new file mode 100644 index 0000000000..6a0175149c --- /dev/null +++ b/frontend/js/src/routes/index.tsx @@ -0,0 +1,48 @@ +import * as React from "react"; +import { Outlet } from "react-router-dom"; +import ImportData from "../import-data/ImportData"; +import LastfmProxy from "../lastfm-proxy/LastfmProxy"; +import ListensOffline from "../listens-offline/ListensOffline"; +import MusicBrainzOffline from "../musicbrainz-offline/MusicBrainzOffline"; +import SearchResults from "../search/UserSearch"; +import MessyBrainz from "../messybrainz/MessyBrainz"; +import RouteLoader from "../utils/Loader"; + +const getIndexRoutes = () => { + const routes = [ + { + path: "/", + element: , + children: [ + { + path: "import-data/", + element: , + }, + { + path: "messybrainz/", + element: , + }, + { + path: "lastfm-proxy/", + element: , + }, + { + path: "listens-offline/", + element: , + }, + { + path: "musicbrainz-offline/", + element: , + }, + { + path: "search/", + element: , + loader: RouteLoader, + }, + ], + }, + ]; + return routes; +}; + +export default getIndexRoutes; diff --git a/frontend/js/src/search/UserSearch.tsx b/frontend/js/src/search/UserSearch.tsx new file mode 100644 index 0000000000..651fedc63b --- /dev/null +++ b/frontend/js/src/search/UserSearch.tsx @@ -0,0 +1,103 @@ +import * as React from "react"; +import { useLoaderData, useNavigate } from "react-router-dom"; +import GlobalAppContext from "../utils/GlobalAppContext"; + +type SearchResultsLoaderData = { + searchTerm: string; + users: [string, number, number?][]; +}; + +export default function SearchResults() { + const navigate = useNavigate(); + const { searchTerm, users } = useLoaderData() as SearchResultsLoaderData; + const { currentUser } = React.useContext(GlobalAppContext); + + const [searchTermInput, setSearchTermInput] = React.useState(searchTerm); + const username = currentUser ? currentUser.name : null; + + const search = () => { + if (!searchTermInput) { + return; + } + navigate(`/search/?search_term=${searchTermInput}`); + }; + + return ( + <> +
+

Username Search Results

+
+ setSearchTermInput(e.target.value)} + required + /> +
+
+ + + +
+
+ + + + + + + {username && ( + + )} + + + + {users.length > 0 ? ( + users.map((row, index) => ( + + + + {username && ( + + )} + + )) + ) : ( + + + + )} + +
{}User + Similarity to you{" "} + +
{index + 1} + {row[0]} + + {(() => { + if (username === row[0]) { + return "100%, we hope!"; + } + if (row[2]) { + return `${(row[2] * 10).toFixed(1)}%`; + } + return "Similarity score not available"; + })()} +
No search results found.
+ + ); +} diff --git a/frontend/js/src/utils/Loader.ts b/frontend/js/src/utils/Loader.ts new file mode 100644 index 0000000000..447195209d --- /dev/null +++ b/frontend/js/src/utils/Loader.ts @@ -0,0 +1,17 @@ +import { json } from "react-router-dom"; + +const RouteLoader = async ({ request }: { request: Request }) => { + const response = await fetch(request.url, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + }); + const data = await response.json(); + if (!response.ok) { + throw json(data, { status: response.status }); + } + return data; +}; + +export default RouteLoader; From a0cd3f57ffbbe36b682ccc5e9128c780c9c26145 Mon Sep 17 00:00:00 2001 From: anshg1214 Date: Fri, 9 Feb 2024 21:48:12 +0000 Subject: [PATCH 005/104] feat: add entity pages --- frontend/js/src/album/AlbumPage.tsx | 75 ++---------------- frontend/js/src/artist/ArtistPage.tsx | 76 ++++--------------- frontend/js/src/layout/EntityPages.tsx | 26 +++++++ .../js/src/release-group/ReleaseGroup.tsx | 7 ++ frontend/js/src/release/Release.tsx | 11 +++ frontend/js/src/routes/EntityPages.tsx | 40 ++++++++++ frontend/js/src/routes/redirectRoutes.tsx | 33 ++++++++ frontend/js/src/routes/routes.tsx | 33 ++++++++ webpack.config.js | 3 +- 9 files changed, 173 insertions(+), 131 deletions(-) create mode 100644 frontend/js/src/layout/EntityPages.tsx create mode 100644 frontend/js/src/release-group/ReleaseGroup.tsx create mode 100644 frontend/js/src/release/Release.tsx create mode 100644 frontend/js/src/routes/EntityPages.tsx create mode 100644 frontend/js/src/routes/redirectRoutes.tsx create mode 100644 frontend/js/src/routes/routes.tsx diff --git a/frontend/js/src/album/AlbumPage.tsx b/frontend/js/src/album/AlbumPage.tsx index b0a18dbb77..59f4a227bb 100644 --- a/frontend/js/src/album/AlbumPage.tsx +++ b/frontend/js/src/album/AlbumPage.tsx @@ -1,10 +1,6 @@ import * as React from "react"; -import { createRoot } from "react-dom/client"; -import * as Sentry from "@sentry/react"; -import { Integrations } from "@sentry/tracing"; -import NiceModal from "@ebay/nice-modal-react"; -import { toast, ToastContainer } from "react-toastify"; +import { toast } from "react-toastify"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faHeadphones, @@ -13,20 +9,19 @@ import { } from "@fortawesome/free-solid-svg-icons"; import { chain, flatten, isEmpty, isUndefined, merge } from "lodash"; import tinycolor from "tinycolor2"; +import { Helmet } from "react-helmet"; +import { useLoaderData } from "react-router-dom"; import { getRelIconLink, ListeningStats, popularRecordingToListen, } from "./utils"; -import withAlertNotifications from "../notifications/AlertNotificationsHOC"; import GlobalAppContext from "../utils/GlobalAppContext"; import Loader from "../components/Loader"; -import ErrorBoundary from "../utils/ErrorBoundary"; import { generateAlbumArtThumbnailLink, getAlbumArtFromReleaseGroupMBID, getAverageRGBOfImage, - getPageProps, getReviewEventContent, } from "../utils/utils"; import BrainzPlayer from "../common/brainzplayer/BrainzPlayer"; @@ -62,7 +57,7 @@ export type AlbumPageProps = { listening_stats: ListeningStats; }; -export default function AlbumPage(props: AlbumPageProps): JSX.Element { +export default function AlbumPage(): JSX.Element { const { currentUser, APIService } = React.useContext(GlobalAppContext); const { release_group_metadata: initialReleaseGroupMetadata, @@ -73,7 +68,7 @@ export default function AlbumPage(props: AlbumPageProps): JSX.Element { caa_release_mbid, type, listening_stats, - } = props; + } = useLoaderData() as AlbumPageProps; const { total_listen_count: listenCount, listeners: topListeners, @@ -204,6 +199,9 @@ export default function AlbumPage(props: AlbumPageProps): JSX.Element { className="album-page" style={{ ["--bg-color" as string]: adjustedAlbumColor }} > + + {album?.name} +
@@ -482,60 +480,3 @@ export default function AlbumPage(props: AlbumPageProps): JSX.Element {
); } - -document.addEventListener("DOMContentLoaded", async () => { - const { - domContainer, - reactProps, - globalAppContext, - sentryProps, - } = await getPageProps(); - const { sentry_dsn, sentry_traces_sample_rate } = sentryProps; - - if (sentry_dsn) { - Sentry.init({ - dsn: sentry_dsn, - integrations: [new Integrations.BrowserTracing()], - tracesSampleRate: sentry_traces_sample_rate, - }); - } - const { - recordings_release_mbid, - mediums, - release_group_mbid, - caa_id, - caa_release_mbid, - type, - release_group_metadata, - listening_stats, - } = reactProps; - - const AlbumPageWithAlertNotifications = withAlertNotifications(AlbumPage); - - const renderRoot = createRoot(domContainer!); - renderRoot.render( - - - - - - - - - ); -}); diff --git a/frontend/js/src/artist/ArtistPage.tsx b/frontend/js/src/artist/ArtistPage.tsx index 5169c3368a..52b2ef1cce 100644 --- a/frontend/js/src/artist/ArtistPage.tsx +++ b/frontend/js/src/artist/ArtistPage.tsx @@ -1,10 +1,6 @@ import * as React from "react"; -import { createRoot } from "react-dom/client"; -import * as Sentry from "@sentry/react"; -import { Integrations } from "@sentry/tracing"; -import NiceModal from "@ebay/nice-modal-react"; -import { toast, ToastContainer } from "react-toastify"; +import { toast } from "react-toastify"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faHeadphones, @@ -13,11 +9,11 @@ import { } from "@fortawesome/free-solid-svg-icons"; import { chain, isEmpty, isUndefined, partition, sortBy } from "lodash"; import { sanitize } from "dompurify"; -import withAlertNotifications from "../notifications/AlertNotificationsHOC"; +import { useLoaderData } from "react-router-dom"; +import { Helmet } from "react-helmet"; import GlobalAppContext from "../utils/GlobalAppContext"; import Loader from "../components/Loader"; -import ErrorBoundary from "../utils/ErrorBoundary"; -import { getPageProps, getReviewEventContent } from "../utils/utils"; +import { getReviewEventContent } from "../utils/utils"; import BrainzPlayer from "../common/brainzplayer/BrainzPlayer"; import TagsComponent from "../tags/TagsComponent"; import ListenCard from "../common/listens/ListenCard"; @@ -27,7 +23,11 @@ import { ListeningStats, popularRecordingToListen, } from "../album/utils"; -import type { PopularRecording, ReleaseGroup, SimilarArtist } from "../album/utils"; +import type { + PopularRecording, + ReleaseGroup, + SimilarArtist, +} from "../album/utils"; import ReleaseCard from "../explore/fresh-releases/components/ReleaseCard"; export type ArtistPageProps = { @@ -39,7 +39,7 @@ export type ArtistPageProps = { coverArt?: string; }; -export default function ArtistPage(props: ArtistPageProps): JSX.Element { +export default function ArtistPage(): JSX.Element { const { APIService } = React.useContext(GlobalAppContext); const { artist: initialArtist, @@ -48,7 +48,7 @@ export default function ArtistPage(props: ArtistPageProps): JSX.Element { similarArtists, listeningStats, coverArt: coverArtSVG, - } = props; + } = useLoaderData() as ArtistPageProps; const { total_listen_count: listenCount, listeners: topListeners, @@ -166,6 +166,9 @@ export default function ArtistPage(props: ArtistPageProps): JSX.Element { return (
+ + {artist?.name} +
); } - -document.addEventListener("DOMContentLoaded", async () => { - const { - domContainer, - reactProps, - globalAppContext, - sentryProps, - } = await getPageProps(); - const { sentry_dsn, sentry_traces_sample_rate } = sentryProps; - - if (sentry_dsn) { - Sentry.init({ - dsn: sentry_dsn, - integrations: [new Integrations.BrowserTracing()], - tracesSampleRate: sentry_traces_sample_rate, - }); - } - const { - artist_data, - popular_recordings, - release_groups, - similar_artists, - listening_stats, - cover_art, - } = reactProps; - - const ArtistPageWithAlertNotifications = withAlertNotifications(ArtistPage); - - const renderRoot = createRoot(domContainer!); - renderRoot.render( - - - - - - - - - ); -}); diff --git a/frontend/js/src/layout/EntityPages.tsx b/frontend/js/src/layout/EntityPages.tsx new file mode 100644 index 0000000000..d5bb7b4545 --- /dev/null +++ b/frontend/js/src/layout/EntityPages.tsx @@ -0,0 +1,26 @@ +import React from "react"; +import { Outlet, ScrollRestoration, useNavigate } from "react-router-dom"; + +export default function EntityPageLayout() { + const navigate = useNavigate(); + + const goBack = () => { + navigate(-1); + }; + + return ( + <> + +
+
    +
  1. + +
  2. +
+
+ + + ); +} diff --git a/frontend/js/src/release-group/ReleaseGroup.tsx b/frontend/js/src/release-group/ReleaseGroup.tsx new file mode 100644 index 0000000000..8de3d0d00e --- /dev/null +++ b/frontend/js/src/release-group/ReleaseGroup.tsx @@ -0,0 +1,7 @@ +import * as React from "react"; +import { Navigate, useParams } from "react-router-dom"; + +export default function ReleaseGroup() { + const { releaseGroupMBID } = useParams(); + return ; +} diff --git a/frontend/js/src/release/Release.tsx b/frontend/js/src/release/Release.tsx new file mode 100644 index 0000000000..4441ce49f0 --- /dev/null +++ b/frontend/js/src/release/Release.tsx @@ -0,0 +1,11 @@ +import * as React from "react"; +import { Navigate, useLoaderData } from "react-router-dom"; + +type ReleaseLoaderData = { + releaseGroupMBID: string; +}; + +export default function Release() { + const { releaseGroupMBID } = useLoaderData() as ReleaseLoaderData; + return ; +} diff --git a/frontend/js/src/routes/EntityPages.tsx b/frontend/js/src/routes/EntityPages.tsx new file mode 100644 index 0000000000..0364b6a8b4 --- /dev/null +++ b/frontend/js/src/routes/EntityPages.tsx @@ -0,0 +1,40 @@ +import * as React from "react"; +import EntityPageLayout from "../layout/EntityPages"; +import ArtistPage from "../artist/ArtistPage"; +import AlbumPage from "../album/AlbumPage"; +import RouteLoader from "../utils/Loader"; +import ReleaseGroup from "../release-group/ReleaseGroup"; +import Release from "../release/Release"; + +const getEntityPages = () => { + const routes = [ + { + path: "/", + element: , + children: [ + { + path: "artist/:artistMBID/", + element: , + loader: RouteLoader, + }, + { + path: "album/:albumMBID/", + element: , + loader: RouteLoader, + }, + { + path: "release-group/:releaseGroupMBID/", + element: , + }, + { + path: "release/:releaseMBID/", + element: , + loader: RouteLoader, + }, + ], + }, + ]; + return routes; +}; + +export default getEntityPages; diff --git a/frontend/js/src/routes/redirectRoutes.tsx b/frontend/js/src/routes/redirectRoutes.tsx new file mode 100644 index 0000000000..2621df3528 --- /dev/null +++ b/frontend/js/src/routes/redirectRoutes.tsx @@ -0,0 +1,33 @@ +import * as React from "react"; +import { Navigate, Outlet, Params } from "react-router-dom"; + +const getRedirectRoutes = () => { + const routes = [ + { + path: "/", + element: , + children: [ + { + path: "download/", + element: , + }, + { + path: "similar-users/", + element: , + }, + { + path: "huesound/", + element: , + }, + { + path: "import/", + element: , + }, + ], + }, + ]; + + return routes; +}; + +export default getRedirectRoutes; diff --git a/frontend/js/src/routes/routes.tsx b/frontend/js/src/routes/routes.tsx new file mode 100644 index 0000000000..a01b63f5c6 --- /dev/null +++ b/frontend/js/src/routes/routes.tsx @@ -0,0 +1,33 @@ +import * as React from "react"; + +import { Outlet } from "react-router-dom"; +import getIndexRoutes from "."; +import getAboutRoutes from "../about/routes"; +import getRedirectRoutes from "./redirectRoutes"; +import getEntityPages from "./EntityPages"; +import ErrorBoundary from "../error/ErrorBoundary"; + +const getRoutes = () => { + const indexRoutes = getIndexRoutes(); + const aboutRoutes = getAboutRoutes(); + const aboutRedirectRoutes = getRedirectRoutes(); + const entityPages = getEntityPages(); + + const routes = [ + { + path: "/", + element: , + errorElement: , + children: [ + ...indexRoutes, + ...aboutRoutes, + ...aboutRedirectRoutes, + ...entityPages, + ], + }, + ]; + + return routes; +}; + +export default getRoutes; diff --git a/webpack.config.js b/webpack.config.js index d5acf02325..048775c369 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -116,9 +116,8 @@ module.exports = function (env, argv) { "src/explore/music-neighborhood/MusicNeighborhood.tsx" ), artCreator: path.resolve(jsDir, "src/explore/art-creator/ArtCreator.tsx"), - artistPage: path.resolve(jsDir, "src/artist/ArtistPage.tsx"), - albumPage: path.resolve(jsDir, "src/album/AlbumPage.tsx"), settingsPage: path.resolve(jsDir, "src/settings/index.tsx"), + indexPage: path.resolve(jsDir, "src/index.tsx"), }, output: { filename: isProd ? "[name].[contenthash].js" : "[name].js", From 690647c4c24c35e214e8cb58ac5dd8ee126e4655 Mon Sep 17 00:00:00 2001 From: anshg1214 Date: Fri, 9 Feb 2024 21:51:41 +0000 Subject: [PATCH 006/104] refactor: Cleanup and add error boundary --- frontend/js/src/error/ErrorBoundary.tsx | 52 +++++++++++ frontend/js/src/layout/index.tsx | 11 +++ listenbrainz/webserver/templates/index.html | 10 +++ listenbrainz/webserver/templates/navbar.html | 2 +- listenbrainz/webserver/views/entity_pages.py | 74 ++++++++------- listenbrainz/webserver/views/index.py | 95 ++++---------------- 6 files changed, 137 insertions(+), 107 deletions(-) create mode 100644 frontend/js/src/error/ErrorBoundary.tsx create mode 100644 frontend/js/src/layout/index.tsx create mode 100644 listenbrainz/webserver/templates/index.html diff --git a/frontend/js/src/error/ErrorBoundary.tsx b/frontend/js/src/error/ErrorBoundary.tsx new file mode 100644 index 0000000000..1d694c7ffd --- /dev/null +++ b/frontend/js/src/error/ErrorBoundary.tsx @@ -0,0 +1,52 @@ +import React from "react"; +import { Helmet } from "react-helmet"; +import { isRouteErrorResponse, useRouteError } from "react-router-dom"; + +const ErrorStatusMessage: { [key: number]: string } = { + 400: "Invalid request", + 401: "Unauthorized", + 403: "Access denied", + 404: "Page not found", + 413: "Filesize limit exceeded", + 500: "Internal server error", + 503: "Service unavailable", +}; + +type RouteError = { + status?: number; + message?: string; + data?: any; +}; + +export function ErrorBoundary() { + const error = useRouteError() as RouteError; + const errorStatus = error.status || 500; + const errorStatusMessage = ErrorStatusMessage[errorStatus] || "Error"; + + const errorMessage = + error.data?.error || error.message || "An error occurred"; + + return isRouteErrorResponse(error) ? ( + <> + + {errorStatusMessage} + +

{errorStatusMessage}

+

+ {`${errorStatus}: ${errorMessage}`} +

+

+ Back to home page +

+ + ) : ( + <> + + Error + +

Error Occured!

+ + ); +} + +export default ErrorBoundary; diff --git a/frontend/js/src/layout/index.tsx b/frontend/js/src/layout/index.tsx new file mode 100644 index 0000000000..5062ef2a44 --- /dev/null +++ b/frontend/js/src/layout/index.tsx @@ -0,0 +1,11 @@ +import * as React from "react"; +import { Outlet, ScrollRestoration } from "react-router-dom"; + +export default function Layout() { + return ( + <> + + + + ); +} diff --git a/listenbrainz/webserver/templates/index.html b/listenbrainz/webserver/templates/index.html new file mode 100644 index 0000000000..838da79d06 --- /dev/null +++ b/listenbrainz/webserver/templates/index.html @@ -0,0 +1,10 @@ +{%- extends 'base-react.html' -%} + +{% block content %} + {{ super() }} +{% endblock %} + +{% block scripts %} + {{ super() }} + +{% endblock %} \ No newline at end of file diff --git a/listenbrainz/webserver/templates/navbar.html b/listenbrainz/webserver/templates/navbar.html index 641ed7a0b1..fe7663c317 100644 --- a/listenbrainz/webserver/templates/navbar.html +++ b/listenbrainz/webserver/templates/navbar.html @@ -40,7 +40,7 @@ {% else %} Sign in {%- endif -%} - About + About Community
); @@ -458,9 +459,9 @@ export default class YearInMusic extends React.Component< Double click on any song to start playing it — we will do our best to find a matching song to play. If you have a Spotify pro account, we recommend{" "} - + connecting your account - {" "} + {" "} for a better playback experience.

diff --git a/frontend/js/src/user/year-in-music/2022/YearInMusic2022.tsx b/frontend/js/src/user/year-in-music/2022/YearInMusic2022.tsx index 69531af84a..50e02ca0e1 100644 --- a/frontend/js/src/user/year-in-music/2022/YearInMusic2022.tsx +++ b/frontend/js/src/user/year-in-music/2022/YearInMusic2022.tsx @@ -344,12 +344,14 @@ export default class YearInMusic extends React.Component<

Check out how you can submit listens by{" "} - + connecting a music service - {" "} + {" "} or{" "} - importing your listening history, - and come back next year! + + importing your listening history + + , and come back next year!

diff --git a/frontend/js/src/user/year-in-music/2023/YearInMusic2023.tsx b/frontend/js/src/user/year-in-music/2023/YearInMusic2023.tsx index fe0338149f..dbab32d072 100644 --- a/frontend/js/src/user/year-in-music/2023/YearInMusic2023.tsx +++ b/frontend/js/src/user/year-in-music/2023/YearInMusic2023.tsx @@ -624,8 +624,8 @@ export default class YearInMusic extends React.Component< We don't have enough 2023 statistics for {user.name}.

- Submit enough - listens before the end of December to generate your + Submit{" "} + enough listens before the end of December to generate your #yearinmusic next year.

diff --git a/frontend/js/src/utils/ProtectedRoutes.tsx b/frontend/js/src/utils/ProtectedRoutes.tsx new file mode 100644 index 0000000000..73ae658149 --- /dev/null +++ b/frontend/js/src/utils/ProtectedRoutes.tsx @@ -0,0 +1,13 @@ +import React from "react"; + +import { Navigate, Outlet } from "react-router-dom"; +import GlobalAppContext from "./GlobalAppContext"; +import Layout from "../layout"; + +function ProtectedRoutes() { + const { currentUser } = React.useContext(GlobalAppContext); + + return currentUser?.name ? : ; +} + +export default ProtectedRoutes; diff --git a/listenbrainz/webserver/templates/navbar.html b/listenbrainz/webserver/templates/navbar.html index fe7663c317..765405ff78 100644 --- a/listenbrainz/webserver/templates/navbar.html +++ b/listenbrainz/webserver/templates/navbar.html @@ -24,8 +24,8 @@ Feed Dashboard {% else %} - Feed - Dashboard + Feed + Dashboard {% endif %} Explore diff --git a/listenbrainz/webserver/templates/settings/index.html b/listenbrainz/webserver/templates/settings/index.html index 6f408b8a79..00e50d6e7f 100644 --- a/listenbrainz/webserver/templates/settings/index.html +++ b/listenbrainz/webserver/templates/settings/index.html @@ -6,5 +6,5 @@ {% block scripts %} {{ super() }} - + {% endblock %} diff --git a/webpack.config.js b/webpack.config.js index 4ee09958ce..b642cfd4e6 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -68,7 +68,6 @@ module.exports = function (env, argv) { jsDir, "src/metadata-viewer/MetadataViewerPage.tsx" ), - settingsPage: path.resolve(jsDir, "src/settings/index.tsx"), indexPage: path.resolve(jsDir, "src/index.tsx"), }, output: { From 9e9664b3f1420363e097ba5a1d7e096637d639a0 Mon Sep 17 00:00:00 2001 From: anshg1214 Date: Thu, 15 Feb 2024 13:50:36 +0000 Subject: [PATCH 013/104] fix: PEP8 issue --- listenbrainz/webserver/views/metadata_viewer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/listenbrainz/webserver/views/metadata_viewer.py b/listenbrainz/webserver/views/metadata_viewer.py index 11f618e390..e47a52a208 100644 --- a/listenbrainz/webserver/views/metadata_viewer.py +++ b/listenbrainz/webserver/views/metadata_viewer.py @@ -32,4 +32,4 @@ def playing_now_metadata_viewer(): return jsonify({ "playingNow": playing_now - }) \ No newline at end of file + }) From d530a6d229eef93cb8e01708b7da9063b464c29f Mon Sep 17 00:00:00 2001 From: anshg1214 Date: Thu, 15 Feb 2024 21:19:02 +0000 Subject: [PATCH 014/104] feat: Switch Layout from HTML to React --- frontend/css/main.less | 4 +- frontend/css/new-navbar.less | 23 +- frontend/css/theme/bootstrap/grid.less | 2 +- frontend/css/theme/bootstrap/jumbotron.less | 4 +- frontend/css/theme/bootstrap/navbar.less | 4 +- frontend/js/src/components/Footer.tsx | 279 ++++++++++++++++++++ frontend/js/src/components/Navbar.tsx | 148 +++++++++++ frontend/js/src/layout/index.tsx | 11 +- listenbrainz/webserver/templates/index.html | 36 ++- listenbrainz/webserver/views/settings.py | 2 +- 10 files changed, 488 insertions(+), 25 deletions(-) create mode 100644 frontend/js/src/components/Footer.tsx create mode 100644 frontend/js/src/components/Navbar.tsx diff --git a/frontend/css/main.less b/frontend/css/main.less index ae4cdb3ce5..6bb3610143 100644 --- a/frontend/css/main.less +++ b/frontend/css/main.less @@ -108,12 +108,14 @@ pre code.hljs { } // Content Wrapper -.wrapper { +.container-react-main { min-height: 70vh; } /* Footer */ .footer { + padding-left: 25px; + width: 100%; background: #fff; color: #000000; padding-bottom: 10px; diff --git a/frontend/css/new-navbar.less b/frontend/css/new-navbar.less index b5bf8ddffa..5488d8dd59 100644 --- a/frontend/css/new-navbar.less +++ b/frontend/css/new-navbar.less @@ -17,8 +17,12 @@ @gradient-orange: #ffa500; body { - display: flex; - > nav[role="navigation"] { + #react-container { + display: flex; + width: 100%; + } + + nav[role="navigation"] { max-width: @sidenav-width; #side-nav { @@ -154,15 +158,14 @@ body { } } /* main content */ - > :not(nav) { - margin-left: 1em; + .container-react { // Leave some space for BrainzPlayer padding-bottom: @brainzplayer-height; width: 100%; // fallback width: calc(100% - @sidenav-width); - div[role="main"] { + .container-react-main { max-width: @max-content-width; margin-left: auto; margin-right: auto; @@ -171,8 +174,7 @@ body { /* Styles for mobile devices */ @media (max-width: @offscreen-sidenav-breakpoint) { - display: initial; - > nav[role="navigation"] { + nav[role="navigation"] { max-width: unset; #side-nav { transition: transform 0.3s ease-in-out; @@ -218,12 +220,15 @@ body { align-items: center; } } - > :not(nav) { + .container-react { &, - div[role="main"] { + .container-react-main { width: 100%; } } + #react-container { + display: initial; + } } } diff --git a/frontend/css/theme/bootstrap/grid.less b/frontend/css/theme/bootstrap/grid.less index e100655b70..be48690209 100644 --- a/frontend/css/theme/bootstrap/grid.less +++ b/frontend/css/theme/bootstrap/grid.less @@ -27,7 +27,7 @@ // Utilizes the mixin meant for fixed width containers, but without any defined // width for fluid, full width layouts. -.container-fluid { +.container-react-main { .container-fixed(); } diff --git a/frontend/css/theme/bootstrap/jumbotron.less b/frontend/css/theme/bootstrap/jumbotron.less index fa80a38c66..e913b16d15 100644 --- a/frontend/css/theme/bootstrap/jumbotron.less +++ b/frontend/css/theme/bootstrap/jumbotron.less @@ -26,7 +26,7 @@ } .container &, - .container-fluid & { + .container-react & { border-radius: @border-radius-large; // Only round corners at higher resolutions if contained in a container } @@ -39,7 +39,7 @@ padding-bottom: (@jumbotron-padding * 1.6); .container &, - .container-fluid & { + .container-react & { padding-left: (@jumbotron-padding * 2); padding-right: (@jumbotron-padding * 2); } diff --git a/frontend/css/theme/bootstrap/navbar.less b/frontend/css/theme/bootstrap/navbar.less index 6d751bb9ce..e816806d0e 100644 --- a/frontend/css/theme/bootstrap/navbar.less +++ b/frontend/css/theme/bootstrap/navbar.less @@ -104,7 +104,7 @@ // When a container is present, change the behavior of the header and collapse. .container, -.container-fluid { +.container-react-main { > .navbar-header, > .navbar-collapse { margin-right: -@navbar-padding-horizontal; @@ -178,7 +178,7 @@ @media (min-width: @grid-float-breakpoint) { .navbar > .container &, - .navbar > .container-fluid & { + .navbar > .container-react & { margin-left: -@navbar-padding-horizontal; } } diff --git a/frontend/js/src/components/Footer.tsx b/frontend/js/src/components/Footer.tsx new file mode 100644 index 0000000000..aeb15bd250 --- /dev/null +++ b/frontend/js/src/components/Footer.tsx @@ -0,0 +1,279 @@ +import * as React from "react"; + +import { faAnglesRight } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; + +export default function Footer() { + return ( +
+
+
+
+

+ ListenBrainz +

+
+

+ ListenBrainz keeps track of music you listen to and provides you + with insights into your listening habits. +
+ You can use ListenBrainz to track your listening habits, discover + new music with personalized recommendations, and share your + musical taste with others using our visualizations. +

+ +
+
+
+

Useful Links

+ +
+
+

Fellow Projects

+ +
+
+
+
+

+ OSS Geek?{" "} + + {" "} + Contribute Here {" "} + +

+
+
+

+ Brought to you by{" "} + MetaBrainz{" "} + MetaBrainz Foundation +

+
+
+

+ Found an Issue?{" "} + + {" "} + Report Here + +

+
+
+
+
+ ); +} diff --git a/frontend/js/src/components/Navbar.tsx b/frontend/js/src/components/Navbar.tsx new file mode 100644 index 0000000000..c705c6836b --- /dev/null +++ b/frontend/js/src/components/Navbar.tsx @@ -0,0 +1,148 @@ +import React from "react"; +import { Link, useLocation } from "react-router-dom"; +import GlobalAppContext from "../utils/GlobalAppContext"; + +function Navbar() { + const { currentUser } = React.useContext(GlobalAppContext); + const location = useLocation(); + + const [activePage, setActivePage] = React.useState(""); + const [myProfile, setMyProfile] = React.useState(false); + React.useEffect(() => { + const path = location.pathname.split("/")[1]; + if (path === "user") { + setMyProfile(location.pathname.split("/")[2] === currentUser?.name); + } + setActivePage(path); + }, [location.pathname, currentUser?.name]); + + return ( + + ); +} + +export default Navbar; diff --git a/frontend/js/src/layout/index.tsx b/frontend/js/src/layout/index.tsx index 5062ef2a44..e032a8fa7f 100644 --- a/frontend/js/src/layout/index.tsx +++ b/frontend/js/src/layout/index.tsx @@ -1,11 +1,20 @@ import * as React from "react"; import { Outlet, ScrollRestoration } from "react-router-dom"; +import Footer from "../components/Footer"; +import Navbar from "../components/Navbar"; + export default function Layout() { return ( <> - + +
+
+ +
+
+
); } diff --git a/listenbrainz/webserver/templates/index.html b/listenbrainz/webserver/templates/index.html index 838da79d06..5789087636 100644 --- a/listenbrainz/webserver/templates/index.html +++ b/listenbrainz/webserver/templates/index.html @@ -1,10 +1,30 @@ -{%- extends 'base-react.html' -%} + + + + {%- block head -%} + + {% block title %}ListenBrainz{% endblock %} + + -{% block content %} - {{ super() }} -{% endblock %} + + + -{% block scripts %} - {{ super() }} - -{% endblock %} \ No newline at end of file + {# The css file has a .less extension in the manifest file entry (due to its original name in Webpack entry) #} + + {%- endblock -%} + + + + +
+
+ + + + + + + + diff --git a/listenbrainz/webserver/views/settings.py b/listenbrainz/webserver/views/settings.py index 0b32aa2411..6f52f3cb04 100644 --- a/listenbrainz/webserver/views/settings.py +++ b/listenbrainz/webserver/views/settings.py @@ -371,4 +371,4 @@ def missing_mb_data(): @settings_bp.route('//') @login_required def index(path): - return render_template("settings/index.html") + return render_template("index.html") From a98ef0896d07fd259ac1348ffbf593cc70fbf976 Mon Sep 17 00:00:00 2001 From: anshg1214 Date: Thu, 15 Feb 2024 22:21:59 +0000 Subject: [PATCH 015/104] feat: migrate homepage to SPA --- frontend/js/src/components/Navbar.tsx | 4 +- frontend/js/src/home/Homepage.tsx | 82 ++++++++----------- frontend/js/src/routes/index.tsx | 6 ++ .../webserver/templates/index/index.html | 28 ------- listenbrainz/webserver/views/index.py | 14 +--- webpack.config.js | 1 - 6 files changed, 47 insertions(+), 88 deletions(-) delete mode 100644 listenbrainz/webserver/templates/index/index.html diff --git a/frontend/js/src/components/Navbar.tsx b/frontend/js/src/components/Navbar.tsx index c705c6836b..0bb893d3b4 100644 --- a/frontend/js/src/components/Navbar.tsx +++ b/frontend/js/src/components/Navbar.tsx @@ -30,13 +30,13 @@ function Navbar() { - + ListenBrainz - +
diff --git a/frontend/js/src/home/Homepage.tsx b/frontend/js/src/home/Homepage.tsx index 9ad4275ca2..7e12afc156 100644 --- a/frontend/js/src/home/Homepage.tsx +++ b/frontend/js/src/home/Homepage.tsx @@ -19,31 +19,42 @@ */ import * as React from "react"; -import { createRoot } from "react-dom/client"; -import * as Sentry from "@sentry/react"; -import { Integrations } from "@sentry/tracing"; -import { ToastContainer } from "react-toastify"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faSortDown, faSortUp } from "@fortawesome/free-solid-svg-icons"; import { throttle } from "lodash"; -import { getPageProps } from "../utils/utils"; -import ErrorBoundary from "../utils/ErrorBoundary"; -import GlobalAppContext from "../utils/GlobalAppContext"; -import withAlertNotifications from "../notifications/AlertNotificationsHOC"; +import { useLoaderData, useNavigate } from "react-router-dom"; +import { Helmet } from "react-helmet"; import NumberCounter from "./NumberCounter"; import Blob from "./Blob"; +import GlobalAppContext from "../utils/GlobalAppContext"; type HomePageProps = { listenCount: number; artistCount: number; }; -function HomePage({ listenCount, artistCount }: HomePageProps) { +function HomePage() { + const { listenCount, artistCount } = useLoaderData() as HomePageProps; const homepageUpperRef = React.useRef(null); const homepageLowerRef = React.useRef(null); + const { currentUser } = React.useContext(GlobalAppContext); + const navigate = useNavigate(); const [windowHeight, setWindowHeight] = React.useState(window.innerHeight); + React.useEffect(() => { + const redirectParam = new URLSearchParams(window.location.search).get( + "redirect" + ); + + if ( + currentUser?.name && + (redirectParam === "true" || redirectParam === null) + ) { + navigate(`/user/${currentUser.name}`); + } + }, [currentUser, navigate]); + React.useEffect(() => { const handleResize = throttle( () => { @@ -74,6 +85,20 @@ function HomePage({ listenCount, artistCount }: HomePageProps) { } as React.CSSProperties; return (
+ + +
{ - const { - domContainer, - reactProps, - globalAppContext, - sentryProps, - } = await getPageProps(); - const { sentry_dsn, sentry_traces_sample_rate } = sentryProps; - - if (sentry_dsn) { - Sentry.init({ - dsn: sentry_dsn, - integrations: [new Integrations.BrowserTracing()], - tracesSampleRate: sentry_traces_sample_rate, - }); - } - - const { listen_count, artist_count } = reactProps; - - const HomePageWithAlertNotifications = withAlertNotifications(HomePage); - - const renderRoot = createRoot(domContainer!); - renderRoot.render( - - - - - - - ); -}); +export default HomePage; diff --git a/frontend/js/src/routes/index.tsx b/frontend/js/src/routes/index.tsx index 87c876b300..600f0102e8 100644 --- a/frontend/js/src/routes/index.tsx +++ b/frontend/js/src/routes/index.tsx @@ -20,6 +20,7 @@ import { RecentListensWrapper, } from "../recent/RecentListens"; import UserFeedLayout from "../user-feed/UserFeedLayout"; +import HomePage from "../home/Homepage"; const getIndexRoutes = () => { const routes = [ @@ -27,6 +28,11 @@ const getIndexRoutes = () => { path: "/", element: , children: [ + { + index: true, + element: , + loader: RouteLoader, + }, { path: "import-data/", element: , diff --git a/listenbrainz/webserver/templates/index/index.html b/listenbrainz/webserver/templates/index/index.html deleted file mode 100644 index 431a5b4600..0000000000 --- a/listenbrainz/webserver/templates/index/index.html +++ /dev/null @@ -1,28 +0,0 @@ -{%- extends 'base-react.html' -%} - -{% block head %} - {{ super() }} - -{%- endblock -%} - -{%- block content -%} - - {{ super() }} -{%- endblock -%} - -{%- block footer -%} - -{%- endblock -%} - -{% block scripts %} -{{ super() }} - -{% endblock %} diff --git a/listenbrainz/webserver/views/index.py b/listenbrainz/webserver/views/index.py index 73ec330460..d26fa518ae 100644 --- a/listenbrainz/webserver/views/index.py +++ b/listenbrainz/webserver/views/index.py @@ -32,11 +32,8 @@ SEARCH_USER_LIMIT = 100 # max number of users to return in search username results -@index_bp.route("/") +@index_bp.route("/", methods=['POST']) def index(): - if current_user.is_authenticated and request.args.get("redirect", "true") == "true": - return redirect(url_for("user.index", path="", user_name=current_user.musicbrainz_id)) - if _ts: try: listen_count = _ts.get_total_listen_count() @@ -55,14 +52,11 @@ def index(): current_app.logger.error('Error while trying to get total artist count: %s', str(e)) props = { - "listen_count": listen_count, - "artist_count": artist_count, + "listenCount": listen_count, + "artistCount": artist_count, } - return render_template( - "index/index.html", - props=orjson.dumps(props).decode("utf-8") - ) + return jsonify(props) @index_bp.route("/import/") diff --git a/webpack.config.js b/webpack.config.js index 753f4846d9..16a4710b0f 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -59,7 +59,6 @@ module.exports = function (env, argv) { path.resolve(cssDir, "main.less"), ], AIBrainz: [path.resolve(jsDir, "src/explore/ai-brainz/AIBrainz.tsx")], - homepage: path.resolve(jsDir, "src/home/Homepage.tsx"), recommendationsPlayground: path.resolve( jsDir, "src/recommended/tracks/Recommendations.tsx" From a4b69c434ab16b12b6804b02cb2f0d5fd36d68f5 Mon Sep 17 00:00:00 2001 From: anshg1214 Date: Thu, 15 Feb 2024 22:23:37 +0000 Subject: [PATCH 016/104] refactor: misc --- frontend/js/src/common/brainzplayer/ProgressBar.tsx | 4 +++- frontend/js/src/search/UserSearch.tsx | 4 ++-- frontend/js/src/settings/delete-listens/DeleteListens.tsx | 5 ++++- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/frontend/js/src/common/brainzplayer/ProgressBar.tsx b/frontend/js/src/common/brainzplayer/ProgressBar.tsx index 393044c612..68c793de3e 100644 --- a/frontend/js/src/common/brainzplayer/ProgressBar.tsx +++ b/frontend/js/src/common/brainzplayer/ProgressBar.tsx @@ -25,7 +25,9 @@ const TOOLTIP_TOP_OFFSET: number = 102; function ProgressBar(props: ProgressBarProps) { const { durationMs, progressMs, seekToPositionMs } = props; const [tipContent, setTipContent] = React.useState(TOOLTIP_INITIAL_CONTENT); - const progressPercentage = Number((progressMs * 100 / durationMs).toFixed(2)); + const progressPercentage = Number( + ((progressMs * 100) / durationMs).toFixed(2) + ); const hideProgressBar = isNaN(progressPercentage) || progressPercentage <= 0; // Originally by ford04 - https://stackoverflow.com/a/62017005 diff --git a/frontend/js/src/search/UserSearch.tsx b/frontend/js/src/search/UserSearch.tsx index 651fedc63b..b9c1267aab 100644 --- a/frontend/js/src/search/UserSearch.tsx +++ b/frontend/js/src/search/UserSearch.tsx @@ -1,5 +1,5 @@ import * as React from "react"; -import { useLoaderData, useNavigate } from "react-router-dom"; +import { Link, useLoaderData, useNavigate } from "react-router-dom"; import GlobalAppContext from "../utils/GlobalAppContext"; type SearchResultsLoaderData = { @@ -74,7 +74,7 @@ export default function SearchResults() { {index + 1} - {row[0]} + {row[0]} {username && ( diff --git a/frontend/js/src/settings/delete-listens/DeleteListens.tsx b/frontend/js/src/settings/delete-listens/DeleteListens.tsx index a219733546..6823db1e3f 100644 --- a/frontend/js/src/settings/delete-listens/DeleteListens.tsx +++ b/frontend/js/src/settings/delete-listens/DeleteListens.tsx @@ -81,7 +81,10 @@ export default function DeleteListens() {

Once deleted, all your listens data will be removed PERMANENTLY.

- Warning: if you are still connected to Spotify, the last 50 Spotify tracks might be auto-reimported. Disconnect before deleting. + Warning: if you are still connected to Spotify, the last 50 Spotify + tracks might be auto-reimported.{" "} + Disconnect before + deleting.

From cce8cc83f63677cbdfe8cc200857ca8022213a7e Mon Sep 17 00:00:00 2001 From: anshg1214 Date: Thu, 15 Feb 2024 22:31:36 +0000 Subject: [PATCH 017/104] refactor: cleanup --- listenbrainz/webserver/views/index.py | 23 +++-------------------- 1 file changed, 3 insertions(+), 20 deletions(-) diff --git a/listenbrainz/webserver/views/index.py b/listenbrainz/webserver/views/index.py index d26fa518ae..26daa176d3 100644 --- a/listenbrainz/webserver/views/index.py +++ b/listenbrainz/webserver/views/index.py @@ -59,14 +59,6 @@ def index(): return jsonify(props) -@index_bp.route("/import/") -def import_data(): - if current_user.is_authenticated: - return redirect(url_for("settings.index", path='import/')) - else: - return current_app.login_manager.unauthorized() - - @index_bp.route("/blog-data/") def blog_data(): """Proxy to the MetaBrainz blog to get recent posts so that user IP addresses are not leaked to wordpress""" @@ -124,11 +116,8 @@ def current_status(): return jsonify(data) -@index_bp.route("/recent/", methods=['GET', 'POST']) +@index_bp.route("/recent/", methods=['POST']) def recent_listens(): - if request.method == 'GET': - return render_template('index.html') - recent = [] for listen in _redis.get_recent_listens(NUMBER_OF_RECENT_LISTENS): recent.append({ @@ -153,13 +142,6 @@ def recent_listens(): return jsonify(props) -@index_bp.route('/feed/', methods=['GET', 'OPTIONS']) -@login_required -@web_listenstore_needed -def feed(): - return render_template('index.html') - - @index_bp.route('/agree-to-terms/', methods=['GET', 'POST']) @login_required def gdpr_notice(): @@ -260,5 +242,6 @@ def _get_user_count(): @index_bp.route("/", defaults={'path': ''}) @index_bp.route('//') +@web_listenstore_needed def index_pages(path): - return render_template("index.html") \ No newline at end of file + return render_template("index.html") From 7b7ec68c8dd9e576429280f37b21a4d07522533d Mon Sep 17 00:00:00 2001 From: anshg1214 Date: Fri, 16 Feb 2024 06:42:04 +0000 Subject: [PATCH 018/104] feat: Move Login to SPA --- frontend/js/src/components/Navbar.tsx | 4 +- frontend/js/src/gdpr/GDPR.tsx | 107 ++++++++++++++++++ frontend/js/src/login/Login.tsx | 73 ++++++++++++ frontend/js/src/routes/index.tsx | 10 ++ frontend/js/src/utils/ProtectedRoutes.tsx | 11 +- listenbrainz/webserver/__init__.py | 3 +- .../webserver/templates/index/gdpr.html | 44 ------- .../webserver/templates/login/login.html | 40 ------- listenbrainz/webserver/views/index.py | 13 +-- listenbrainz/webserver/views/login.py | 6 +- 10 files changed, 210 insertions(+), 101 deletions(-) create mode 100644 frontend/js/src/gdpr/GDPR.tsx create mode 100644 frontend/js/src/login/Login.tsx delete mode 100644 listenbrainz/webserver/templates/index/gdpr.html delete mode 100644 listenbrainz/webserver/templates/login/login.html diff --git a/frontend/js/src/components/Navbar.tsx b/frontend/js/src/components/Navbar.tsx index 0bb893d3b4..79b66079ed 100644 --- a/frontend/js/src/components/Navbar.tsx +++ b/frontend/js/src/components/Navbar.tsx @@ -94,7 +94,7 @@ function Navbar() { {currentUser?.name ? ( <>

{currentUser.name}
- Logout + Logout ) : ( - Sign in + Sign in )} About diff --git a/frontend/js/src/gdpr/GDPR.tsx b/frontend/js/src/gdpr/GDPR.tsx new file mode 100644 index 0000000000..50a1280cac --- /dev/null +++ b/frontend/js/src/gdpr/GDPR.tsx @@ -0,0 +1,107 @@ +import * as React from "react"; +import { Helmet } from "react-helmet"; +import { useLocation, useNavigate } from "react-router-dom"; +import { toast } from "react-toastify"; + +export default function GDPR() { + const next = new URLSearchParams(window.location.search).get("next"); + const navigate = useNavigate(); + const location = useLocation(); + + const onFormSubmit = async (event: React.FormEvent) => { + event.preventDefault(); + const formData = new FormData(event.currentTarget); + const gdprOption = formData.get("gdpr-options"); + if (gdprOption === "agree") { + try { + const response = await fetch(location.pathname, { + method: "POST", + body: formData, + }); + if (response.ok) { + if (next) { + navigate(next); + } else { + navigate("/"); + } + } + } catch (error) { + toast.error("An error occurred while processing your request."); + } + } + if (gdprOption === "disagree") { + navigate("/settings/delete/"); + } + }; + + return ( +
+ + Agree to Terms + +

+ Agree to General Data Protection Regulations +

+
+
+

+ Important! +

+

+ By signing into ListenBrainz, you grant the MetaBrainz Foundation + permission to include your listening history in data dumps we make + publicly available under the + + CC0 license + + . None of your private information from your user profile will be + included in these data dumps. +

+

+ Furthermore, you grant the MetaBrainz Foundation permission to + process your listening history and include it in new open source + tools such as recommendation engines that the ListenBrainz project + is building. For details on processing your listening history, + please see our{" "} + GDPR compliance statement. +

+

+ If you change your mind about processing your listening history, you + will need to{" "} + delete your ListenBrainz account. +

+ {" "} + +
+ {" "} + +
+ {next && } +
+
+
+ +
+
+
+ ); +} diff --git a/frontend/js/src/login/Login.tsx b/frontend/js/src/login/Login.tsx new file mode 100644 index 0000000000..fbca3ea558 --- /dev/null +++ b/frontend/js/src/login/Login.tsx @@ -0,0 +1,73 @@ +import * as React from "react"; +import { useNavigate } from "react-router-dom"; +import { Helmet } from "react-helmet"; +import GlobalAppContext from "../utils/GlobalAppContext"; + +export default function Login() { + const navigate = useNavigate(); + const next = new URLSearchParams(window.location.search).get("next"); + const { currentUser } = React.useContext(GlobalAppContext); + + // If the user is already logged in, redirect them to their profile page + React.useEffect(() => { + if (currentUser?.name) { + navigate(`/user/${currentUser.name}`); + } + }, [currentUser, navigate]); + + return ( +
+ + Sign in + +

Sign in

+ +

+ To sign in, use your MusicBrainz account, and authorize ListenBrainz to + access your profile data. +

+ +
+

+ Important! +

+

+ By signing into ListenBrainz, you grant the MetaBrainz Foundation + permission to include your listening history in data dumps we make + publicly available under the{" "} + + CC0 license + + . None of your private information from your user profile will be + included in these data dumps. +

+

+ Furthermore, you grant the MetaBrainz Foundation permission to process + your listening history and include it in new open source tools such as + recommendation engines that the ListenBrainz project is building. For + details on processing your listening history, please see our{" "} + GDPR compliance statement. +

+

+ In order to combat spammers and to be able to contact our users in + case something goes wrong with the listen submission process, we now + require an email address when creating a ListenBrainz account. +

+

+ If after creating an account you change your mind about processing + your listening history, you will need to{" "} + delete your ListenBrainz account. +

+
+
+ +
+ ); +} diff --git a/frontend/js/src/routes/index.tsx b/frontend/js/src/routes/index.tsx index 600f0102e8..6cb9b9ca09 100644 --- a/frontend/js/src/routes/index.tsx +++ b/frontend/js/src/routes/index.tsx @@ -21,6 +21,8 @@ import { } from "../recent/RecentListens"; import UserFeedLayout from "../user-feed/UserFeedLayout"; import HomePage from "../home/Homepage"; +import Login from "../login/Login"; +import GDPR from "../gdpr/GDPR"; const getIndexRoutes = () => { const routes = [ @@ -33,6 +35,14 @@ const getIndexRoutes = () => { element: , loader: RouteLoader, }, + { + path: "login/", + element: , + }, + { + path: "agree-to-terms/", + element: , + }, { path: "import-data/", element: , diff --git a/frontend/js/src/utils/ProtectedRoutes.tsx b/frontend/js/src/utils/ProtectedRoutes.tsx index 73ae658149..09362a1a27 100644 --- a/frontend/js/src/utils/ProtectedRoutes.tsx +++ b/frontend/js/src/utils/ProtectedRoutes.tsx @@ -1,13 +1,20 @@ import React from "react"; -import { Navigate, Outlet } from "react-router-dom"; +import { Navigate, useLocation } from "react-router-dom"; import GlobalAppContext from "./GlobalAppContext"; import Layout from "../layout"; function ProtectedRoutes() { const { currentUser } = React.useContext(GlobalAppContext); + const location = useLocation(); + const { pathname } = location; + const urlEncodedPathname = encodeURIComponent(pathname); - return currentUser?.name ? : ; + return currentUser?.name ? ( + + ) : ( + + ); } export default ProtectedRoutes; diff --git a/listenbrainz/webserver/__init__.py b/listenbrainz/webserver/__init__.py index d2b1b390dc..a539cc1637 100644 --- a/listenbrainz/webserver/__init__.py +++ b/listenbrainz/webserver/__init__.py @@ -246,7 +246,8 @@ def before_request_gdpr_check(): or request.path == url_for('settings.index', path='export') \ or request.path == url_for('login.logout') \ or request.path.startswith('/static') \ - or request.path.startswith('/1'): + or request.path.startswith('/1') \ + or request.method in ['OPTIONS', 'POST']: return # otherwise if user is logged in and hasn't agreed to gdpr, # redirect them to agree to terms page. diff --git a/listenbrainz/webserver/templates/index/gdpr.html b/listenbrainz/webserver/templates/index/gdpr.html deleted file mode 100644 index f98fe9f239..0000000000 --- a/listenbrainz/webserver/templates/index/gdpr.html +++ /dev/null @@ -1,44 +0,0 @@ -{% extends 'base.html' %} - -{% block title %}Agree to Terms - ListenBrainz{% endblock %} - -{% block content %} -
- -

Agree to General Data Protection Regulations

- -
-

Important!

-

- By signing into ListenBrainz, you grant the MetaBrainz Foundation permission to include your listening history - in data dumps we make publicly available under the - CC0 license. None of your private information - from your user profile will be included in these data dumps. -

-

- Furthermore, you grant the MetaBrainz Foundation permission to process your listening history and include it - in new open source tools such as recommendation engines that the ListenBrainz project is building. For details - on processing your listening history, please see our GDPR compliance - statement. -

-

- If you change your mind about processing your listening history, you will need to - delete your ListenBrainz account. -

- -
- -
- -
- {% if request.args.get('next') %} - - {% endif %} -
-
-
-
- -
-{% endblock %} diff --git a/listenbrainz/webserver/templates/login/login.html b/listenbrainz/webserver/templates/login/login.html deleted file mode 100644 index 6d053136c1..0000000000 --- a/listenbrainz/webserver/templates/login/login.html +++ /dev/null @@ -1,40 +0,0 @@ -{% extends 'base.html' %} - -{% block title %}Sign in - ListenBrainz{% endblock %} - -{% block content %} -
-

Sign in

- -

To sign in, use your MusicBrainz account, and authorize ListenBrainz to access your profile data.

- -
-

Important!

-

- By signing into ListenBrainz, you grant the MetaBrainz Foundation permission to include your listening history - in data dumps we make publicly available under the - CC0 license. None of your private information - from your user profile will be included in these data dumps. -

-

- Furthermore, you grant the MetaBrainz Foundation permission to process your listening history and include it - in new open source tools such as recommendation engines that the ListenBrainz project is building. For details - on processing your listening history, please see our GDPR compliance - statement. -

-

- In order to combat spammers and to be able to contact our users in case something goes wrong with the listen - submission process, we now require an email address when creating a ListenBrainz account. -

-

- If after creating an account you change your mind about processing your listening history, you will need to - delete your ListenBrainz account. -

-
-
- -
-{% endblock %} diff --git a/listenbrainz/webserver/views/index.py b/listenbrainz/webserver/views/index.py index 26daa176d3..84cc851e39 100644 --- a/listenbrainz/webserver/views/index.py +++ b/listenbrainz/webserver/views/index.py @@ -146,22 +146,16 @@ def recent_listens(): @login_required def gdpr_notice(): if request.method == 'GET': - return render_template('index/gdpr.html', next=request.args.get('next')) + return render_template('index.html') elif request.method == 'POST': if request.form.get('gdpr-options') == 'agree': try: db_user.agree_to_gdpr(db_conn, current_user.musicbrainz_id) except DatabaseException as e: flash.error('Could not store agreement to GDPR terms') - next = request.form.get('next') - if next: - return redirect(next) - return redirect(url_for('index.index')) - elif request.form.get('gdpr-options') == 'disagree': - return redirect(url_for('settings.index', path='delete')) + return jsonify({'status': 'agreed'}) else: - flash.error('You must agree to or decline our terms') - return render_template('index/gdpr.html', next=request.args.get('next')) + return jsonify({'status': 'not_agreed'}), 400 @index_bp.route('/search/', methods=['POST', 'OPTIONS']) @@ -244,4 +238,5 @@ def _get_user_count(): @index_bp.route('//') @web_listenstore_needed def index_pages(path): + current_app.logger.warn("USER landed on page %s", path) return render_template("index.html") diff --git a/listenbrainz/webserver/views/login.py b/listenbrainz/webserver/views/login.py index 5eb7608d14..de3a2346ed 100644 --- a/listenbrainz/webserver/views/login.py +++ b/listenbrainz/webserver/views/login.py @@ -18,7 +18,7 @@ @web_listenstore_needed @login_forbidden def index(): - return render_template('login/login.html') + return render_template('index.html') @login_bp.route('/musicbrainz/') @@ -65,7 +65,7 @@ def musicbrainz_post(): flash.error(no_email_warning + 'before creating a ListenBrainz account. ' + blog_link) else: flash.error("Login failed.") - return redirect(url_for('index.index')) + return redirect(url_for('index.index_pages', path='')) @login_bp.route('/logout/') @@ -73,4 +73,4 @@ def musicbrainz_post(): def logout(): session.clear() logout_user() - return redirect(url_for('index.index')) + return redirect(url_for('index.index_pages', path='')) From 85b774e358a781b48777f72027ab64c2b09c6369 Mon Sep 17 00:00:00 2001 From: anshg1214 Date: Fri, 16 Feb 2024 06:42:24 +0000 Subject: [PATCH 019/104] feat: Add Layout for ErrorBoundary --- frontend/js/src/layout/index.tsx | 3 ++- frontend/js/src/routes/routes.tsx | 11 ++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/frontend/js/src/layout/index.tsx b/frontend/js/src/layout/index.tsx index e032a8fa7f..4963d7669f 100644 --- a/frontend/js/src/layout/index.tsx +++ b/frontend/js/src/layout/index.tsx @@ -4,7 +4,7 @@ import { Outlet, ScrollRestoration } from "react-router-dom"; import Footer from "../components/Footer"; import Navbar from "../components/Navbar"; -export default function Layout() { +export default function Layout({ children }: { children?: React.ReactNode }) { return ( <> @@ -12,6 +12,7 @@ export default function Layout() {
+ {children}
diff --git a/frontend/js/src/routes/routes.tsx b/frontend/js/src/routes/routes.tsx index 51952cff0c..50bbb4e67d 100644 --- a/frontend/js/src/routes/routes.tsx +++ b/frontend/js/src/routes/routes.tsx @@ -28,7 +28,11 @@ const getRoutes = (musicbrainzID?: string) => { { path: "/", element: , - errorElement: , + errorElement: ( + + + + ), children: [ ...indexRoutes, ...aboutRoutes, @@ -41,6 +45,11 @@ const getRoutes = (musicbrainzID?: string) => { }, { element: , + errorElement: ( + + + + ), children: [...settingsRoutes, ...settingsRedirectRoutes], }, ]; From 286548da14a87e3d9a9bb4aece778835b4e5c2ff Mon Sep 17 00:00:00 2001 From: anshg1214 Date: Fri, 16 Feb 2024 08:10:37 +0000 Subject: [PATCH 020/104] fix: Searching user reloads page --- frontend/js/src/components/Navbar.tsx | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/frontend/js/src/components/Navbar.tsx b/frontend/js/src/components/Navbar.tsx index 79b66079ed..7aec88df94 100644 --- a/frontend/js/src/components/Navbar.tsx +++ b/frontend/js/src/components/Navbar.tsx @@ -1,13 +1,15 @@ import React from "react"; -import { Link, useLocation } from "react-router-dom"; +import { Link, useLocation, useNavigate } from "react-router-dom"; import GlobalAppContext from "../utils/GlobalAppContext"; function Navbar() { const { currentUser } = React.useContext(GlobalAppContext); const location = useLocation(); + const navigate = useNavigate(); const [activePage, setActivePage] = React.useState(""); const [myProfile, setMyProfile] = React.useState(false); + const [searchTerm, setSearchTerm] = React.useState(""); React.useEffect(() => { const path = location.pathname.split("/")[1]; if (path === "user") { @@ -16,6 +18,16 @@ function Navbar() { setActivePage(path); }, [location.pathname, currentUser?.name]); + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + const searchInput = searchTerm; + if (!searchInput) { + return; + } + setSearchTerm(""); + navigate(`/search/?search_term=${searchInput}`); + }; + return (