This repository has been archived by the owner on Apr 5, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #56 from forte-music/feature/search
Implemented Search Feature
- Loading branch information
Showing
24 changed files
with
856 additions
and
66 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
55 changes: 55 additions & 0 deletions
55
src/components/AlbumSearchResultsContainer/enhancers/query.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
import React from 'react'; | ||
import { | ||
AlbumResultsQuery as Data, | ||
AlbumResultsQueryVariables as Variables, | ||
} from './__generated__/AlbumResultsQuery'; | ||
import gql from 'graphql-tag'; | ||
import { Omit } from '../../../utils'; | ||
import { | ||
ConnectionQuery, | ||
ConnectionQueryProps, | ||
ConnectionQueryResult, | ||
} from '../../ConnectionQuery'; | ||
|
||
export const albumSearchResultFragment = gql` | ||
fragment AlbumSearchResults on AlbumConnection { | ||
pageInfo { | ||
hasNextPage | ||
} | ||
edges { | ||
node { | ||
id | ||
artworkUrl | ||
name | ||
artist { | ||
id | ||
name | ||
} | ||
songs { | ||
id | ||
} | ||
} | ||
} | ||
} | ||
`; | ||
|
||
const query = gql` | ||
query AlbumResultsQuery($cursor: String, $query: String!, $first: Int!) { | ||
albums( | ||
first: $first | ||
after: $cursor | ||
sort: { filter: $query, sortBy: LEXICOGRAPHICALLY } | ||
) @connection { | ||
...AlbumSearchResults | ||
} | ||
} | ||
${albumSearchResultFragment} | ||
`; | ||
|
||
export type Result = ConnectionQueryResult<Data, Variables>; | ||
|
||
export const AlbumsQuery = ( | ||
props: Omit<ConnectionQueryProps<Data, Variables>, 'query'> | ||
) => <ConnectionQuery query={query} children={props.children} {...props} />; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
import React from 'react'; | ||
import Observer from 'react-intersection-observer'; | ||
import { AlbumsQuery, Result } from './enhancers/query'; | ||
import { AlbumSearchResultsLoadingContainer } from '../AlbumSearchResultsLoadingContainer'; | ||
import { AlbumInfo } from '../AlbumsContainer/components/AlbumInfo'; | ||
import { ArtworkGrid } from '../styled/search'; | ||
|
||
interface Props { | ||
// Whether or not to load more items when scrolled to the bottom of the page. | ||
loadMore: boolean; | ||
|
||
// The query which the header link navigates to and used to fetch songs. | ||
query: string; | ||
} | ||
|
||
// Fetches data and renders album results of search results pages. | ||
export const AlbumSearchResultsContainer = (props: Props) => ( | ||
<AlbumsQuery | ||
variables={{ query: props.query, first: props.loadMore ? 30 : 6 }} | ||
> | ||
{(result: Result) => ( | ||
<AlbumSearchResultsLoadingContainer | ||
query={props.query} | ||
albums={ | ||
!result.loading | ||
? result.data && result.data.albums.edges.map(edge => edge.node) | ||
: undefined | ||
} | ||
children={albums => ( | ||
<React.Fragment> | ||
<ArtworkGrid> | ||
{albums.map(album => <AlbumInfo key={album.id} album={album} />)} | ||
{props.loadMore && | ||
!result.loading && | ||
result.data && | ||
result.data.albums.pageInfo.hasNextPage && ( | ||
<Observer | ||
key={'final'} | ||
onChange={inView => { | ||
if (!inView) { | ||
return; | ||
} | ||
|
||
result.getNextPage(); | ||
}} | ||
> | ||
<div /> | ||
</Observer> | ||
)} | ||
</ArtworkGrid> | ||
</React.Fragment> | ||
)} | ||
/> | ||
)} | ||
</AlbumsQuery> | ||
); | ||
|
||
// TODO: Share Loading More Logic With AlbumsPage |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import React from 'react'; | ||
import { | ||
EmptyResult, | ||
SearchResultTypeContainer, | ||
SearchResultTypeHeader, | ||
} from './styled/search'; | ||
|
||
import { Album } from './AlbumsContainer/components/AlbumInfo'; | ||
import { LinkStyled } from './LinkStyled'; | ||
|
||
interface Props { | ||
// Query which yielded the albums below. | ||
query: string; | ||
|
||
// Albums which were found. Undefined when initially loading. | ||
albums?: Album[]; | ||
|
||
children: (albums: Album[]) => React.ReactNode; | ||
} | ||
|
||
// Renders header for album search results. Handles the cases when `albums` is | ||
// undefined or an empty array. Calls the `children` function when neither is | ||
// the case. | ||
export const AlbumSearchResultsLoadingContainer = (props: Props) => ( | ||
<SearchResultTypeContainer> | ||
<SearchResultTypeHeader> | ||
<LinkStyled to={`/search/${props.query}/albums`}>Albums</LinkStyled> | ||
</SearchResultTypeHeader> | ||
|
||
{props.albums ? ( | ||
props.albums.length ? ( | ||
props.children(props.albums) | ||
) : ( | ||
<EmptyResult>No albums found</EmptyResult> | ||
) | ||
) : ( | ||
<EmptyResult>Loading...</EmptyResult> | ||
)} | ||
</SearchResultTypeContainer> | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import React from 'react'; | ||
import { SongSearchResultsContainer } from './SongSearchResultsContainer'; | ||
import { AlbumSearchResultsContainer } from './AlbumSearchResultsContainer'; | ||
|
||
interface Props { | ||
query: string; | ||
} | ||
|
||
export const AllSearchResults = (props: Props) => ( | ||
<React.Fragment> | ||
<AlbumSearchResultsContainer loadMore={false} query={props.query} /> | ||
<SongSearchResultsContainer loadMore={false} query={props.query} /> | ||
</React.Fragment> | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import React from 'react'; | ||
|
||
interface Props { | ||
className?: string; | ||
onKeyPress?: (event: React.KeyboardEvent<HTMLInputElement>) => void | false; | ||
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void; | ||
value?: string; | ||
placeholder?: string; | ||
} | ||
|
||
// Text input focused on component mount. | ||
export class FocusedTextInput extends React.Component<Props> { | ||
private element: HTMLInputElement | null = null; | ||
|
||
public componentDidMount() { | ||
if (!this.element) { | ||
return; | ||
} | ||
|
||
this.element.focus(); | ||
} | ||
|
||
public render() { | ||
return ( | ||
<input | ||
{...this.props} | ||
ref={element => (this.element = element)} | ||
type="text" | ||
/> | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
import React from 'react'; | ||
import { DetailRow, Song as DetailRowSong } from './DetailSongTable'; | ||
import { InfiniteSongList } from './InfiniteSongList'; | ||
import { LoadingRow } from './LoadingRow'; | ||
import { InteractiveDetailTableHeader } from './InteractiveDetailTableHeader'; | ||
|
||
import { SortBy } from './SongsContainer/enhancers/__generated__/SongsQuery'; | ||
|
||
export interface Props { | ||
// Songs to display in the list. | ||
songs: Song[]; | ||
|
||
// The identifier of the active song. Undefined if no song is active. The | ||
// song with this identifier will be styled as active. | ||
activeSongId?: string; | ||
|
||
// If there are more elements to load. When true, loadMore is called when | ||
// more items are needed. | ||
hasMore: boolean; | ||
|
||
// Called to load more items. | ||
loadMore: () => void; | ||
|
||
// True when more items are being loaded. A loading row is displayed as | ||
// the last row of the list when true. | ||
isLoadingMore: boolean; | ||
|
||
// Field which is currently being sorted by. | ||
sortBy: SortBy; | ||
|
||
// Update the currently sorted by field. | ||
setSortBy: (newSort: SortBy) => void; | ||
|
||
// Whether or not the sort is reversed. | ||
isReverse: boolean; | ||
|
||
// Update the sort direction. | ||
setReverse: (newReverse: boolean) => void; | ||
|
||
// Starts playing all the songs in the view sorted order starting from the | ||
// song at index. Called when a song is double clicked. | ||
startPlayingFrom: (index: number) => void; | ||
} | ||
|
||
interface Song extends DetailRowSong { | ||
id: string; | ||
} | ||
|
||
export const InfiniteDetailSongList = (props: Props) => ( | ||
<InfiniteSongList | ||
rows={props.songs} | ||
hasMoreRows={props.hasMore} | ||
loadMoreRows={props.loadMore} | ||
isLoadingMore={props.isLoadingMore} | ||
render={(song, index) => ( | ||
<DetailRow | ||
song={song} | ||
active={song.id === props.activeSongId} | ||
onDoubleClick={() => props.startPlayingFrom(index)} | ||
/> | ||
)} | ||
loading={<LoadingRow />} | ||
header={ | ||
<InteractiveDetailTableHeader | ||
sortBy={props.sortBy} | ||
setSortBy={props.setSortBy} | ||
isReverse={props.isReverse} | ||
setReverse={props.setReverse} | ||
/> | ||
} | ||
/> | ||
); |
8 changes: 4 additions & 4 deletions
8
...mponents/InteractiveDetailTableHeader.tsx → ...mponents/InteractiveDetailTableHeader.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.