Agregada vista de artista
This commit is contained in:
2
TODO.md
2
TODO.md
@@ -1 +1 @@
|
|||||||
Searchbar tendria que hacer callback de la query que se ejecuto, no ejecutarla el mismo
|
Transition para cosas a las que hago hover para hacer un efecto mas suave
|
||||||
|
|||||||
71
src/components/Artist.jsx
Normal file
71
src/components/Artist.jsx
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
import React, {useEffect, useState} from 'react'
|
||||||
|
import {getArtist, getArtistDiscs} from "../services/entity_service";
|
||||||
|
import {CoverWithCaption} from './CoverArt';
|
||||||
|
import ReactJson from "react-json-view";
|
||||||
|
|
||||||
|
export const Discs = (props) => {
|
||||||
|
const discs = props.discs ? props.discs.discs : null;
|
||||||
|
const paginate = props.discs ? props.discs.paginate : null;
|
||||||
|
|
||||||
|
if (discs) {
|
||||||
|
return (
|
||||||
|
<div className='discs'>
|
||||||
|
<h2>Discos</h2>
|
||||||
|
<ul className={'discs-list'}>
|
||||||
|
{discs.map((disc, index) => {
|
||||||
|
return (
|
||||||
|
<div className='cover-container'>
|
||||||
|
<CoverWithCaption key={index} cover_art={disc.cover_art} alt={`Cover art del disco ${disc.title}`} caption={disc.title}/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
} else return <></>
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Artist = (props) => {
|
||||||
|
const artist = props.artist;
|
||||||
|
if (artist) {
|
||||||
|
return (
|
||||||
|
<div className='artist'>
|
||||||
|
<div className='space-between'>
|
||||||
|
<div className='title'>
|
||||||
|
<h1>{artist.name}</h1>
|
||||||
|
<h4>{[artist.type, artist.country].filter(Boolean).join(' - ')}</h4>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<button className='button'>Agregar a mi Lista</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ul className='tags'>
|
||||||
|
{artist.tags.map((tag, index) => (<li key={index}>{tag.name}</li>))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return <></>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ArtistView = (props) => {
|
||||||
|
const [artist, setArtist] = useState(null);
|
||||||
|
const [discs, setDiscs] = useState(null);
|
||||||
|
const mbid = props.match.params.mbid;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (mbid) {
|
||||||
|
getArtist(mbid).then((result) => setArtist(result));
|
||||||
|
getArtistDiscs(mbid, 15).then((result) => setDiscs(result));
|
||||||
|
}
|
||||||
|
}, [mbid])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='artist-view'>
|
||||||
|
<Artist artist={artist}/>
|
||||||
|
<Discs discs={discs}/>
|
||||||
|
{!mbid && <p>AHH</p>}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
33
src/components/CoverArt.jsx
Normal file
33
src/components/CoverArt.jsx
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import React, {Fragment, useState} from "react";
|
||||||
|
import {ReactComponent as DiscSVG} from "../svg/disc.svg";
|
||||||
|
|
||||||
|
export const CoverArt = (props) => {
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
|
const handleLoad = () => setLoading(false)
|
||||||
|
|
||||||
|
if (props.cover_art) {
|
||||||
|
if (loading) {
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
<img src={props.cover_art.image} className={'coverart loading'} alt={props.alt} onLoad={handleLoad}/>
|
||||||
|
<div class={'coverart pulsating'}/>
|
||||||
|
</Fragment>
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return <img src={props.cover_art.image} className={'coverart'} alt={props.alt}/>
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return <DiscSVG className='coverart'/>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const CoverWithCaption = (props) => {
|
||||||
|
return(
|
||||||
|
<figure className='cover-caption'>
|
||||||
|
<CoverArt cover_art={props.cover_art} alt={props.alt}/>
|
||||||
|
<figcaption>{props.caption}</figcaption>
|
||||||
|
</figure>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -4,31 +4,10 @@ import queryString from "query-string";
|
|||||||
import {searchArtist, searchDisc, searchSong} from "../services/search_service";
|
import {searchArtist, searchDisc, searchSong} from "../services/search_service";
|
||||||
import {SearchBar} from "./SearchBar";
|
import {SearchBar} from "./SearchBar";
|
||||||
import {Paginate} from "./Paginate";
|
import {Paginate} from "./Paginate";
|
||||||
|
import {CoverArt} from "./CoverArt";
|
||||||
import {Link} from "react-router-dom";
|
import {Link} from "react-router-dom";
|
||||||
import {Tab, TabList, TabPanel, Tabs} from "react-tabs";
|
import {Tab, TabList, TabPanel, Tabs} from "react-tabs";
|
||||||
|
|
||||||
import {ReactComponent as DiscSVG} from "../svg/disc.svg";
|
|
||||||
|
|
||||||
const CoverArt = (props) => {
|
|
||||||
const [loading, setLoading] = useState(true);
|
|
||||||
|
|
||||||
const handleLoad = () => setLoading(false)
|
|
||||||
|
|
||||||
if (props.cover_art) {
|
|
||||||
if (loading) {
|
|
||||||
return (
|
|
||||||
<Fragment>
|
|
||||||
<img src={props.cover_art.image} className={'coverart loading'} alt={props.alt} onLoad={handleLoad}/>
|
|
||||||
<DiscSVG className='coverart'/>
|
|
||||||
</Fragment>
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
return <img src={props.cover_art.image} className={'coverart'} alt={props.alt}/>
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return <DiscSVG className='coverart'/>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const SearchPlaceholder = (props) => {
|
const SearchPlaceholder = (props) => {
|
||||||
return (
|
return (
|
||||||
@@ -109,7 +88,9 @@ const SearchDisc = (props) => {
|
|||||||
return (
|
return (
|
||||||
<li className='disc'>
|
<li className='disc'>
|
||||||
<Link to={`/disc/${disc.id}`}>
|
<Link to={`/disc/${disc.id}`}>
|
||||||
<CoverArt cover_art={disc.cover_art} alt={`Cover del disco: ${disc.title}`}/>
|
<div className={'cover-container'}>
|
||||||
|
<CoverArt cover_art={disc.cover_art} alt={`Cover del disco: ${disc.title}`}/>
|
||||||
|
</div>
|
||||||
<div className='description'>
|
<div className='description'>
|
||||||
<span>{disc.title}</span>
|
<span>{disc.title}</span>
|
||||||
<span className='small'>{disc.artist[0].name}</span>
|
<span className='small'>{disc.artist[0].name}</span>
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import './styles/main.scss';
|
|||||||
import {Nav} from "./components/Nav";
|
import {Nav} from "./components/Nav";
|
||||||
|
|
||||||
import {BrowserRouter, Switch, Route} from "react-router-dom";
|
import {BrowserRouter, Switch, Route} from "react-router-dom";
|
||||||
|
import {ArtistView} from "./components/Artist";
|
||||||
|
|
||||||
const Main = (props) => {
|
const Main = (props) => {
|
||||||
const navigate = (query) => props.history.push(`/search?query=${query}`);
|
const navigate = (query) => props.history.push(`/search?query=${query}`);
|
||||||
@@ -36,6 +37,7 @@ const App = () => (
|
|||||||
<Nav/>
|
<Nav/>
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route path='/search/:who?' component={Search}/>
|
<Route path='/search/:who?' component={Search}/>
|
||||||
|
<Route path='/artist/:mbid?' component={ArtistView}/>
|
||||||
<Route exact path='/' component={Main}/>
|
<Route exact path='/' component={Main}/>
|
||||||
<Route path='*' component={NoRoute}/>
|
<Route path='*' component={NoRoute}/>
|
||||||
</Switch>
|
</Switch>
|
||||||
|
|||||||
16
src/services/entity_service.js
Normal file
16
src/services/entity_service.js
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
let baseUrl = `${process.env.REACT_APP_API_SERVER}/api/brainz`;
|
||||||
|
|
||||||
|
export async function getArtist(mbid) {
|
||||||
|
const url = `${baseUrl}/artist/${mbid}`;
|
||||||
|
const response = await axios.get(url);
|
||||||
|
return response.data
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getArtistDiscs(mbid, per_page=10) {
|
||||||
|
const url = `${baseUrl}/artist/${mbid}/discs?per_page=${per_page}`;
|
||||||
|
const response = await axios.get(url);
|
||||||
|
return response.data
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,15 +1,21 @@
|
|||||||
@import url('https://fonts.googleapis.com/css2?family=Work+Sans:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap');
|
@import url('https://fonts.googleapis.com/css2?family=Work+Sans:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap');
|
||||||
|
|
||||||
/* Colorscheme */
|
/* Variables */
|
||||||
:root {
|
:root {
|
||||||
--white: hsl(0, 0%, 99%);
|
--white: hsl(20, 20%, 98%);
|
||||||
--gray-1: hsl(0, 0%, 95%);
|
--gray-1: hsl(20, 20%, 95%);
|
||||||
--gray-2: hsl(0, 0%, 85%);
|
--gray-2: hsl(20, 20%, 85%);
|
||||||
--gray-3: hsl(0, 0%, 80%);
|
--gray-3: hsl(20, 20%, 80%);
|
||||||
--gray-4: hsl(0, 0%, 30%);
|
--gray-4: hsl(20, 20%, 30%);
|
||||||
--black: hsl(0, 0%, 20%);
|
--black: hsl(20, 20%, 20%);
|
||||||
|
|
||||||
--accent: hsl(354, 81%, 56%);
|
--accent: hsl(354, 81%, 56%);
|
||||||
|
|
||||||
|
--font-1: 4.209em;
|
||||||
|
--font-2: 3.157em;
|
||||||
|
--font-3: 2.369em;
|
||||||
|
--font-4: 1.777em;
|
||||||
|
--font-5: 1.333em;
|
||||||
|
--font-6: 0.75em;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Modificación básica de elementos*/
|
/* Modificación básica de elementos*/
|
||||||
@@ -22,6 +28,7 @@ body {
|
|||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
line-height: 1.65;
|
line-height: 1.65;
|
||||||
font-size: 100%;
|
font-size: 100%;
|
||||||
|
background-color: var(--white);
|
||||||
}
|
}
|
||||||
|
|
||||||
p {
|
p {
|
||||||
@@ -30,7 +37,7 @@ p {
|
|||||||
|
|
||||||
input {
|
input {
|
||||||
border: 1px var(--gray-2) solid;
|
border: 1px var(--gray-2) solid;
|
||||||
padding: .3em;
|
padding: .3em .5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
button {
|
||||||
@@ -60,18 +67,40 @@ h1, h2, h3, h4, h5 {
|
|||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
font-size: 4.209em;
|
font-size: var(--font-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
h2 {font-size: 3.157em;}
|
h2 {font-size: var(--font-2);}
|
||||||
|
|
||||||
h3 {font-size: 2.369em;}
|
h3 {font-size: var(--font-3);}
|
||||||
|
|
||||||
h4 {font-size: 1.777em;}
|
h4 {font-size: var(--font-4);}
|
||||||
|
|
||||||
h5 {font-size: 1.333em;}
|
h5 {font-size: var(--font-5);}
|
||||||
|
|
||||||
small, .text_small {font-size: 0.75em;}
|
small, .text_small {font-size: var(--font-6);}
|
||||||
|
|
||||||
|
button.button {
|
||||||
|
border: 3px solid var(--accent);
|
||||||
|
background-color: var(--white);
|
||||||
|
padding: .5em 1em;
|
||||||
|
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: var(--font-5);
|
||||||
|
color: var(--accent);
|
||||||
|
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--gray-1);
|
||||||
|
transition: .3s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
figure {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
/* Navbar */
|
/* Navbar */
|
||||||
.nav {
|
.nav {
|
||||||
@@ -169,6 +198,12 @@ ul.entity_list {
|
|||||||
border-bottom: 2px var(--gray-1) solid;
|
border-bottom: 2px var(--gray-1) solid;
|
||||||
margin-bottom: 1em;
|
margin-bottom: 1em;
|
||||||
|
|
||||||
|
.cover-container {
|
||||||
|
width: 200px;
|
||||||
|
height: 200px;
|
||||||
|
margin: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
&:last-child {
|
&:last-child {
|
||||||
margin-bottom: auto;
|
margin-bottom: auto;
|
||||||
}
|
}
|
||||||
@@ -208,18 +243,6 @@ ul.entity_list {
|
|||||||
height: 14em;
|
height: 14em;
|
||||||
|
|
||||||
a {
|
a {
|
||||||
.coverart {
|
|
||||||
object-fit: cover;
|
|
||||||
width: 200px;
|
|
||||||
height: 200px;
|
|
||||||
margin: 1em;
|
|
||||||
border: 1px solid var(--gray-2);
|
|
||||||
|
|
||||||
&.loading {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.release-date {
|
.release-date {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
font-size: .7em;
|
font-size: .7em;
|
||||||
@@ -238,24 +261,126 @@ ul.entity_list {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.cover-caption {
|
||||||
|
height: 250px;
|
||||||
|
width: 250px;
|
||||||
|
|
||||||
|
figcaption {
|
||||||
|
position: relative;
|
||||||
|
display: none;
|
||||||
|
z-index: 2;
|
||||||
|
height: 0;
|
||||||
|
background-color: var(--accent);
|
||||||
|
color: var(--white);
|
||||||
|
font-weight: 600;
|
||||||
|
padding: 1em;
|
||||||
|
opacity: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
figcaption{
|
||||||
|
display: block;
|
||||||
|
animation: slidein 300ms ease-in-out both;
|
||||||
|
|
||||||
|
@keyframes slidein {
|
||||||
|
to{
|
||||||
|
height: 80px;
|
||||||
|
transform: translateY(-80px);
|
||||||
|
opacity: 100;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.coverart {
|
||||||
|
object-fit: cover;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border: 1px solid var(--gray-2);
|
||||||
|
|
||||||
|
&.loading {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Tabs */
|
/* Tabs */
|
||||||
ul.tabs {
|
ul.tabs {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
border-bottom: 2px var(--gray-1) solid;
|
border-bottom: 2px var(--gray-1) solid;
|
||||||
margin: 1rem 0;
|
margin: 1rem 0;
|
||||||
|
|
||||||
li.tab {
|
li.tab {
|
||||||
padding: .5rem 1em;
|
padding: .5rem 1em;
|
||||||
margin-bottom: -2px;
|
margin-bottom: -2px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
border-bottom: 2px var(--gray-2) solid;
|
border-bottom: 2px var(--gray-2) solid;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.selected {
|
&.selected {
|
||||||
border-bottom: 2px var(--accent) solid;
|
border-bottom: 2px var(--accent) solid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Artists */
|
||||||
|
.artist-view {
|
||||||
|
.artist{
|
||||||
|
.title {
|
||||||
|
h1 { margin-bottom: 0}
|
||||||
|
h4 { margin-top: 0; margin-bottom: .5em }
|
||||||
|
max-width: 60%;
|
||||||
|
}
|
||||||
|
.button {
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
.tags {
|
||||||
|
display:flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
li {
|
||||||
|
border-bottom: 3px solid var(--accent);
|
||||||
|
margin-right: 1em;
|
||||||
|
line-height: 1;
|
||||||
|
margin-bottom: .5em;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
margin-bottom: 3em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.discs {
|
||||||
|
.discs-list {
|
||||||
|
display: grid;
|
||||||
|
grid-auto-rows: 250px;
|
||||||
|
gap: 30px;
|
||||||
|
justify-content: start;
|
||||||
|
align-content: start;
|
||||||
|
|
||||||
|
$grid-width: 250px;
|
||||||
|
$grid-gap: 16px;
|
||||||
|
|
||||||
|
|
||||||
|
@for $x from 1 to 5 {
|
||||||
|
@media (min-width: $grid-width * $x + $grid-gap * $x ) {
|
||||||
|
grid-template-columns: repeat($x, auto);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: $grid-width * 4 + $grid-gap * 4 ) {
|
||||||
|
justify-content: space-between;
|
||||||
|
align-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.cover-container {
|
||||||
|
width: 250px;
|
||||||
|
height: 250px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -282,3 +407,15 @@ ul.tabs {
|
|||||||
*[hidden] {
|
*[hidden] {
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.space-between {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
@media (max-width: 767px) {
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: flex-start;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user