Skip to content

Commit

Permalink
Merge pull request metabrainz#2866 from metabrainz/spa-fixes
Browse files Browse the repository at this point in the history
Misc SPA fixes, part 2
  • Loading branch information
MonkeyDo authored May 10, 2024
2 parents 4ed4476 + cf0e318 commit d10c144
Show file tree
Hide file tree
Showing 20 changed files with 212 additions and 280 deletions.
3 changes: 2 additions & 1 deletion frontend/js/src/error/ErrorBoundary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ export function ErrorBoundary() {
<Helmet>
<title>Error</title>
</Helmet>
<h2 className="page-title">Error Occured!</h2>
<h2 className="page-title">An error occured</h2>
<p>{errorMessage}</p>
</>
);
}
Expand Down
197 changes: 101 additions & 96 deletions frontend/js/src/explore/fresh-releases/components/ReleaseTimeline.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,95 @@ type ReleaseTimelineProps = {
direction: SortDirection;
};

function createMarks(
releases: Array<FreshReleaseItem>,
sortDirection: string,
order: string
) {
let dataArr: Array<string> = [];
let percentArr: Array<number> = [];
// We want to filter out the keys that have less than 1.5% of the total releases
const minReleasesThreshold = Math.floor(releases.length * 0.015);
if (order === "release_date") {
const releasesPerDate = countBy(
releases,
(item: FreshReleaseItem) => item.release_date
);
const filteredDates = Object.keys(releasesPerDate).filter(
(date) => releasesPerDate[date] >= minReleasesThreshold
);

dataArr = filteredDates.map((item) => formatReleaseDate(item));
percentArr = filteredDates
.map((item) => (releasesPerDate[item] / releases.length) * 100)
.map((_, index, arr) =>
arr.slice(0, index + 1).reduce((prev, curr) => prev + curr)
);
} else if (order === "artist_credit_name") {
const artistInitialsCount = countBy(releases, (item: FreshReleaseItem) =>
item.artist_credit_name.charAt(0).toUpperCase()
);
const filteredInitials = Object.keys(artistInitialsCount).filter(
(initial) => artistInitialsCount[initial] >= minReleasesThreshold
);

dataArr = filteredInitials.sort();
percentArr = filteredInitials
.map((item) => (artistInitialsCount[item] / releases.length) * 100)
.map((_, index, arr) =>
arr.slice(0, index + 1).reduce((prev, curr) => prev + curr)
);
} else if (order === "release_name") {
const releaseInitialsCount = countBy(releases, (item: FreshReleaseItem) =>
item.release_name.charAt(0).toUpperCase()
);
const filteredInitials = Object.keys(releaseInitialsCount).filter(
(initial) => releaseInitialsCount[initial] >= minReleasesThreshold
);

dataArr = filteredInitials.sort();
percentArr = filteredInitials
.map((item) => (releaseInitialsCount[item] / releases.length) * 100)
.map((_, index, arr) =>
arr.slice(0, index + 1).reduce((prev, curr) => prev + curr)
);
} else {
// conutBy gives us an asc-sorted Dict by confidence
const confidenceInitialsCount = countBy(
releases,
(item: FreshReleaseItem) => item?.confidence
);
dataArr = Object.keys(confidenceInitialsCount);
percentArr = Object.values(confidenceInitialsCount)
.map((item) => (item / releases.length) * 100)
.map((_, index, arr) =>
arr.slice(0, index + 1).reduce((prev, curr) => prev + curr)
);
}

if (sortDirection === "descend") {
dataArr.reverse();
percentArr = percentArr.reverse().map((v) => (v <= 100 ? 100 - v : 0));
}

/**
* We want the timeline dates or marks to start where the grid starts.
* So the 0% should always have the first date. Therefore we use unshift(0) here.
* With the same logic, we don't want the last date to be at 100% because
* that will mean we're at the bottom of the grid.
* The last date should start before 100%. That explains the pop().
* For descending sort, the reverse computation above possibly already ensures that
* the percentArr starts with 0 and ends with a non-100% value, which is desired.
* Hence, we add a check to skip the unshift(0) and pop() operations in that case.
*/
if (percentArr[0] !== 0) {
percentArr.unshift(0);
percentArr.pop();
}

return zipObject(percentArr, dataArr);
}

