2024-03-24 22:36:25 +00:00
< template >
< div class = "movies-page" >
< Filters
v - if = "showFilters"
: class = "{ loading }"
>
< div class = "filter" >
< input
v - model = "filters.search"
type = "search"
placeholder = "Search movies"
class = "search input"
@ search = "search"
>
< / div >
< TagsFilter
: filters = "filters"
: tags = "aggTags"
@ update = "updateFilter"
/ >
< ChannelsFilter
: filters = "filters"
: channels = "aggChannels"
@ update = "updateFilter"
/ >
< ActorsFilter
: filters = "filters"
: actors = "aggActors"
@ update = "updateFilter"
/ >
< / Filters >
< div class = "movies-container" >
< div class = "movies-header" >
< div class = "meta" > { { total } } results < / div >
< select
v - model = "scope"
class = "input"
@ change = "search({ autoScope: false })"
>
<!-- not selected in SSR without prop -- >
< option
v - if = "pageStash"
: selected = "scope === 'stashed'"
value = "stashed"
> Added < / option >
< option
v - if = "filters.search"
: selected = "scope === 'results'"
value = "results"
> Relevance < / option >
< option value = "likes" > Popular < / option >
< option value = "latest" > Latest < / option >
< option value = "upcoming" > Upcoming < / option >
< option value = "new" > New < / option >
< / select >
< / div >
< ul class = "movies nolist" >
< li
v - for = "movie in movies"
: key = "`movie-${movie.id}`"
>
2024-03-25 01:08:09 +00:00
< MovieTile :movie ="movie" / >
2024-03-24 22:36:25 +00:00
< / li >
< / ul >
< Pagination
: total = "total"
: page = "currentPage"
/ >
< / div >
< / div >
< / template >
< script setup >
import { ref , inject } from 'vue' ;
import { parse } from 'path-to-regexp' ;
import navigate from '#/src/navigate.js' ;
import { get } from '#/src/api.js' ;
import { getActorIdentifier , parseActorIdentifier } from '#/src/query.js' ;
import events from '#/src/events.js' ;
2024-03-25 01:08:09 +00:00
import MovieTile from '#/components/movies/tile.vue' ;
2024-03-24 22:36:25 +00:00
import Filters from '#/components/filters/filters.vue' ;
import ActorsFilter from '#/components/filters/actors.vue' ;
import TagsFilter from '#/components/filters/tags.vue' ;
import ChannelsFilter from '#/components/filters/channels.vue' ;
import Pagination from '#/components/pagination/pagination.vue' ;
const pageContext = inject ( 'pageContext' ) ;
const { pageProps , routeParams , urlParsed } = pageContext ;
const {
actor : pageActor ,
tag : pageTag ,
entity : pageEntity ,
stash : pageStash ,
} = pageProps ;
const movies = ref ( pageProps . movies ) ;
const aggActors = ref ( pageProps . aggActors || [ ] ) ;
const aggTags = ref ( pageProps . aggTags || [ ] ) ;
const aggChannels = ref ( pageProps . aggChannels || [ ] ) ;
const currentPage = ref ( Number ( routeParams . page ) ) ;
const scope = ref ( routeParams . scope ) ;
const total = ref ( Number ( pageProps . total ) ) ;
const loading = ref ( false ) ;
const showFilters = ref ( true ) ;
const actorIds = urlParsed . search . actors ? . split ( ',' ) . map ( ( identifier ) => parseActorIdentifier ( identifier ) ? . id ) . filter ( Boolean ) || [ ] ;
const queryActors = actorIds . map ( ( urlActorId ) => aggActors . value . find ( ( aggActor ) => aggActor . id === urlActorId ) ) . filter ( Boolean ) ;
const networks = Object . fromEntries ( aggChannels . value . map ( ( channel ) => ( channel . type === 'network' ? channel : channel . parent ) ) . filter ( Boolean ) . map ( ( parent ) => [ ` _ ${ parent . slug } ` , parent ] ) ) ;
const channels = Object . fromEntries ( aggChannels . value . filter ( ( channel ) => channel . type === 'channel' ) . map ( ( channel ) => [ channel . slug , channel ] ) ) ;
const queryEntity = networks [ urlParsed . search . e ] || channels [ urlParsed . search . e ] ;
const filters = ref ( {
search : urlParsed . search . q ,
tags : urlParsed . search . tags ? . split ( ',' ) . filter ( Boolean ) || [ ] ,
entity : queryEntity ,
actors : queryActors ,
} ) ;
function getPath ( targetScope , preserveQuery ) {
const path = parse ( routeParams . path ) . map ( ( segment ) => {
if ( segment . name === 'scope' ) {
return ` ${ segment . prefix } ${ targetScope } ` ;
}
if ( segment . name === 'page' ) {
return ` ${ segment . prefix } ${ 1 } ` ;
}
return ` ${ segment . prefix || '' } ${ routeParams [ segment . name ] || segment } ` ;
} ) . join ( '' ) ;
if ( preserveQuery && urlParsed . searchOriginal ) {
return ` ${ path } ${ urlParsed . searchOriginal } ` ;
}
return path ;
}
async function search ( options = { } ) {
if ( options . resetPage !== false ) {
currentPage . value = 1 ;
}
if ( options . autoScope !== false ) {
if ( filters . value . search ) {
scope . value = 'results' ;
}
if ( ! filters . value . search && scope . value === 'results' ) {
scope . value = 'latest' ;
}
}
const query = {
q : filters . value . search || undefined ,
} ;
const entity = filters . value . entity || pageEntity ;
const entitySlug = entity ? . type === 'network' ? ` _ ${ entity . slug } ` : entity ? . slug ;
loading . value = true ;
navigate ( getPath ( scope . value , false ) , {
... query ,
actors : filters . value . actors . map ( ( filterActor ) => getActorIdentifier ( filterActor ) ) . join ( ',' ) || undefined , // don't include page actor ID in query, already a parameter
tags : filters . value . tags . join ( ',' ) || undefined ,
e : filters . value . entity ? . type === 'network' ? ` _ ${ filters . value . entity . slug } ` : ( filters . value . entity ? . slug || undefined ) ,
} , { redirect : false } ) ;
const res = await get ( '/movies' , {
... query ,
actors : [ pageActor , ... filters . value . actors ] . filter ( Boolean ) . map ( ( filterActor ) => getActorIdentifier ( filterActor ) ) . join ( ',' ) , // if we're on an actor page, that actor ID needs to be included
tags : [ pageTag ? . slug , ... filters . value . tags ] . filter ( Boolean ) . join ( ',' ) ,
e : entitySlug ,
stashId : pageStash ? . id ,
scope : scope . value ,
page : currentPage . value , // client uses param rather than query pagination
} ) ;
movies . value = res . movies ;
aggActors . value = res . aggActors ;
aggTags . value = res . aggTags ;
aggChannels . value = res . aggChannels ;
total . value = res . total ;
loading . value = false ;
events . emit ( 'scrollUp' ) ;
}
function updateFilter ( prop , value , reload = true ) {
filters . value [ prop ] = value ;
if ( reload ) {
search ( ) ;
}
}
< / script >
< style scoped >
. movies - page {
display : flex ;
background : var ( -- background - base - 10 ) ;
}
. movies - container {
display : flex ;
flex - direction : column ;
flex - grow : 1 ;
}
. movies - header {
display : flex ;
align - items : center ;
padding : .5 rem 1 rem .25 rem 3 rem ;
}
. meta {
display : flex ;
flex - grow : 1 ;
justify - content : space - between ;
align - items : center ;
}
. movies {
display : grid ;
grid - template - columns : repeat ( auto - fill , minmax ( 13 rem , 1 fr ) ) ;
gap : 1 rem ;
padding : .5 rem 1 rem 1 rem 1 rem ;
}
@ media ( -- compact ) {
. movies {
grid - template - columns : repeat ( auto - fill , minmax ( 11 rem , 1 fr ) ) ;
}
}
@ media ( -- small - 20 ) {
. movies {
padding : .5 rem .5 rem 1 rem .5 rem ;
gap : .5 rem .25 rem ;
}
}
@ media ( -- small - 50 ) {
. movies {
grid - template - columns : repeat ( auto - fill , minmax ( 9 rem , 1 fr ) ) ;
}
}
< / style >