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
>
2024-04-02 01:13:15 +00:00
< div class = "filter search-container" >
<!-- onsearch not compatible with FF and Safari -- >
2024-03-19 00:40:56 +00:00
< input
v - model = "filters.search"
type = "search"
placeholder = "Search scenes"
class = "search input"
2024-04-02 01:13:15 +00:00
@ keydown . enter = "search"
2024-03-19 00:40:56 +00:00
>
2024-04-02 01:13:15 +00:00
< Icon
icon = "search"
class = "search-button"
@ click = "search"
/ >
2024-03-19 00:40:56 +00:00
< / div >
2024-08-17 23:36:37 +00:00
< YearsFilter
: filters = "filters"
: years = "aggYears"
@ update = "updateFilter"
/ >
2024-03-19 00:40:56 +00:00
< 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
2024-06-12 15:09:53 +00:00
< Campaign
v - if = "campaigns?.meta"
: campaign = "campaigns.meta"
/ >
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-06-12 15:09:53 +00:00
< div class = "scopes-pills" >
< 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 >
< / div >
< Campaign
v - if = "campaigns?.scope"
: campaign = "campaigns.scope"
/ >
2024-01-07 05:13:40 +00:00
< / nav >
2024-08-04 21:50:26 +00:00
< div class = "scenes-items" >
< ul class = "scenes nolist" >
< template v-for ="item in campaignScenes" >
< li
v - if = "item === 'campaign' && sceneCampaign"
: key = "`campaign-${item.id}`"
>
< Campaign :campaign ="sceneCampaign" / >
< / li >
< li
v - else
: key = "`scene-${item.id}`"
>
< SceneTile :scene ="item" / >
< / li >
< / template >
< / ul >
< / div >
2024-01-07 05:13:40 +00:00
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-06-12 01:28:40 +00:00
import { ref , computed , 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-08-17 23:36:37 +00:00
import YearsFilter from '#/components/filters/years.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-06-12 00:34:13 +00:00
import Campaign from '#/components/campaigns/campaign.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
2024-06-12 15:09:53 +00:00
const {
pageProps ,
routeParams ,
urlParsed ,
campaigns ,
} = inject ( 'pageContext' ) ;
2024-01-07 05:13:40 +00:00
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-08-17 23:36:37 +00:00
const aggYears = ref ( pageProps . aggYears || [ ] ) ;
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-06-02 03:22:08 +00:00
const total = ref ( Number ( pageProps . sceneTotal || 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-08-17 23:36:37 +00:00
years : urlParsed . search . years ? . split ( ',' ) . filter ( Boolean ) . map ( Number ) || [ ] ,
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
} ) ;
2024-06-12 15:09:53 +00:00
const sceneCampaign = campaigns ? . scenes ;
const campaignIndex = campaigns ? . index ;
2024-06-12 01:28:40 +00:00
2024-06-12 15:09:53 +00:00
const campaignScenes = computed ( ( ) => scenes . value . flatMap ( ( scene , index ) => ( sceneCampaign && index === campaignIndex ? [ 'campaign' , scene ] : scene ) ) ) ;
2024-06-12 00:34:13 +00:00
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 ,
2024-08-17 23:36:37 +00:00
years : filters . value . years . join ( ',' ) || undefined ,
2024-02-22 04:08:06 +00:00
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-08-17 23:36:37 +00:00
years : filters . value . years . filter ( Boolean ) . join ( ',' ) , // if we're on an actor page, that actor ID needs to be included
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-08-17 23:36:37 +00:00
aggYears . value = res . aggYears ;
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 ;
2024-06-12 01:54:38 +00:00
flex - grow : 1 ;
2023-12-30 05:29:53 +00:00
}
2024-01-07 05:13:40 +00:00
. scenes - header {
display : flex ;
2024-06-12 15:09:53 +00:00
align - items : flex - end ;
justify - content : space - between ;
2024-01-25 02:07:26 +00:00
padding : .5 rem 1 rem .25 rem 3 rem ;
2024-06-12 15:09:53 +00:00
. campaign {
max - height : 6 rem ;
justify - content : center ;
margin : .5 rem 1 rem 0 1 rem ;
2024-08-29 15:53:54 +00:00
width : 0 ;
flex - grow : 1 ;
2024-06-12 15:09:53 +00:00
}
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 ;
justify - content : space - between ;
align - items : center ;
2024-06-13 01:10:11 +00:00
flex - shrink : 0 ;
2024-06-12 15:09:53 +00:00
margin - bottom : .5 rem ;
2024-01-07 05:13:40 +00:00
}
2024-08-04 21:50:26 +00:00
. scenes - items {
/* grow additional container to prevent gaps between grid tiles */
flex - grow : 1 ;
}
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 ;
2024-06-13 01:25:09 +00:00
: deep ( . campaign ) . campaign - banner {
border - radius : .25 rem ;
box - shadow : 0 0 3 px var ( -- shadow - weak - 20 ) ;
}
2023-12-30 05:29:53 +00:00
}
. scopes {
2024-03-18 00:47:49 +00:00
display : flex ;
2024-06-12 15:09:53 +00:00
align - items : flex - end ;
justify - content : space - between ;
padding : .75 rem 1 rem .25 rem 1 rem ;
. campaign {
max - height : 6 rem ;
justify - content : flex - end ;
margin - left : 1 rem ;
}
}
. scopes - pills {
display : flex ;
align - items : center ;
2024-03-18 00:47:49 +00:00
gap : .5 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 ;
2024-06-10 01:24:48 +00:00
color : var ( -- glass ) ;
2023-12-30 05:29:53 +00:00
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-06-12 15:09:53 +00:00
flex - direction : column - reverse ;
. campaign {
width : 100 % ;
justify - content : center ;
margin - left : 0 ;
margin - bottom : 1 rem ;
}
}
. scopes - pills {
width : 100 % ;
justify - content : center ;
2024-03-18 01:00:04 +00:00
}
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 >