export default function ReleaseTimeline(props: ReleaseTimelineProps) {
const { releases, order, direction } = props;

Expand All @@ -29,108 +118,24 @@ export default function ReleaseTimeline(props: ReleaseTimelineProps) {
return scrollTo;
}, []);

function createMarks(data: Array<FreshReleaseItem>, sortDirection: string) {
let dataArr: Array<string> = [];
let percentArr: Array<number> = [];
// We want to filter out the keys that have less than 1.5% of the total releases
const minReleasesThreshold = Math.floor(data.length * 0.015);
if (order === "release_date") {
const releasesPerDate = countBy(
releases,
(item: FreshReleaseItem) => item.release_date
);
const filteredDates = Object.keys(releasesPerDate).filter(
(date) => releasesPerDate[date] >= minReleasesThreshold
);

dataArr = filteredDates.map((item) => formatReleaseDate(item));
percentArr = filteredDates
.map((item) => (releasesPerDate[item] / data.length) * 100)
.map((_, index, arr) =>
arr.slice(0, index + 1).reduce((prev, curr) => prev + curr)
);
} else if (order === "artist_credit_name") {
const artistInitialsCount = countBy(data, (item: FreshReleaseItem) =>
item.artist_credit_name.charAt(0).toUpperCase()
);
const filteredInitials = Object.keys(artistInitialsCount).filter(
(initial) => artistInitialsCount[initial] >= minReleasesThreshold
);

dataArr = filteredInitials.sort();
percentArr = filteredInitials
.map((item) => (artistInitialsCount[item] / data.length) * 100)
.map((_, index, arr) =>
arr.slice(0, index + 1).reduce((prev, curr) => prev + curr)
);
} else if (order === "release_name") {
const releaseInitialsCount = countBy(data, (item: FreshReleaseItem) =>
item.release_name.charAt(0).toUpperCase()
);
const filteredInitials = Object.keys(releaseInitialsCount).filter(
(initial) => releaseInitialsCount[initial] >= minReleasesThreshold
);

dataArr = filteredInitials.sort();
percentArr = filteredInitials
.map((item) => (releaseInitialsCount[item] / data.length) * 100)
.map((_, index, arr) =>
arr.slice(0, index + 1).reduce((prev, curr) => prev + curr)
);
} else {
// conutBy gives us an asc-sorted Dict by confidence
const confidenceInitialsCount = countBy(
data,
(item: FreshReleaseItem) => item?.confidence
);
dataArr = Object.keys(confidenceInitialsCount);
percentArr = Object.values(confidenceInitialsCount)
.map((item) => (item / data.length) * 100)
.map((_, index, arr) =>
arr.slice(0, index + 1).reduce((prev, curr) => prev + curr)
);
}

if (sortDirection === "descend") {
dataArr.reverse();
percentArr = percentArr.reverse().map((v) => (v <= 100 ? 100 - v : 0));
}

/**
* We want the timeline dates or marks to start where the grid starts.
* So the 0% should always have the first date. Therefore we use unshift(0) here.
* With the same logic, we don't want the last date to be at 100% because
* that will mean we're at the bottom of the grid.
* The last date should start before 100%. That explains the pop().
* For descending sort, the reverse computation above possibly already ensures that
* the percentArr starts with 0 and ends with a non-100% value, which is desired.
* Hence, we add a check to skip the unshift(0) and pop() operations in that case.
*/
if (percentArr[0] !== 0) {
percentArr.unshift(0);
percentArr.pop();
}

return zipObject(percentArr, dataArr);
}
React.useEffect(() => {
setMarks(createMarks(releases, direction, order));
}, [releases, direction, order]);

