mirror of
https://github.com/AnimeThemes/animethemes-web.git
synced 2026-07-11 01:24:31 +02:00
feat: Added support for studios (#72)
* Added studio page and included links on anime pages. * Series and studios now have summary cards. * Added series and studio cards to the video page. * New search indices (currently hidden) for series and studios. * Fixed bug where icons would not show on big buttons.
This commit is contained in:
+14
-1
@@ -36,6 +36,11 @@ export async function createPages({ actions: { createPage }, graphql }) {
|
||||
slug
|
||||
}
|
||||
}
|
||||
allStudio {
|
||||
nodes {
|
||||
slug
|
||||
}
|
||||
}
|
||||
allArtist {
|
||||
nodes {
|
||||
slug
|
||||
@@ -103,6 +108,14 @@ export async function createPages({ actions: { createPage }, graphql }) {
|
||||
});
|
||||
}
|
||||
|
||||
for (const { slug } of data.allStudio.nodes) {
|
||||
createPage({
|
||||
path: `/studio/${slug}`,
|
||||
component: require.resolve("./src/templates/studio.js"),
|
||||
context: { slug }
|
||||
});
|
||||
}
|
||||
|
||||
for (const { slug } of data.allArtist.nodes) {
|
||||
createPage({
|
||||
path: `/artist/${slug}`,
|
||||
@@ -119,7 +132,7 @@ export async function createPages({ actions: { createPage }, graphql }) {
|
||||
});
|
||||
}
|
||||
|
||||
for (const entity of [ null, "anime", "theme", "artist" ]) {
|
||||
for (const entity of [ null, "anime", "theme", "artist", "series", "studio" ]) {
|
||||
createPage({
|
||||
path: `/search${entity ? `/${entity}` : ``}`,
|
||||
component: require.resolve("./src/templates/search.js"),
|
||||
|
||||
@@ -9,7 +9,8 @@ module.exports = ({ createResolvers }) => {
|
||||
...pivotResolver({
|
||||
series: { type: "AnimeSeries", from: "anime", to: "series" },
|
||||
resources: { type: "AnimeResource", from: "anime", to: "resource" },
|
||||
images: { type: "AnimeImage", from: "anime", to: "image" }
|
||||
images: { type: "AnimeImage", from: "anime", to: "image" },
|
||||
studios: { type: "AnimeStudio", from: "anime", to: "studio" }
|
||||
})
|
||||
},
|
||||
Artist: {
|
||||
@@ -43,6 +44,11 @@ module.exports = ({ createResolvers }) => {
|
||||
anime: { type: "AnimeSeries", from: "series", to: "anime" }
|
||||
})
|
||||
},
|
||||
Studio: {
|
||||
...pivotResolver({
|
||||
anime: { type: "AnimeStudio", from: "studio", to: "anime" }
|
||||
})
|
||||
},
|
||||
Video: {
|
||||
...enumResolver({
|
||||
overlap: videoOverlap,
|
||||
|
||||
@@ -17,6 +17,7 @@ module.exports = ({ actions }) => {
|
||||
series: [Series]
|
||||
resources: [Resource]
|
||||
images: [Image]
|
||||
studios: [Studio]
|
||||
}
|
||||
|
||||
type Synonym implements Node & Entity {
|
||||
@@ -93,7 +94,7 @@ module.exports = ({ actions }) => {
|
||||
type Series implements Node & Entity {
|
||||
idRaw: Int
|
||||
slug: String!
|
||||
name: String!
|
||||
name: String!
|
||||
anime: [Anime]
|
||||
}
|
||||
|
||||
@@ -109,6 +110,13 @@ module.exports = ({ actions }) => {
|
||||
link: String
|
||||
}
|
||||
|
||||
type Studio implements Node & Entity {
|
||||
idRaw: Int
|
||||
slug: String!
|
||||
name: String!
|
||||
anime: [Anime]
|
||||
}
|
||||
|
||||
type AnimeSeries implements Node {
|
||||
anime: Anime! @link(by: "id")
|
||||
series: Series! @link(by: "id")
|
||||
@@ -139,6 +147,11 @@ module.exports = ({ actions }) => {
|
||||
image: Image! @link(by: "id")
|
||||
}
|
||||
|
||||
type AnimeStudio implements Node {
|
||||
anime: Anime! @link(by: "id")
|
||||
studio: Studio! @link(by: "id")
|
||||
}
|
||||
|
||||
type Announcement implements Node & Entity {
|
||||
idRaw: Int
|
||||
content: String!
|
||||
|
||||
@@ -158,6 +158,15 @@ module.exports = async ({ actions, createNodeId, createContentDigest, reporter }
|
||||
}, "Image", helpers);
|
||||
}
|
||||
|
||||
for (const studio of await selectAllFrom("studios")) {
|
||||
createNodeFromData({
|
||||
id: studio.studio_id,
|
||||
idRaw: studio.studio_id,
|
||||
slug: studio.slug,
|
||||
name: studio.name
|
||||
}, "Studio", helpers);
|
||||
}
|
||||
|
||||
try {
|
||||
for (const announcement of await selectAllFrom("announcements")) {
|
||||
createNodeFromData({
|
||||
@@ -270,6 +279,18 @@ module.exports = async ({ actions, createNodeId, createContentDigest, reporter }
|
||||
}, "EntryVideo", helpers);
|
||||
}
|
||||
|
||||
for (const animeStudio of await selectAllFrom(
|
||||
"anime_studio", true,
|
||||
"anime", "anime_id", "anime_id",
|
||||
"studios", "studio_id", "studio_id"
|
||||
)) {
|
||||
createNodeFromData({
|
||||
id: `${animeStudio.anime_id}-${animeStudio.studio_id}`,
|
||||
anime: createNodeId(`Anime-${animeStudio.anime_id}`),
|
||||
studio: createNodeId(`Studio-${animeStudio.studio_id}`)
|
||||
}, "AnimeStudio", helpers);
|
||||
}
|
||||
|
||||
// await cache.set("last-fetched", now);
|
||||
};
|
||||
|
||||
|
||||
+1231
-14
File diff suppressed because it is too large
Load Diff
@@ -14,10 +14,12 @@ const StyledCover = styled.img.attrs({
|
||||
|
||||
export function SummaryCard({ title, description, image, to, children, ...props }) {
|
||||
return (
|
||||
<Card display="flex" flexDirection="row" alignItems="center" p={0} pr="1rem" {...props}>
|
||||
<Link to={to}>
|
||||
<StyledCover alt="Cover" src={image}/>
|
||||
</Link>
|
||||
<Card display="flex" flexDirection="row" alignItems="center" p={0} pr="1rem" height="64px" {...props}>
|
||||
{!!image && (
|
||||
<Link to={to}>
|
||||
<StyledCover alt="Cover" src={image} loading="lazy"/>
|
||||
</Link>
|
||||
)}
|
||||
<Flex flex={1} flexDirection="column" justifyContent="center" gapsColumn="0.25rem" px="1rem">
|
||||
<Text fontWeight="600" maxLines={1}>
|
||||
{typeof title === "string" ? (
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
import useEntitySearch from "hooks/useEntitySearch";
|
||||
import { SearchEntity } from "components/search";
|
||||
import { navigate } from "gatsby";
|
||||
import { AnimeSummaryCard } from "components/card";
|
||||
|
||||
const sortByFields = new Map([
|
||||
[ "Relevance", null ],
|
||||
@@ -53,7 +54,6 @@ export function SearchAnime({ searchQuery, locationState }) {
|
||||
return (
|
||||
<SearchEntity
|
||||
searchQuery={searchQuery}
|
||||
searchEntity="anime"
|
||||
filters={
|
||||
<>
|
||||
<SearchFilterFirstLetter value={filterFirstLetter} setValue={updateState("filterFirstLetter")}/>
|
||||
@@ -68,6 +68,7 @@ export function SearchAnime({ searchQuery, locationState }) {
|
||||
isFetchingNextPage={isFetchingNextPage}
|
||||
isLoading={isLoading}
|
||||
isPlaceholderData={isPlaceholderData}
|
||||
renderSummaryCard={(anime) => <AnimeSummaryCard key={anime.slug} anime={anime}/>}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { SearchFilterFirstLetter, SearchFilterSortBy } from "components/search-f
|
||||
import useEntitySearch from "hooks/useEntitySearch";
|
||||
import { SearchEntity } from "components/search";
|
||||
import { navigate } from "gatsby";
|
||||
import { ArtistSummaryCard } from "components/card";
|
||||
|
||||
const sortByFields = new Map([
|
||||
[ "Relevance", null ],
|
||||
@@ -42,7 +43,6 @@ export function SearchArtist({ searchQuery, locationState }) {
|
||||
return (
|
||||
<SearchEntity
|
||||
searchQuery={searchQuery}
|
||||
searchEntity="artist"
|
||||
filters={
|
||||
<>
|
||||
<SearchFilterFirstLetter value={filterFirstLetter} setValue={updateState("filterFirstLetter")}/>
|
||||
@@ -55,6 +55,7 @@ export function SearchArtist({ searchQuery, locationState }) {
|
||||
isFetchingNextPage={isFetchingNextPage}
|
||||
isLoading={isLoading}
|
||||
isPlaceholderData={isPlaceholderData}
|
||||
renderSummaryCard={(artist) => <ArtistSummaryCard key={artist.slug} artist={artist}/>}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,19 +3,18 @@ import { Button } from "components/button";
|
||||
import { Text } from "components/text";
|
||||
import { Box, Flex } from "components/box";
|
||||
import { Icon } from "components/icon";
|
||||
import { SearchResultList } from "components/search";
|
||||
import { SearchFilterGroup } from "components/search-filter";
|
||||
|
||||
export function SearchEntity({
|
||||
searchQuery,
|
||||
searchEntity,
|
||||
filters,
|
||||
data,
|
||||
fetchNextPage,
|
||||
hasNextPage,
|
||||
isFetchingNextPage,
|
||||
isLoading,
|
||||
isPlaceholderData
|
||||
isPlaceholderData,
|
||||
renderSummaryCard
|
||||
}) {
|
||||
return (
|
||||
<Box gapsColumn="1.5rem">
|
||||
@@ -50,7 +49,7 @@ export function SearchEntity({
|
||||
return (
|
||||
<>
|
||||
<Box gapsColumn="1rem">
|
||||
<SearchResultList results={results} entity={searchEntity}/>
|
||||
{results.map(renderSummaryCard)}
|
||||
</Box>
|
||||
{(hasNextPage || isPlaceholderData) && (
|
||||
<Flex justifyContent="center">
|
||||
|
||||
@@ -5,9 +5,9 @@ import { Text } from "components/text";
|
||||
import { Box, Flex } from "components/box";
|
||||
import { Link } from "gatsby";
|
||||
import { Icon } from "components/icon";
|
||||
import { SearchResultList } from "components/search";
|
||||
import { useQuery } from "react-query";
|
||||
import { fetchGlobalSearchResults } from "lib/search";
|
||||
import { AnimeSummaryCard, ArtistSummaryCard, ThemeSummaryCard } from "components/card";
|
||||
|
||||
export function SearchGlobal({ searchQuery }) {
|
||||
const fetchSearchResults = () => fetchGlobalSearchResults(
|
||||
@@ -48,14 +48,29 @@ export function SearchGlobal({ searchQuery }) {
|
||||
|
||||
return (
|
||||
<Box gapsColumn="1.5rem">
|
||||
<GlobalSearchSection entity="anime" title="Anime" results={animeResults}/>
|
||||
<GlobalSearchSection entity="theme" title="Themes" results={themeResults}/>
|
||||
<GlobalSearchSection entity="artist" title="Artist" results={artistResults}/>
|
||||
<GlobalSearchSection
|
||||
entity="anime"
|
||||
title="Anime"
|
||||
results={animeResults}
|
||||
renderSummaryCard={(anime) => <AnimeSummaryCard key={anime.slug} anime={anime}/>}
|
||||
/>
|
||||
<GlobalSearchSection
|
||||
entity="theme"
|
||||
title="Themes"
|
||||
results={themeResults}
|
||||
renderSummaryCard={(theme) => <ThemeSummaryCard key={theme.anime.slug + theme.slug} theme={theme}/>}
|
||||
/>
|
||||
<GlobalSearchSection
|
||||
entity="artist"
|
||||
title="Artist"
|
||||
results={artistResults}
|
||||
renderSummaryCard={(artist) => <ArtistSummaryCard key={artist.slug} artist={artist}/>}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
function GlobalSearchSection({ entity, title, results }) {
|
||||
function GlobalSearchSection({ entity, title, results, renderSummaryCard }) {
|
||||
const { search, hash } = useLocation();
|
||||
const urlSuffix = search + hash;
|
||||
|
||||
@@ -70,7 +85,7 @@ function GlobalSearchSection({ entity, title, results }) {
|
||||
<>
|
||||
<Text variant="h2">{title}</Text>
|
||||
<Box gapsColumn="1rem">
|
||||
<SearchResultList results={resultsPreview} entity={entity}/>
|
||||
{resultsPreview.map(renderSummaryCard)}
|
||||
</Box>
|
||||
{hasMoreResults && (
|
||||
<Flex justifyContent="center">
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
import { AnimeSummaryCard, ArtistSummaryCard, ThemeSummaryCard } from "components/card";
|
||||
|
||||
export function SearchResultList({ results, entity }) {
|
||||
return (
|
||||
<>
|
||||
{results.map((result) => {
|
||||
switch (entity) {
|
||||
case "anime":
|
||||
return <AnimeSummaryCard key={result.slug} anime={result}/>;
|
||||
case "theme":
|
||||
return <ThemeSummaryCard key={result.anime.slug + result.slug} theme={result}/>;
|
||||
case "artist":
|
||||
return <ArtistSummaryCard key={result.slug} artist={result}/>;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
})}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import { SearchFilterFirstLetter, SearchFilterSortBy } from "components/search-filter";
|
||||
import useEntitySearch from "hooks/useEntitySearch";
|
||||
import { SearchEntity } from "components/search";
|
||||
import { navigate } from "gatsby";
|
||||
import { SummaryCard } from "components/card";
|
||||
|
||||
const sortByFields = new Map([
|
||||
[ "Relevance", null ],
|
||||
[ "A ➜ Z", "name" ],
|
||||
[ "Z ➜ A", "-name" ],
|
||||
[ "Last Added", "-created_at" ]
|
||||
]);
|
||||
const sortByOptions = [ ...sortByFields.keys() ];
|
||||
|
||||
export function SearchSeries({ searchQuery, locationState }) {
|
||||
const filterFirstLetter = locationState?.filterFirstLetter || null;
|
||||
const sortBy = locationState?.sortBy || sortByOptions[0];
|
||||
|
||||
const updateState = (field) => (newValue) => {
|
||||
navigate("", {
|
||||
state: {
|
||||
...locationState,
|
||||
[field]: newValue
|
||||
},
|
||||
replace: true
|
||||
});
|
||||
};
|
||||
|
||||
const {
|
||||
data,
|
||||
fetchNextPage,
|
||||
hasNextPage,
|
||||
isFetchingNextPage,
|
||||
isLoading,
|
||||
isPlaceholderData
|
||||
} = useEntitySearch("series", searchQuery, {
|
||||
filters: {
|
||||
"name][like": filterFirstLetter ? `${filterFirstLetter}%` : null,
|
||||
},
|
||||
sortBy: sortByFields.get(sortBy)
|
||||
});
|
||||
|
||||
return (
|
||||
<SearchEntity
|
||||
searchQuery={searchQuery}
|
||||
filters={
|
||||
<>
|
||||
<SearchFilterFirstLetter value={filterFirstLetter} setValue={updateState("filterFirstLetter")}/>
|
||||
<SearchFilterSortBy options={sortByOptions} value={sortBy} setValue={updateState("sortBy")}/>
|
||||
</>
|
||||
}
|
||||
data={data}
|
||||
fetchNextPage={fetchNextPage}
|
||||
hasNextPage={hasNextPage}
|
||||
isFetchingNextPage={isFetchingNextPage}
|
||||
isLoading={isLoading}
|
||||
isPlaceholderData={isPlaceholderData}
|
||||
renderSummaryCard={(series) => <SummaryCard key={series.slug} title={series.name} description="Series" to={`/series/${series.slug}`} />}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import { SearchFilterFirstLetter, SearchFilterSortBy } from "components/search-filter";
|
||||
import useEntitySearch from "hooks/useEntitySearch";
|
||||
import { SearchEntity } from "components/search";
|
||||
import { navigate } from "gatsby";
|
||||
import { SummaryCard } from "components/card";
|
||||
|
||||
const sortByFields = new Map([
|
||||
[ "Relevance", null ],
|
||||
[ "A ➜ Z", "name" ],
|
||||
[ "Z ➜ A", "-name" ],
|
||||
[ "Last Added", "-created_at" ]
|
||||
]);
|
||||
const sortByOptions = [ ...sortByFields.keys() ];
|
||||
|
||||
export function SearchStudio({ searchQuery, locationState }) {
|
||||
const filterFirstLetter = locationState?.filterFirstLetter || null;
|
||||
const sortBy = locationState?.sortBy || sortByOptions[0];
|
||||
|
||||
const updateState = (field) => (newValue) => {
|
||||
navigate("", {
|
||||
state: {
|
||||
...locationState,
|
||||
[field]: newValue
|
||||
},
|
||||
replace: true
|
||||
});
|
||||
};
|
||||
|
||||
const {
|
||||
data,
|
||||
fetchNextPage,
|
||||
hasNextPage,
|
||||
isFetchingNextPage,
|
||||
isLoading,
|
||||
isPlaceholderData
|
||||
} = useEntitySearch("studio", searchQuery, {
|
||||
filters: {
|
||||
"name][like": filterFirstLetter ? `${filterFirstLetter}%` : null,
|
||||
},
|
||||
sortBy: sortByFields.get(sortBy)
|
||||
});
|
||||
|
||||
return (
|
||||
<SearchEntity
|
||||
searchQuery={searchQuery}
|
||||
filters={
|
||||
<>
|
||||
<SearchFilterFirstLetter value={filterFirstLetter} setValue={updateState("filterFirstLetter")}/>
|
||||
<SearchFilterSortBy options={sortByOptions} value={sortBy} setValue={updateState("sortBy")}/>
|
||||
</>
|
||||
}
|
||||
data={data}
|
||||
fetchNextPage={fetchNextPage}
|
||||
hasNextPage={hasNextPage}
|
||||
isFetchingNextPage={isFetchingNextPage}
|
||||
isLoading={isLoading}
|
||||
isPlaceholderData={isPlaceholderData}
|
||||
renderSummaryCard={(studio) => <SummaryCard key={studio.slug} title={studio.name} description="Studio" to={`/studio/${studio.slug}`} />}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import { SearchFilterSortBy, SearchFilterThemeType } from "components/search-fil
|
||||
import useEntitySearch from "hooks/useEntitySearch";
|
||||
import { SearchEntity } from "components/search";
|
||||
import { navigate } from "gatsby";
|
||||
import { ThemeSummaryCard } from "components/card";
|
||||
|
||||
const sortByFields = new Map([
|
||||
[ "Relevance", null ],
|
||||
@@ -40,7 +41,6 @@ export function SearchTheme({ searchQuery, locationState }) {
|
||||
return (
|
||||
<SearchEntity
|
||||
searchQuery={searchQuery}
|
||||
searchEntity="theme"
|
||||
filters={
|
||||
<>
|
||||
<SearchFilterThemeType value={filterType} setValue={updateState("filterType")}/>
|
||||
@@ -53,6 +53,7 @@ export function SearchTheme({ searchQuery, locationState }) {
|
||||
isFetchingNextPage={isFetchingNextPage}
|
||||
isLoading={isLoading}
|
||||
isPlaceholderData={isPlaceholderData}
|
||||
renderSummaryCard={(theme) => <ThemeSummaryCard key={theme.anime.slug + theme.slug} theme={theme}/>}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,4 +3,5 @@ export { SearchEntity } from "./SearchEntity";
|
||||
export { SearchAnime } from "./SearchAnime";
|
||||
export { SearchTheme } from "./SearchTheme";
|
||||
export { SearchArtist } from "./SearchArtist";
|
||||
export { SearchResultList } from "./SearchResultList";
|
||||
export { SearchSeries } from "./SearchSeries";
|
||||
export { SearchStudio } from "./SearchStudio";
|
||||
|
||||
@@ -88,6 +88,23 @@ const entityConfigs = {
|
||||
// TODO: Wait till we can specify deep field property lists
|
||||
// song: []
|
||||
}
|
||||
},
|
||||
series: {
|
||||
fields: {
|
||||
series: [
|
||||
"name",
|
||||
"slug"
|
||||
]
|
||||
}
|
||||
},
|
||||
studio: {
|
||||
plural: "studios",
|
||||
fields: {
|
||||
studio: [
|
||||
"name",
|
||||
"slug"
|
||||
]
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+35
-8
@@ -42,7 +42,7 @@ export default function DevelopmentPage({ data: { site, allAnnouncement } }) {
|
||||
)}
|
||||
</Text>
|
||||
<Text as="p">
|
||||
<span>This page is still activily being worked on. If you are a developer and interested in contributing feel free to contact us on </span>
|
||||
<span>This page is still actively being worked on. If you are a developer and interested in contributing feel free to contact us on </span>
|
||||
<ExternalLink href="https://discordapp.com/invite/m9zbVyQ">Discord</ExternalLink>
|
||||
<span>.</span>
|
||||
</Text>
|
||||
@@ -73,21 +73,30 @@ export default function DevelopmentPage({ data: { site, allAnnouncement } }) {
|
||||
<Box gapsColumn={["2rem", "1.5rem"]}>
|
||||
<PageGridItem
|
||||
path="/"
|
||||
description={
|
||||
<Text>
|
||||
<Text variant="small" color="text-primary" letterSpacing="0.1rem">NEW: </Text>
|
||||
The new home page of AnimeThemes.
|
||||
</Text>
|
||||
}
|
||||
/>
|
||||
<PageGridItem
|
||||
path="/dev"
|
||||
description="This page!"
|
||||
/>
|
||||
<PageGridItem
|
||||
path="/design"
|
||||
description={
|
||||
<Text>
|
||||
<Text variant="small" color="text-primary" letterSpacing="0.1rem">NEW: </Text>
|
||||
A page listing various design components!
|
||||
</Text>
|
||||
}
|
||||
description="A page listing various design components."
|
||||
isNew
|
||||
/>
|
||||
<PageGridItem
|
||||
path="/search"
|
||||
description="Search the AnimeThemes database. You can also use the search bar in the navigation."
|
||||
description="Search the AnimeThemes database."
|
||||
otherPaths={{
|
||||
"/search/anime": "Search only for anime or explore the list of all anime.",
|
||||
"/search/theme": "Search only for themes or explore the list of all themes.",
|
||||
"/search/artist": "Search only for artists or explore the list of all artists.",
|
||||
}}
|
||||
/>
|
||||
<PageGridItem
|
||||
path="/year"
|
||||
@@ -113,6 +122,15 @@ export default function DevelopmentPage({ data: { site, allAnnouncement } }) {
|
||||
"/series/fma": "Only two anime."
|
||||
}}
|
||||
/>
|
||||
<PageGridItem
|
||||
path="/studio/kyoto_animation"
|
||||
description={
|
||||
<Text>
|
||||
<Text variant="small" color="text-primary" letterSpacing="0.1rem">NEW: </Text>
|
||||
Browse all anime which were produced by the same studio.
|
||||
</Text>
|
||||
}
|
||||
/>
|
||||
<PageGridItem
|
||||
path="/artist/kana_hanazawa"
|
||||
description="Browse all songs an artist has performed."
|
||||
@@ -142,6 +160,15 @@ export default function DevelopmentPage({ data: { site, allAnnouncement } }) {
|
||||
"/anime/shingeki_no_kyojin_ova/ED1-NCBD1080": "See above."
|
||||
}}
|
||||
/>
|
||||
<PageGridItem
|
||||
path="/bracket/best-anime-opening-ix-salty-arrow"
|
||||
description={
|
||||
<Text>
|
||||
<Text variant="small" color="text-primary" letterSpacing="0.1rem">NEW: </Text>
|
||||
Explore every theme competing in one of the brackets hosted at AnimeBracket.
|
||||
</Text>
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
{!!process.env.GATSBY_CI && (
|
||||
<>
|
||||
|
||||
+4
-3
@@ -50,10 +50,11 @@ const BigButton = styled(Button).attrs({
|
||||
justify-content: flex-end;
|
||||
`;
|
||||
|
||||
const BigIcon = styled(Icon)`
|
||||
const BigIcon = styled(Icon).attrs({
|
||||
size: "4x"
|
||||
})`
|
||||
margin: 0 auto -1rem -2rem;
|
||||
|
||||
font-size: 4rem;
|
||||
color: ${theme.colors["text-disabled"]};
|
||||
`;
|
||||
|
||||
@@ -118,7 +119,7 @@ export default function HomePage({ data: { featuredTheme, recentlyAdded } }) {
|
||||
We provide high quality WebMs of your favorite OPs and EDs for your listening and discussion needs.
|
||||
</Text>
|
||||
<Text as="p">
|
||||
<span>This page is still activily being worked on. If you are a developer and interested in contributing feel free to contact us on </span>
|
||||
<span>This page is still actively being worked on. If you are a developer and interested in contributing feel free to contact us on </span>
|
||||
<ExternalLink href="https://discordapp.com/invite/m9zbVyQ">Discord</ExternalLink>
|
||||
<span>.</span>
|
||||
</Text>
|
||||
|
||||
+19
-2
@@ -49,7 +49,7 @@ export default function AnimeDetailPage({ data: { anime } }) {
|
||||
</Text>
|
||||
</Link>
|
||||
</DescriptionList.Item>
|
||||
{!!anime.series && !!anime.series.length && (
|
||||
{!!anime.series?.length && (
|
||||
<DescriptionList.Item title="Series">
|
||||
<StyledList>
|
||||
{anime.series.map((series) =>
|
||||
@@ -62,7 +62,20 @@ export default function AnimeDetailPage({ data: { anime } }) {
|
||||
</StyledList>
|
||||
</DescriptionList.Item>
|
||||
)}
|
||||
{!!anime.resources && !!anime.resources.length && (
|
||||
{!!anime.studios?.length && (
|
||||
<DescriptionList.Item title="Studios">
|
||||
<StyledList>
|
||||
{anime.studios.map((studio) =>
|
||||
<Link key={studio.slug} to={`/studio/${studio.slug}`}>
|
||||
<Text link>
|
||||
{studio.name}
|
||||
</Text>
|
||||
</Link>
|
||||
)}
|
||||
</StyledList>
|
||||
</DescriptionList.Item>
|
||||
)}
|
||||
{!!anime.resources?.length && (
|
||||
<DescriptionList.Item title="Links">
|
||||
<StyledList>
|
||||
{anime.resources.map((resource) => (
|
||||
@@ -108,6 +121,10 @@ export const query = graphql`
|
||||
slug
|
||||
name
|
||||
}
|
||||
studios {
|
||||
slug
|
||||
name
|
||||
}
|
||||
themes {
|
||||
group
|
||||
slug
|
||||
|
||||
@@ -3,7 +3,7 @@ import styled from "styled-components";
|
||||
import { Link, navigate } from "gatsby";
|
||||
import { useDebounce } from "use-debounce";
|
||||
import { SearchInput } from "components/input";
|
||||
import { SearchAnime, SearchArtist, SearchGlobal, SearchTheme } from "components/search";
|
||||
import { SearchAnime, SearchArtist, SearchGlobal, SearchTheme, SearchSeries, SearchStudio } from "components/search";
|
||||
import { gapsColumn, gapsRow } from "styles/mixins";
|
||||
import { Box, Flex } from "components/box";
|
||||
import { Switcher } from "components/switcher";
|
||||
@@ -117,6 +117,10 @@ function Search({ searchQuery, searchEntity, locationState }) {
|
||||
return <SearchTheme searchQuery={searchQuery} locationState={locationState}/>;
|
||||
case "artist":
|
||||
return <SearchArtist searchQuery={searchQuery} locationState={locationState}/>;
|
||||
case "series":
|
||||
return <SearchSeries searchQuery={searchQuery} locationState={locationState}/>;
|
||||
case "studio":
|
||||
return <SearchStudio searchQuery={searchQuery} locationState={locationState}/>;
|
||||
default:
|
||||
// Should never happen
|
||||
return null;
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
import { SidebarContainer } from "components/container";
|
||||
import styled from "styled-components";
|
||||
import { AnimeSummaryCard } from "components/card";
|
||||
import { SEO } from "components/seo";
|
||||
import { graphql } from "gatsby";
|
||||
import useImage from "hooks/useImage";
|
||||
import { Box } from "components/box";
|
||||
import { Text } from "components/text";
|
||||
import { AspectRatio } from "components/utils";
|
||||
|
||||
const StyledCoverContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
height: 100%;
|
||||
border-radius: 0.5rem;
|
||||
overflow: hidden;
|
||||
`;
|
||||
const StyledCoverItemContainer = styled.div`
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
`;
|
||||
const StyledCover = styled.img`
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
`;
|
||||
|
||||
export default function StudioDetailPage({ data: { studio } }) {
|
||||
const anime = studio.anime.sort((a, b) => a.name.localeCompare(b.name));
|
||||
const images = [
|
||||
useImage(anime[0]),
|
||||
useImage(anime[1]),
|
||||
useImage(anime[2]),
|
||||
useImage(anime[3])
|
||||
].map((images) => images.largeCover).filter((image) => !!image);
|
||||
|
||||
return (
|
||||
<Box gapsColumn="1.5rem">
|
||||
<SEO title={studio.name} />
|
||||
<Text variant="h1">{studio.name}</Text>
|
||||
<SidebarContainer>
|
||||
<Box gapsColumn="1.5rem">
|
||||
<AspectRatio ratio={1 / 2}>
|
||||
<StyledCoverContainer>
|
||||
{images.map((image) => (
|
||||
<StyledCoverItemContainer key={image}>
|
||||
<StyledCover src={image}/>
|
||||
</StyledCoverItemContainer>
|
||||
))}
|
||||
</StyledCoverContainer>
|
||||
</AspectRatio>
|
||||
</Box>
|
||||
<Box gapsColumn="1.5rem">
|
||||
<Text variant="h2">Anime</Text>
|
||||
<Box gapsColumn="1rem">
|
||||
{anime.map((anime) => (
|
||||
<AnimeSummaryCard key={anime.slug} anime={anime}/>
|
||||
))}
|
||||
</Box>
|
||||
</Box>
|
||||
</SidebarContainer>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export const query = graphql`
|
||||
query($slug: String!) {
|
||||
studio(slug: { eq: $slug }) {
|
||||
slug
|
||||
name
|
||||
anime {
|
||||
slug
|
||||
name
|
||||
year
|
||||
season
|
||||
themes {
|
||||
slug
|
||||
...VideoSlug
|
||||
}
|
||||
resources {
|
||||
link
|
||||
site
|
||||
}
|
||||
images {
|
||||
facet
|
||||
link
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
+16
-2
@@ -6,7 +6,7 @@ import { Text } from "components/text";
|
||||
import useSiteMeta from "hooks/useSiteMeta";
|
||||
import { SongTitleWithArtists, ThemeEntryTags, VideoTags } from "components/utils";
|
||||
import { VideoButton } from "components/button";
|
||||
import { AnimeSummaryCard, ArtistSummaryCard } from "components/card";
|
||||
import { AnimeSummaryCard, ArtistSummaryCard, SummaryCard } from "components/card";
|
||||
import { SEO } from "components/seo";
|
||||
import useImage from "hooks/useImage";
|
||||
|
||||
@@ -105,8 +105,14 @@ export default function VideoPage({ data: { video, entry } }) {
|
||||
<Box gapsColumn="1rem">
|
||||
<Text variant="h2">Related entries</Text>
|
||||
<Flex flexDirection={["column", "row"]} gapsColumn={["1rem", 0]} gapsRow={[0, "1rem"]}>
|
||||
<Box flex="1">
|
||||
<Box flex="1" gapsColumn="1rem">
|
||||
<AnimeSummaryCard anime={anime} hideThemes/>
|
||||
{!!anime.series?.length && anime.series.map((series) => (
|
||||
<SummaryCard key={series.slug} title={series.name} description="Series" to={`/series/${series.slug}`} />
|
||||
))}
|
||||
{!!anime.studios?.length && anime.studios.map((studio) => (
|
||||
<SummaryCard key={studio.slug} title={studio.name} description="Studio" to={`/studio/${studio.slug}`} />
|
||||
))}
|
||||
</Box>
|
||||
<Box flex="1">
|
||||
<Box gapsColumn="1rem">
|
||||
@@ -193,6 +199,14 @@ export const query = graphql`
|
||||
facet
|
||||
link
|
||||
}
|
||||
series {
|
||||
slug
|
||||
name
|
||||
}
|
||||
studios {
|
||||
slug
|
||||
name
|
||||
}
|
||||
}
|
||||
song {
|
||||
title
|
||||
|
||||
Reference in New Issue
Block a user