2023-12-30 05:29:53 +00:00
< template >
2024-01-10 01:00:38 +00:00
< div
2024-01-25 02:07:26 +00:00
class = "scenes-page"
2024-01-10 01:00:38 +00:00
>
2024-03-19 00:40:56 +00:00
< transition name = "sidebar" >
< Filters
v - if = "showFilters"
2024-03-19 01:35:37 +00:00
: loading = "loading"
2024-03-19 00:40:56 +00:00
>
< div class = "filter" >
< input
v - model = "filters.search"
type = "search"
placeholder = "Search scenes"
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 >
< / transition >
2024-01-07 05:13:40 +00:00
2024-01-10 01:00:38 +00:00
< div
class = "scenes-container"
: class = "{ loading }"
>
2024-01-07 22:44:33 +00:00
< div
v - if = "showMeta"
class = "scenes-header"
>
2024-01-07 05:13:40 +00:00
< div class = "meta" > { { total } } results < / div >
2024-01-25 02:07:26 +00:00
< select
v - model = "scope"
class = "input"
2024-02-22 04:08:06 +00:00
@ change = "search({ autoScope: false })"
2024-01-25 02:07:26 +00:00
>
2024-03-17 02:03:36 +00:00
<!-- not selected in SSR without prop -- >
< option
v - if = "pageStash"
: selected = "scope === 'stashed'"
value = "stashed"
2024-03-19 19:50:50 +00:00
> Added < / option >
2024-03-17 02:03:36 +00:00
2024-02-22 04:08:06 +00:00
< option
2024-03-17 02:03:36 +00:00
v - if = "filters.search"
: selected = "scope === 'results'"
2024-02-22 04:08:06 +00:00
value = "results"
> Relevance < / option >
2024-03-17 02:03:36 +00:00
< option value = "latest" > Latest < / option >
< option value = "upcoming" > Upcoming < / option >
< option value = "new" > New < / option >
2024-03-21 01:54:05 +00:00
< option value = "likes" > Popular < / option >
2024-01-25 02:07:26 +00:00
< / select >
2024-01-07 05:13:40 +00:00
< / div >
2024-01-25 02:07:26 +00:00
< nav
v - if = "showScopeTabs"
class = "scopes"
>
2024-01-07 05:13:40 +00:00
< Link
: href = "getPath('latest')"
class = "scope nolink"
: active = "scope === 'latest'"
> Latest < / Link >
< Link
: href = "getPath('upcoming')"
class = "scope nolink"
: active = "scope === 'upcoming'"
> Upcoming < / Link >
< Link
: href = "getPath('new')"
class = "scope nolink"
: active = "scope === 'new'"
> New < / Link >
< / nav >
2024-01-10 01:00:38 +00:00
< ul
class = "scenes nolist"
>
2024-01-07 05:13:40 +00:00
< li
v - for = "scene in scenes"
2024-03-24 22:36:25 +00:00
: key = "`scene-${scene.id}`"
2024-01-07 05:13:40 +00:00
>
2024-03-25 01:08:09 +00:00
< SceneTile :scene ="scene" / >
2024-01-07 05:13:40 +00:00
< / li >
< / ul >
2024-03-19 00:40:56 +00:00
< Pagination
: total = "total"
: page = "currentPage"
/ >
2024-01-07 05:13:40 +00:00
< / div >
2024-01-10 01:00:38 +00:00
< Ellipsis
class = "ellipsis"
: class = "{ loading }"
/ >
2023-12-30 05:29:53 +00:00
< / div >
< / template >
< script setup >
2024-01-07 22:44:33 +00:00
import { ref , inject } from 'vue' ;
2024-01-07 05:13:40 +00:00
import { parse } from 'path-to-regexp' ;
import navigate from '#/src/navigate.js' ;
import { get } from '#/src/api.js' ;
import events from '#/src/events.js' ;
2024-01-09 01:26:32 +00:00
import { getActorIdentifier , parseActorIdentifier } from '#/src/query.js' ;
2023-12-30 05:29:53 +00:00
2024-01-07 05:13:40 +00:00
import Filters from '#/components/filters/filters.vue' ;
2024-01-07 22:44:33 +00:00
import ActorsFilter from '#/components/filters/actors.vue' ;
2024-01-08 01:21:57 +00:00
import TagsFilter from '#/components/filters/tags.vue' ;
2024-01-09 01:26:32 +00:00
import ChannelsFilter from '#/components/filters/channels.vue' ;
2024-03-25 01:08:09 +00:00
import SceneTile from '#/components/scenes/tile.vue' ;
2024-01-10 01:00:38 +00:00
import Pagination from '#/components/pagination/pagination.vue' ;
import Ellipsis from '#/components/loading/ellipsis.vue' ;
2023-12-30 05:29:53 +00:00
2024-02-22 04:08:06 +00:00
const props = defineProps ( {
2024-01-07 05:13:40 +00:00
showFilters : {
type : Boolean ,
default : true ,
2023-12-30 05:29:53 +00:00
} ,
2024-01-07 22:44:33 +00:00
showMeta : {
type : Boolean ,
default : true ,
} ,
2024-01-25 02:07:26 +00:00
showScopeTabs : {
type : Boolean ,
default : false ,
} ,
2024-02-22 04:08:06 +00:00
defaultScope : {
type : String ,
default : 'latest' ,
} ,
2023-12-30 05:29:53 +00:00
} ) ;
2024-01-07 05:13:40 +00:00
const { pageProps , routeParams , urlParsed } = inject ( 'pageContext' ) ;
const {
2024-01-09 01:26:32 +00:00
actor : pageActor ,
tag : pageTag ,
2024-01-25 02:07:26 +00:00
entity : pageEntity ,
2024-03-14 23:08:24 +00:00
stash : pageStash ,
2024-01-07 05:13:40 +00:00
} = pageProps ;
const scenes = ref ( pageProps . scenes ) ;
2024-01-10 01:00:38 +00:00
const aggActors = ref ( pageProps . aggActors || [ ] ) ;
const aggTags = ref ( pageProps . aggTags || [ ] ) ;
const aggChannels = ref ( pageProps . aggChannels || [ ] ) ;
2024-01-07 05:13:40 +00:00
const currentPage = ref ( Number ( routeParams . page ) ) ;
2024-02-22 04:08:06 +00:00
const scope = ref ( routeParams . scope || props . defaultScope ) ;
2024-01-07 05:13:40 +00:00
const total = ref ( Number ( pageProps . total ) ) ;
2024-01-10 01:00:38 +00:00
const loading = ref ( false ) ;
2024-01-07 05:13:40 +00:00
2024-01-09 01:26:32 +00:00
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 ] ) ) ;
2024-01-10 01:00:38 +00:00
const queryEntity = networks [ urlParsed . search . e ] || channels [ urlParsed . search . e ] ;
2024-01-09 01:26:32 +00:00
2024-01-07 05:13:40 +00:00
const filters = ref ( {
2024-02-22 04:08:06 +00:00
search : urlParsed . search . q ,
2024-01-08 01:21:57 +00:00
tags : urlParsed . search . tags ? . split ( ',' ) . filter ( Boolean ) || [ ] ,
2024-01-10 01:00:38 +00:00
entity : queryEntity ,
2024-01-09 01:26:32 +00:00
actors : queryActors ,
2024-01-07 05:13:40 +00:00
} ) ;
function getPath ( targetScope , preserveQuery ) {
2024-01-25 02:07:26 +00:00
const path = parse ( routeParams . path ) . map ( ( segment ) => {
if ( segment . name === 'scope' ) {
return ` ${ segment . prefix } ${ targetScope } ` ;
}
2024-01-07 05:13:40 +00:00
2024-01-25 02:07:26 +00:00
if ( segment . name === 'page' ) {
return ` ${ segment . prefix } ${ 1 } ` ;
}
2024-01-07 05:13:40 +00:00
2024-01-25 02:07:26 +00:00
return ` ${ segment . prefix || '' } ${ routeParams [ segment . name ] || segment } ` ;
} ) . join ( '' ) ;
2024-01-07 05:13:40 +00:00
if ( preserveQuery && urlParsed . searchOriginal ) {
return ` ${ path } ${ urlParsed . searchOriginal } ` ;
}
return path ;
}
2024-02-22 04:08:06 +00:00
async function search ( options = { } ) {
if ( options . resetPage !== false ) {
2024-01-07 05:13:40 +00:00
currentPage . value = 1 ;
}
2024-02-22 04:08:06 +00:00
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 ,
} ;
2024-01-08 01:21:57 +00:00
2024-01-25 02:07:26 +00:00
const entity = filters . value . entity || pageEntity ;
2024-01-09 01:26:32 +00:00
const entitySlug = entity ? . type === 'network' ? ` _ ${ entity . slug } ` : entity ? . slug ;
2024-01-07 05:13:40 +00:00
2024-01-10 01:00:38 +00:00
loading . value = true ;
2024-02-22 04:08:06 +00:00
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 } ) ;
2024-01-07 05:13:40 +00:00
const res = await get ( '/scenes' , {
... query ,
2024-01-09 01:26:32 +00:00
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 ( ',' ) ,
2024-03-14 23:08:24 +00:00
stashId : pageStash ? . id ,
2024-01-10 01:00:38 +00:00
e : entitySlug ,
2024-01-25 02:07:26 +00:00
scope : scope . value ,
2024-01-07 05:13:40 +00:00
page : currentPage . value , // client uses param rather than query pagination
} ) ;
scenes . value = res . scenes ;
2024-01-08 01:21:57 +00:00
aggActors . value = res . aggActors ;
aggTags . value = res . aggTags ;
2024-01-10 01:00:38 +00:00
aggChannels . value = res . aggChannels ;
2024-01-07 05:13:40 +00:00
2024-01-25 02:07:26 +00:00
total . value = res . total ;
2024-01-10 01:00:38 +00:00
loading . value = false ;
2024-01-25 02:07:26 +00:00
2024-01-07 05:13:40 +00:00
events . emit ( 'scrollUp' ) ;
}
function updateFilter ( prop , value , reload = true ) {
filters . value [ prop ] = value ;
if ( reload ) {
search ( ) ;
}
}
2023-12-30 05:29:53 +00:00
< / script >
< style scoped >
2024-01-25 02:07:26 +00:00
. scenes - page {
2024-01-07 05:13:40 +00:00
display : flex ;
2023-12-30 05:29:53 +00:00
background : var ( -- background - base - 10 ) ;
2024-01-10 01:00:38 +00:00
position : relative ;
2023-12-30 05:29:53 +00:00
}
2024-01-07 05:13:40 +00:00
. scenes - header {
display : flex ;
align - items : center ;
2024-01-25 02:07:26 +00:00
padding : .5 rem 1 rem .25 rem 3 rem ;
2024-01-07 05:13:40 +00:00
}
. scenes - container {
display : flex ;
flex - direction : column ;
2024-01-07 22:44:33 +00:00
flex - grow : 1 ;
2024-01-07 05:13:40 +00:00
}
. meta {
display : flex ;
flex - grow : 1 ;
justify - content : space - between ;
align - items : center ;
}
2023-12-30 05:29:53 +00:00
. scenes {
display : grid ;
2024-03-17 22:55:36 +00:00
grid - template - columns : repeat ( auto - fill , minmax ( 20 rem , 1 fr ) ) ;
2024-03-19 19:50:50 +00:00
gap : .5 rem ;
2024-01-25 02:07:26 +00:00
padding : .5 rem 1 rem 1 rem 1 rem ;
2023-12-30 05:29:53 +00:00
}
. scopes {
2024-03-18 00:47:49 +00:00
display : flex ;
gap : .5 rem ;
2024-03-19 19:50:50 +00:00
padding : .75 rem 0 .25 rem 1 rem ;
2023-12-30 05:29:53 +00:00
}
. scope {
box - sizing : border - box ;
2024-03-18 00:47:49 +00:00
padding : .5 rem 1 rem ;
background : var ( -- background - dark - 20 ) ;
border - radius : 1 rem ;
2023-12-30 05:29:53 +00:00
color : var ( -- shadow ) ;
font - size : .9 rem ;
font - weight : bold ;
& . active {
2024-03-18 00:47:49 +00:00
background : var ( -- primary ) ;
color : var ( -- text - light ) ;
2023-12-30 05:29:53 +00:00
}
}
2024-01-10 01:00:38 +00:00
2024-03-19 01:35:37 +00:00
. scenes - container . loading : not ( . ellipsis ) {
2024-01-10 01:00:38 +00:00
opacity : .3 ;
pointer - events : none ;
}
. ellipsis {
display : none ;
position : absolute ;
top : 1 rem ;
left : 50 % ;
& . loading {
display : flex ;
}
}
2024-03-17 22:55:36 +00:00
@ media ( -- small - 10 ) {
. scenes {
grid - template - columns : repeat ( auto - fill , minmax ( 18 rem , 1 fr ) ) ;
}
2024-03-18 01:00:04 +00:00
. scopes {
justify - content : center ;
}
2024-03-17 22:55:36 +00:00
}
@ media ( -- small - 20 ) {
. scenes {
grid - template - columns : repeat ( auto - fill , minmax ( 16 rem , 1 fr ) ) ;
2024-03-19 19:50:50 +00:00
padding : .5 rem .5 rem 1 rem .5 rem ;
gap : .5 rem .25 rem ;
2024-03-17 22:55:36 +00:00
}
}
2023-12-30 05:29:53 +00:00
< / style >