const handleScroll = React.useCallback(
debounce(() => {
// TODO change to relative position of #release-cards-grid instead of window
React.useEffect(() => {
const handleScroll = debounce(() => {
const container = document.getElementById("release-card-grids");
if (!container) {
return;
}
const scrollPos =
(window.scrollY / document.documentElement.scrollHeight) * 100;
((window.scrollY - container.offsetTop) / container.scrollHeight) * 100;
setCurrentValue(scrollPos);
}, 300),
[]
);
}, 500);

React.useEffect(() => {
setMarks(createMarks(releases, direction));
}, [releases, direction]);

React.useEffect(() => {
window.addEventListener("scroll", handleScroll);
return () => {
handleScroll.cancel();
window.removeEventListener("scroll", handleScroll);
};
}, []);
Expand Down
61 changes: 24 additions & 37 deletions frontend/js/src/user/stats/UserReports.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import { useLoaderData, useNavigate } from "react-router-dom";
import type { NavigateFunction } from "react-router-dom";
import { Helmet } from "react-helmet";

import ErrorBoundary from "../../utils/ErrorBoundary";
import Pill from "../../components/Pill";
import UserListeningActivity from "./components/UserListeningActivity";
import UserTopEntity from "./components/UserTopEntity";
Expand Down Expand Up @@ -170,58 +169,46 @@ export default class UserReports extends React.Component<
</a>
</small>
<section id="listening-activity">
<ErrorBoundary>
<UserListeningActivity range={range} apiUrl={apiUrl} user={user} />
</ErrorBoundary>
<UserListeningActivity range={range} apiUrl={apiUrl} user={user} />
</section>
<section id="top-entity">
<div className="row">
<div className="col-md-4">
<ErrorBoundary>
<UserTopEntity
range={range}
entity="artist"
apiUrl={apiUrl}
user={user}
terminology="artist"
/>
</ErrorBoundary>
<UserTopEntity
range={range}
entity="artist"
apiUrl={apiUrl}
user={user}
terminology="artist"
/>
</div>
<div className="col-md-4">
<ErrorBoundary>
<UserTopEntity
range={range}
entity="release-group"
apiUrl={apiUrl}
user={user}
terminology="album"
/>
</ErrorBoundary>
<UserTopEntity
range={range}
entity="release-group"
apiUrl={apiUrl}
user={user}
terminology="album"
/>
</div>
<div className="col-md-4">
<ErrorBoundary>
<UserTopEntity
range={range}
entity="recording"
apiUrl={apiUrl}
user={user}
terminology="track"
/>
</ErrorBoundary>
<UserTopEntity
range={range}
entity="recording"
apiUrl={apiUrl}
user={user}
terminology="track"
/>
</div>
</div>
</section>
{user && (
<section id="daily-activity">
<ErrorBoundary>
<UserDailyActivity range={range} apiUrl={apiUrl} user={user} />
</ErrorBoundary>
<UserDailyActivity range={range} apiUrl={apiUrl} user={user} />
</section>
)}
<section id="artist-origin">
<ErrorBoundary>
<UserArtistMap range={range} apiUrl={apiUrl} user={user} />
</ErrorBoundary>
<UserArtistMap range={range} apiUrl={apiUrl} user={user} />
</section>
</div>
);
Expand Down
15 changes: 5 additions & 10 deletions frontend/js/src/user/stats/components/UserArtistMap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,16 +98,11 @@ export default class UserArtistMap extends React.Component<
try {
return await this.APIService.getUserArtistMap(user?.name, range);
} catch (error) {
if (error.response && error.response.status === 204) {
this.setState({
loading: false,
hasError: true,
errorMessage:
"There are no statistics available for this user for this period",
});
} else {
throw error;
}
this.setState({
loading: false,
hasError: true,
errorMessage: error.message,
});
}
return {} as UserArtistMapResponse;
};
Expand Down
15 changes: 5 additions & 10 deletions frontend/js/src/user/stats/components/UserDailyActivity.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,16 +66,11 @@ export default class UserDailyActivity extends React.Component<
const data = await this.APIService.getUserDailyActivity(user.name, range);
return data;
} catch (error) {
if (error.response && error.response.status === 204) {
this.setState({
loading: false,
hasError: true,
errorMessage:
"There are no statistics available for this user for this period",
});
} else {
throw error;
}
this.setState({
loading: false,
hasError: true,
errorMessage: error.message,
});
}
return {} as UserDailyActivityResponse;
};
Expand Down
15 changes: 5 additions & 10 deletions frontend/js/src/user/stats/components/UserListeningActivity.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -133,16 +133,11 @@ export default class UserListeningActivity extends React.Component<
try {
return await this.APIService.getUserListeningActivity(user?.name, range);
} catch (error) {
if (error.response && error.response.status === 204) {
this.setState({
loading: false,
hasError: true,
errorMessage:
"There are no statistics available for this user for this period",
});
} else {
throw error;
}
this.setState({
loading: false,
hasError: true,
errorMessage: error.message,
});
}
return {} as UserListeningActivityResponse;
};
Expand Down
Loading

0 comments on commit d10c144

Please sign in to comment.