Fixed pagination. Added entity page channel tile expand.
This commit is contained in:
		
							parent
							
								
									0b3f98826b
								
							
						
					
					
						commit
						d739975d36
					
				|  | @ -1 +1,3 @@ | ||||||
|  | @custom-media --small-10 (max-width: 768px); | ||||||
|  | @custom-media --small (max-width: 900px); | ||||||
| @custom-media --compact (max-width: 1200px); | @custom-media --compact (max-width: 1200px); | ||||||
|  |  | ||||||
|  | @ -3,6 +3,7 @@ | ||||||
|     --primary-strong: #f90071; |     --primary-strong: #f90071; | ||||||
|     --primary-faded: #ffcce4; |     --primary-faded: #ffcce4; | ||||||
| 
 | 
 | ||||||
|  |     --grey-dark-50: #111; | ||||||
|     --grey-dark-40: #222; |     --grey-dark-40: #222; | ||||||
|     --grey-dark-30: #444; |     --grey-dark-30: #444; | ||||||
|     --grey-dark-20: #666; |     --grey-dark-20: #666; | ||||||
|  |  | ||||||
|  | @ -32,7 +32,7 @@ | ||||||
| 			</span> | 			</span> | ||||||
| 
 | 
 | ||||||
| 			<span | 			<span | ||||||
| 				v-if="actor.origin.country" | 				v-if="actor.origin?.country" | ||||||
| 				:title="`Born in ${actor.origin.country.name}`" | 				:title="`Born in ${actor.origin.country.name}`" | ||||||
| 				class="country" | 				class="country" | ||||||
| 			> | 			> | ||||||
|  |  | ||||||
|  | @ -0,0 +1,51 @@ | ||||||
|  | <template> | ||||||
|  | 	<a | ||||||
|  | 		:href="`/${entity.type}/${entity.slug}`" | ||||||
|  | 		class="entity" | ||||||
|  | 	> | ||||||
|  | 		<img | ||||||
|  | 			v-if="entity.hasLogo" | ||||||
|  | 			:src="entity.isIndependent ? `/logos/${entity.slug}/entity.png` : `/logos/${entity.parent?.slug}/${entity.slug}.png`" | ||||||
|  | 			:alt="entity.name" | ||||||
|  | 			class="logo" | ||||||
|  | 		> | ||||||
|  | 
 | ||||||
|  | 		<span v-else>{{ entity.name }}</span> | ||||||
|  | 	</a> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <script setup> | ||||||
|  | defineProps({ | ||||||
|  | 	entity: { | ||||||
|  | 		type: Object, | ||||||
|  | 		default: null, | ||||||
|  | 	}, | ||||||
|  | }); | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <style scoped> | ||||||
|  | .entity { | ||||||
|  | 	width: 15rem; | ||||||
|  | 	height: 6rem; | ||||||
|  | 	display: flex; | ||||||
|  | 	justify-content: center; | ||||||
|  | 	align-items: center; | ||||||
|  | 	box-sizing: border-box; | ||||||
|  | 	padding: 1rem; | ||||||
|  | 	border-radius: .5rem; | ||||||
|  | 	background: var(--grey-dark-40); | ||||||
|  | 	color: var(--text-light); | ||||||
|  | 	font-size: 1.25rem; | ||||||
|  | 	font-weight: bold; | ||||||
|  | 
 | ||||||
|  | 	&:hover { | ||||||
|  | 		box-shadow: 0 0 3px var(--shadow); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .logo { | ||||||
|  | 	height: 100%; | ||||||
|  | 	width: 100%; | ||||||
|  | 	object-fit: contain; | ||||||
|  | } | ||||||
|  | </style> | ||||||
|  | @ -0,0 +1,94 @@ | ||||||
|  | <template> | ||||||
|  | 	<li | ||||||
|  | 		:key="`filter-actor-${actor.id}`" | ||||||
|  | 		class="filter-item" | ||||||
|  | 		:class="{ selected: filters.actors.some((filterActor) => filterActor.id === actor.id), first: type === 'available' && index === 0 && filters.actors.length > 0 }" | ||||||
|  | 		@click="emit('actor', actor)" | ||||||
|  | 	> | ||||||
|  | 		<div | ||||||
|  | 			class="filter-include" | ||||||
|  | 			@click.stop="toggleActor(actor)" | ||||||
|  | 		> | ||||||
|  | 			<Icon | ||||||
|  | 				icon="checkmark" | ||||||
|  | 				class="filter-add" | ||||||
|  | 			/> | ||||||
|  | 
 | ||||||
|  | 			<Icon | ||||||
|  | 				icon="cross2" | ||||||
|  | 				class="filter-remove" | ||||||
|  | 			/> | ||||||
|  | 		</div> | ||||||
|  | 
 | ||||||
|  | 		<span class="filter-name actor-name"> | ||||||
|  | 			<span | ||||||
|  | 				class="filter-text" | ||||||
|  | 				:title="actor.name" | ||||||
|  | 			>{{ actor.name }}</span> | ||||||
|  | 
 | ||||||
|  | 			<span class="filter-details"> | ||||||
|  | 				<div class="actor-gender"> | ||||||
|  | 					<Gender | ||||||
|  | 						:gender="actor.gender" | ||||||
|  | 						class="gender" | ||||||
|  | 					/> | ||||||
|  | 				</div> | ||||||
|  | 
 | ||||||
|  | 				<span | ||||||
|  | 					v-if="actor.count" | ||||||
|  | 					class="filter-count" | ||||||
|  | 				>{{ actor.count }}</span> | ||||||
|  | 			</span> | ||||||
|  | 		</span> | ||||||
|  | 	</li> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <script setup> | ||||||
|  | import Gender from '#/components/actors/gender.vue'; | ||||||
|  | 
 | ||||||
|  | defineProps({ | ||||||
|  | 	actor: { | ||||||
|  | 		type: Object, | ||||||
|  | 		default: null, | ||||||
|  | 	}, | ||||||
|  | 	index: { | ||||||
|  | 		type: Number, | ||||||
|  | 		default: null, | ||||||
|  | 	}, | ||||||
|  | 	filters: { | ||||||
|  | 		type: Object, | ||||||
|  | 		default: null, | ||||||
|  | 	}, | ||||||
|  | 	type: { | ||||||
|  | 		type: String, | ||||||
|  | 		default: 'available', | ||||||
|  | 	}, | ||||||
|  | 	toggleActor: { | ||||||
|  | 		type: Function, | ||||||
|  | 		default: null, | ||||||
|  | 	}, | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | const emit = defineEmits(['actor']); | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <style scoped> | ||||||
|  | .filter-name { | ||||||
|  | 	align-items: stretch; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .actor-name { | ||||||
|  | 	display: flex; | ||||||
|  | 	align-items: stretch; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .actor-gender { | ||||||
|  | 	width: 1rem; | ||||||
|  | 	display: flex; | ||||||
|  | 	align-items: center; | ||||||
|  | 
 | ||||||
|  | 	.gender { | ||||||
|  | 		display: flex; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | </style> | ||||||
|  | @ -4,7 +4,7 @@ | ||||||
| 			<input | 			<input | ||||||
| 				v-model="search" | 				v-model="search" | ||||||
| 				type="search" | 				type="search" | ||||||
| 				placeholder="Filter actors" | 				:placeholder="`Filter ${actors.length} actors`" | ||||||
| 				class="input input-inline filters-search" | 				class="input input-inline filters-search" | ||||||
| 			> | 			> | ||||||
| 
 | 
 | ||||||
|  | @ -46,60 +46,44 @@ | ||||||
| 		</div> | 		</div> | ||||||
| 
 | 
 | ||||||
| 		<ul | 		<ul | ||||||
| 			v-for="(group, groupKey) in groupedActors" | 			v-for="(actor, index) in selectedActors" | ||||||
| 			:key="groupKey" | 			:key="`actor-${actor.id}`" | ||||||
| 			class="filter-items nolist" | 			class="filter-items nolist" | ||||||
| 		> | 		> | ||||||
| 			<li | 			<Actor | ||||||
| 				v-for="(actor, index) in group" | 				:actor="actor" | ||||||
| 				:key="`filter-actor-${actor.id}`" | 				:index="index" | ||||||
| 				class="filter-item" | 				:filters="filters" | ||||||
| 				:class="{ selected: filters.actors.some((filterActor) => filterActor.id === actor.id), first: groupKey === 'available' && index === 0 && filters.actors.length > 0 }" | 				:toggle-actor="toggleActor" | ||||||
| 				@click="emit('update', 'actors', [actor])" | 				type="selected" | ||||||
| 			> | 				@actor="(actor) => emit('update', 'actors', [actor])" | ||||||
| 				<div | 			/> | ||||||
| 					class="filter-include" |  | ||||||
| 					@click.stop="toggleActor(actor)" |  | ||||||
| 				> |  | ||||||
| 					<Icon |  | ||||||
| 						icon="checkmark" |  | ||||||
| 						class="filter-add" |  | ||||||
| 					/> |  | ||||||
| 
 |  | ||||||
| 					<Icon |  | ||||||
| 						icon="cross2" |  | ||||||
| 						class="filter-remove" |  | ||||||
| 					/> |  | ||||||
| 				</div> |  | ||||||
| 
 |  | ||||||
| 				<span class="filter-name actor-name"> |  | ||||||
| 					<span |  | ||||||
| 						class="filter-text" |  | ||||||
| 						:title="actor.name" |  | ||||||
| 					>{{ actor.name }}</span> |  | ||||||
| 
 |  | ||||||
| 					<span class="filter-details"> |  | ||||||
| 						<div class="actor-gender"> |  | ||||||
| 							<Gender |  | ||||||
| 								:gender="actor.gender" |  | ||||||
| 								class="gender" |  | ||||||
| 							/> |  | ||||||
| 						</div> |  | ||||||
| 
 |  | ||||||
| 						<span |  | ||||||
| 							v-if="actor.count" |  | ||||||
| 							class="filter-count" |  | ||||||
| 						>{{ actor.count }}</span> |  | ||||||
| 					</span> |  | ||||||
| 				</span> |  | ||||||
| 			</li> |  | ||||||
| 		</ul> | 		</ul> | ||||||
|  | 
 | ||||||
|  | 		<UseVirtualList | ||||||
|  | 			:list="availableActors" | ||||||
|  | 			:options="{ itemHeight: 30 }" | ||||||
|  | 			class="filter-items nolist" | ||||||
|  | 		> | ||||||
|  | 			<template #default="{ data: actor, index }"> | ||||||
|  | 				<Actor | ||||||
|  | 					:actor="actor" | ||||||
|  | 					:index="index" | ||||||
|  | 					:filters="filters" | ||||||
|  | 					:toggle-actor="toggleActor" | ||||||
|  | 					type="available" | ||||||
|  | 					@actor="(actor) => emit('update', 'actors', [actor])" | ||||||
|  | 				/> | ||||||
|  | 			</template> | ||||||
|  | 		</UseVirtualList> | ||||||
| 	</div> | 	</div> | ||||||
| </template> | </template> | ||||||
| 
 | 
 | ||||||
| <script setup> | <script setup> | ||||||
| import { ref, computed, inject } from 'vue'; | import { ref, computed, inject } from 'vue'; | ||||||
|  | import { UseVirtualList } from '@vueuse/components'; | ||||||
| 
 | 
 | ||||||
|  | import Actor from '#/components/filters/actor.vue'; | ||||||
| import Gender from '#/components/actors/gender.vue'; | import Gender from '#/components/actors/gender.vue'; | ||||||
| 
 | 
 | ||||||
| const props = defineProps({ | const props = defineProps({ | ||||||
|  | @ -123,21 +107,19 @@ const order = ref('name'); | ||||||
| const { pageProps } = inject('pageContext'); | const { pageProps } = inject('pageContext'); | ||||||
| const { actor: pageActor } = pageProps; | const { actor: pageActor } = pageProps; | ||||||
| 
 | 
 | ||||||
| const groupedActors = computed(() => ({ | const selectedActors = computed(() => props.filters.actors.map((filterActor) => props.actors.find((actor) => actor.id === filterActor.id)).filter(Boolean)); | ||||||
| 	selected: props.filters.actors.map((filterActor) => props.actors.find((actor) => actor.id === filterActor.id)).filter(Boolean), | const availableActors = computed(() => props.actors | ||||||
| 	available: props.actors | 	.filter((actor) => !props.filters.actors.some((filterActor) => filterActor.id === actor.id) | ||||||
| 		.filter((actor) => !props.filters.actors.some((filterActor) => filterActor.id === actor.id) | 		&& actor.id !== pageActor?.id | ||||||
| 			&& actor.id !== pageActor?.id | 		&& searchRegexp.value.test(actor.name) | ||||||
| 			&& searchRegexp.value.test(actor.name) | 		&& (!selectedGender.value || actor.gender === selectedGender.value)) | ||||||
| 			&& (!selectedGender.value || actor.gender === selectedGender.value)) | 	.sort((actorA, actorB) => { | ||||||
| 		.sort((actorA, actorB) => { | 		if (order.value === 'count') { | ||||||
| 			if (order.value === 'count') { | 			return actorB.count - actorA.count; | ||||||
| 				return actorB.count - actorA.count; | 		} | ||||||
| 			} |  | ||||||
| 
 | 
 | ||||||
| 			return actorA.name.localeCompare(actorB.name); | 		return actorA.name.localeCompare(actorB.name); | ||||||
| 		}), | 	})); | ||||||
| })); |  | ||||||
| 
 | 
 | ||||||
| const genders = computed(() => [null, ...['female', 'male', 'transsexual', 'other'].filter((gender) => props.actors.some((actor) => actor.gender === gender))]); | const genders = computed(() => [null, ...['female', 'male', 'transsexual', 'other'].filter((gender) => props.actors.some((actor) => actor.gender === gender))]); | ||||||
| 
 | 
 | ||||||
|  | @ -171,22 +153,8 @@ function selectGender() { | ||||||
| 	border-top: solid 1px var(--shadow-weak-30); | 	border-top: solid 1px var(--shadow-weak-30); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .filter-name { | .list { | ||||||
| 	align-items: stretch; | 	height: 15rem; | ||||||
| } | 	overflow-y: auto; | ||||||
| 
 |  | ||||||
| .actor-name { |  | ||||||
| 	display: flex; |  | ||||||
| 	align-items: stretch; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .actor-gender { |  | ||||||
| 	width: 1rem; |  | ||||||
| 	display: flex; |  | ||||||
| 	align-items: center; |  | ||||||
| 
 |  | ||||||
| 	.gender { |  | ||||||
| 		display: flex; |  | ||||||
| 	} |  | ||||||
| } | } | ||||||
| </style> | </style> | ||||||
|  |  | ||||||
|  | @ -4,7 +4,7 @@ | ||||||
| 			<input | 			<input | ||||||
| 				v-model="search" | 				v-model="search" | ||||||
| 				type="search" | 				type="search" | ||||||
| 				placeholder="Filter channels" | 				:placeholder="`Filter ${channels.length} channels`" | ||||||
| 				class="input input-inline filters-search" | 				class="input input-inline filters-search" | ||||||
| 			> | 			> | ||||||
| 
 | 
 | ||||||
|  | @ -113,7 +113,10 @@ const entities = computed(() => { | ||||||
| 
 | 
 | ||||||
| 	return Object.values(filteredChannels.reduce((acc, channel) => { | 	return Object.values(filteredChannels.reduce((acc, channel) => { | ||||||
| 		if (!channel.parent || channel.isIndependent) { | 		if (!channel.parent || channel.isIndependent) { | ||||||
| 			acc[channel.id] = channel; | 			acc[channel.id] = { | ||||||
|  | 				...channel, | ||||||
|  | 				children: [], | ||||||
|  | 			}; | ||||||
| 
 | 
 | ||||||
| 			return acc; | 			return acc; | ||||||
| 		} | 		} | ||||||
|  | @ -151,7 +154,7 @@ const entities = computed(() => { | ||||||
| 
 | 
 | ||||||
| .filter-item.channel { | .filter-item.channel { | ||||||
| 	.filter-text .icon { | 	.filter-text .icon { | ||||||
| 		width: 1.5rem; | 		width: 2.25rem; | ||||||
| 		height: 1rem; | 		height: 1rem; | ||||||
| 		transform: rotate(-135deg); | 		transform: rotate(-135deg); | ||||||
| 		fill: var(--shadow-weak-30); | 		fill: var(--shadow-weak-30); | ||||||
|  | @ -160,7 +163,7 @@ const entities = computed(() => { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .favicon { | .favicon { | ||||||
| 	width: 1rem; | 	width: 1.75rem; | ||||||
| 	height: 1rem; | 	height: 1rem; | ||||||
| 	margin-right: .5rem; | 	margin-right: .5rem; | ||||||
| 	object-fit: contain; | 	object-fit: contain; | ||||||
|  |  | ||||||
|  | @ -266,7 +266,7 @@ function toggleFilters(state) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .filter-count { | .filter-count { | ||||||
| 	width: 1.5rem; | 	width: 1.75rem; | ||||||
| 	display: flex; | 	display: flex; | ||||||
| 	align-items: center; | 	align-items: center; | ||||||
| 	justify-content: center; | 	justify-content: center; | ||||||
|  |  | ||||||
|  | @ -4,7 +4,7 @@ | ||||||
| 			<input | 			<input | ||||||
| 				v-model="search" | 				v-model="search" | ||||||
| 				type="search" | 				type="search" | ||||||
| 				placeholder="Filter tags" | 				:placeholder="`Filter ${tags.length} tags`" | ||||||
| 				class="input input-inline filters-search" | 				class="input input-inline filters-search" | ||||||
| 			> | 			> | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -149,8 +149,14 @@ function go(page, event) { | ||||||
| 
 | 
 | ||||||
| function getPath(page) { | function getPath(page) { | ||||||
| 	const path = parse(routeParams.path) | 	const path = parse(routeParams.path) | ||||||
| 		.map((segment) => (segment.name === 'page' ? String(page) : routeParams[segment.name] || segment)) | 		.map((segment) => { | ||||||
| 		.join('/'); | 			if (segment.name === 'page') { | ||||||
|  | 				return `/${page}`; | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			return `${segment.prefix || ''}${routeParams[segment.name] || segment}`; | ||||||
|  | 		}) | ||||||
|  | 		.join(''); | ||||||
| 
 | 
 | ||||||
| 	if (props.includeQuery && urlParsed.searchOriginal) { | 	if (props.includeQuery && urlParsed.searchOriginal) { | ||||||
| 		return `${path}${urlParsed.searchOriginal}`; | 		return `${path}${urlParsed.searchOriginal}`; | ||||||
|  | @ -165,6 +171,7 @@ function getPath(page) { | ||||||
| 	height: 5rem; | 	height: 5rem; | ||||||
| 	display: flex; | 	display: flex; | ||||||
| 	justify-content: center; | 	justify-content: center; | ||||||
|  | 	flex-shrink: 0; | ||||||
| 	box-sizing: border-box; | 	box-sizing: border-box; | ||||||
| 	padding: 1rem; | 	padding: 1rem; | ||||||
| 	font-size: 0; | 	font-size: 0; | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| <template> | <template> | ||||||
| 	<div | 	<div | ||||||
| 		class="page" | 		class="scenes-page" | ||||||
| 	> | 	> | ||||||
| 		<Filters | 		<Filters | ||||||
| 			v-if="showFilters" | 			v-if="showFilters" | ||||||
|  | @ -34,9 +34,23 @@ | ||||||
| 				class="scenes-header" | 				class="scenes-header" | ||||||
| 			> | 			> | ||||||
| 				<div class="meta">{{ total }} results</div> | 				<div class="meta">{{ total }} results</div> | ||||||
|  | 
 | ||||||
|  | 				<select | ||||||
|  | 					v-model="scope" | ||||||
|  | 					class="input" | ||||||
|  | 					@change="search" | ||||||
|  | 				> | ||||||
|  | 					<option value="likes">Likes</option> | ||||||
|  | 					<option value="latest">Latest</option> | ||||||
|  | 					<option value="upcoming">Upcoming</option> | ||||||
|  | 					<option value="new">New</option> | ||||||
|  | 				</select> | ||||||
| 			</div> | 			</div> | ||||||
| 
 | 
 | ||||||
| 			<nav class="scopes"> | 			<nav | ||||||
|  | 				v-if="showScopeTabs" | ||||||
|  | 				class="scopes" | ||||||
|  | 			> | ||||||
| 				<Link | 				<Link | ||||||
| 					:href="getPath('latest')" | 					:href="getPath('latest')" | ||||||
| 					class="scope nolink" | 					class="scope nolink" | ||||||
|  | @ -103,15 +117,18 @@ defineProps({ | ||||||
| 		type: Boolean, | 		type: Boolean, | ||||||
| 		default: true, | 		default: true, | ||||||
| 	}, | 	}, | ||||||
|  | 	showScopeTabs: { | ||||||
|  | 		type: Boolean, | ||||||
|  | 		default: false, | ||||||
|  | 	}, | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| const { pageProps, routeParams, urlParsed } = inject('pageContext'); | const { pageProps, routeParams, urlParsed } = inject('pageContext'); | ||||||
| const { scope } = routeParams; |  | ||||||
| 
 | 
 | ||||||
| const { | const { | ||||||
| 	actor: pageActor, | 	actor: pageActor, | ||||||
| 	tag: pageTag, | 	tag: pageTag, | ||||||
| 	channel: pageChannel, | 	entity: pageEntity, | ||||||
| } = pageProps; | } = pageProps; | ||||||
| 
 | 
 | ||||||
| const scenes = ref(pageProps.scenes); | const scenes = ref(pageProps.scenes); | ||||||
|  | @ -120,6 +137,7 @@ const aggTags = ref(pageProps.aggTags || []); | ||||||
| const aggChannels = ref(pageProps.aggChannels || []); | const aggChannels = ref(pageProps.aggChannels || []); | ||||||
| 
 | 
 | ||||||
| const currentPage = ref(Number(routeParams.page)); | const currentPage = ref(Number(routeParams.page)); | ||||||
|  | const scope = ref(routeParams.scope); | ||||||
| const total = ref(Number(pageProps.total)); | const total = ref(Number(pageProps.total)); | ||||||
| const loading = ref(false); | const loading = ref(false); | ||||||
| 
 | 
 | ||||||
|  | @ -138,19 +156,17 @@ const filters = ref({ | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| function getPath(targetScope, preserveQuery) { | function getPath(targetScope, preserveQuery) { | ||||||
| 	const path = parse(routeParams.path) | 	const path = parse(routeParams.path).map((segment) => { | ||||||
| 		.map((segment) => { | 		if (segment.name === 'scope') { | ||||||
| 			if (segment.name === 'scope') { | 			return `${segment.prefix}${targetScope}`; | ||||||
| 				return targetScope; | 		} | ||||||
| 			} |  | ||||||
| 
 | 
 | ||||||
| 			if (segment.name === 'page') { | 		if (segment.name === 'page') { | ||||||
| 				return 1; | 			return `${segment.prefix}${1}`; | ||||||
| 			} | 		} | ||||||
| 
 | 
 | ||||||
| 			return routeParams[segment.name] || segment; | 		return `${segment.prefix || ''}${routeParams[segment.name] || segment}`; | ||||||
| 		}) | 	}).join(''); | ||||||
| 		.join('/'); |  | ||||||
| 
 | 
 | ||||||
| 	if (preserveQuery && urlParsed.searchOriginal) { | 	if (preserveQuery && urlParsed.searchOriginal) { | ||||||
| 		return `${path}${urlParsed.searchOriginal}`; | 		return `${path}${urlParsed.searchOriginal}`; | ||||||
|  | @ -166,7 +182,7 @@ async function search(resetPage = true) { | ||||||
| 
 | 
 | ||||||
| 	const query = {}; | 	const query = {}; | ||||||
| 
 | 
 | ||||||
| 	const entity = filters.value.entity || pageChannel; | 	const entity = filters.value.entity || pageEntity; | ||||||
| 	const entitySlug = entity?.type === 'network' ? `_${entity.slug}` : entity?.slug; | 	const entitySlug = entity?.type === 'network' ? `_${entity.slug}` : entity?.slug; | ||||||
| 
 | 
 | ||||||
| 	loading.value = true; | 	loading.value = true; | ||||||
|  | @ -176,7 +192,7 @@ async function search(resetPage = true) { | ||||||
| 		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 | 		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(','), | 		tags: [pageTag?.slug, ...filters.value.tags].filter(Boolean).join(','), | ||||||
| 		e: entitySlug, | 		e: entitySlug, | ||||||
| 		scope, | 		scope: scope.value, | ||||||
| 		page: currentPage.value, // client uses param rather than query pagination | 		page: currentPage.value, // client uses param rather than query pagination | ||||||
| 	}); | 	}); | ||||||
| 
 | 
 | ||||||
|  | @ -184,12 +200,13 @@ async function search(resetPage = true) { | ||||||
| 	aggActors.value = res.aggActors; | 	aggActors.value = res.aggActors; | ||||||
| 	aggTags.value = res.aggTags; | 	aggTags.value = res.aggTags; | ||||||
| 	aggChannels.value = res.aggChannels; | 	aggChannels.value = res.aggChannels; | ||||||
| 	total.value = res.total; |  | ||||||
| 
 | 
 | ||||||
|  | 	total.value = res.total; | ||||||
| 	loading.value = false; | 	loading.value = false; | ||||||
|  | 
 | ||||||
| 	events.emit('scrollUp'); | 	events.emit('scrollUp'); | ||||||
| 
 | 
 | ||||||
| 	navigate(getPath(scope, false), { | 	navigate(getPath(scope.value, false), { | ||||||
| 		...query, | 		...query, | ||||||
| 		actors: filters.value.actors.map((filterActor) => getActorIdentifier(filterActor)).join(',') || undefined, // don't include page actor ID in query, already a parameter | 		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, | 		tags: filters.value.tags.join(',') || undefined, | ||||||
|  | @ -207,7 +224,7 @@ function updateFilter(prop, value, reload = true) { | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| <style scoped> | <style scoped> | ||||||
| .page { | .scenes-page { | ||||||
| 	display: flex; | 	display: flex; | ||||||
| 	background: var(--background-base-10); | 	background: var(--background-base-10); | ||||||
| 	position: relative; | 	position: relative; | ||||||
|  | @ -216,7 +233,7 @@ function updateFilter(prop, value, reload = true) { | ||||||
| .scenes-header { | .scenes-header { | ||||||
| 	display: flex; | 	display: flex; | ||||||
| 	align-items: center; | 	align-items: center; | ||||||
| 	padding: 1rem 0 .25rem 3rem; | 	padding: .5rem 1rem .25rem 3rem; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .scenes-container { | .scenes-container { | ||||||
|  | @ -236,7 +253,7 @@ function updateFilter(prop, value, reload = true) { | ||||||
| 	display: grid; | 	display: grid; | ||||||
|     grid-template-columns: repeat(auto-fill, minmax(22rem, 1fr)); |     grid-template-columns: repeat(auto-fill, minmax(22rem, 1fr)); | ||||||
| 	gap: .75rem .5rem; | 	gap: .75rem .5rem; | ||||||
| 	padding: 1rem; | 	padding: .5rem 1rem 1rem 1rem; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .scopes { | .scopes { | ||||||
|  |  | ||||||
|  | @ -13,6 +13,8 @@ | ||||||
|         "@vitejs/plugin-vue": "^4.5.2", |         "@vitejs/plugin-vue": "^4.5.2", | ||||||
|         "@vue/compiler-sfc": "^3.3.10", |         "@vue/compiler-sfc": "^3.3.10", | ||||||
|         "@vue/server-renderer": "^3.3.10", |         "@vue/server-renderer": "^3.3.10", | ||||||
|  |         "@vueuse/components": "^10.7.1", | ||||||
|  |         "@vueuse/core": "^10.7.1", | ||||||
|         "compression": "^1.7.4", |         "compression": "^1.7.4", | ||||||
|         "config": "^3.3.9", |         "config": "^3.3.9", | ||||||
|         "convert": "^4.14.1", |         "convert": "^4.14.1", | ||||||
|  | @ -34,6 +36,7 @@ | ||||||
|         "vike": "^0.4.150", |         "vike": "^0.4.150", | ||||||
|         "vite": "^4.5.1", |         "vite": "^4.5.1", | ||||||
|         "vue": "^3.3.10", |         "vue": "^3.3.10", | ||||||
|  |         "vue-virtual-scroller": "^2.0.0-beta.8", | ||||||
|         "winston": "^3.11.0", |         "winston": "^3.11.0", | ||||||
|         "winston-daily-rotate-file": "^4.7.1" |         "winston-daily-rotate-file": "^4.7.1" | ||||||
|       }, |       }, | ||||||
|  | @ -2747,6 +2750,11 @@ | ||||||
|       "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", |       "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", | ||||||
|       "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==" |       "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==" | ||||||
|     }, |     }, | ||||||
|  |     "node_modules/@types/web-bluetooth": { | ||||||
|  |       "version": "0.0.20", | ||||||
|  |       "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz", | ||||||
|  |       "integrity": "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==" | ||||||
|  |     }, | ||||||
|     "node_modules/@ungap/structured-clone": { |     "node_modules/@ungap/structured-clone": { | ||||||
|       "version": "1.2.0", |       "version": "1.2.0", | ||||||
|       "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", |       "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", | ||||||
|  | @ -2867,6 +2875,124 @@ | ||||||
|       "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.12.tgz", |       "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.12.tgz", | ||||||
|       "integrity": "sha512-6p0Yin0pclvnER7BLNOQuod9Z+cxSYh8pSh7CzHnWNjAIP6zrTlCdHRvSCb1aYEx6i3Q3kvfuWU7nG16CgG1ag==" |       "integrity": "sha512-6p0Yin0pclvnER7BLNOQuod9Z+cxSYh8pSh7CzHnWNjAIP6zrTlCdHRvSCb1aYEx6i3Q3kvfuWU7nG16CgG1ag==" | ||||||
|     }, |     }, | ||||||
|  |     "node_modules/@vueuse/components": { | ||||||
|  |       "version": "10.7.1", | ||||||
|  |       "resolved": "https://registry.npmjs.org/@vueuse/components/-/components-10.7.1.tgz", | ||||||
|  |       "integrity": "sha512-bAS5ff1uNhNSeIYL7R3qeR0DP5u+Lutb0mFiTZTMhcicjhBfGiDbahHqoOAr9M/wfpYKNluP1U107y2fUTw4yw==", | ||||||
|  |       "dependencies": { | ||||||
|  |         "@vueuse/core": "10.7.1", | ||||||
|  |         "@vueuse/shared": "10.7.1", | ||||||
|  |         "vue-demi": ">=0.14.6" | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "node_modules/@vueuse/components/node_modules/vue-demi": { | ||||||
|  |       "version": "0.14.6", | ||||||
|  |       "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.6.tgz", | ||||||
|  |       "integrity": "sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==", | ||||||
|  |       "hasInstallScript": true, | ||||||
|  |       "bin": { | ||||||
|  |         "vue-demi-fix": "bin/vue-demi-fix.js", | ||||||
|  |         "vue-demi-switch": "bin/vue-demi-switch.js" | ||||||
|  |       }, | ||||||
|  |       "engines": { | ||||||
|  |         "node": ">=12" | ||||||
|  |       }, | ||||||
|  |       "funding": { | ||||||
|  |         "url": "https://github.com/sponsors/antfu" | ||||||
|  |       }, | ||||||
|  |       "peerDependencies": { | ||||||
|  |         "@vue/composition-api": "^1.0.0-rc.1", | ||||||
|  |         "vue": "^3.0.0-0 || ^2.6.0" | ||||||
|  |       }, | ||||||
|  |       "peerDependenciesMeta": { | ||||||
|  |         "@vue/composition-api": { | ||||||
|  |           "optional": true | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "node_modules/@vueuse/core": { | ||||||
|  |       "version": "10.7.1", | ||||||
|  |       "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.7.1.tgz", | ||||||
|  |       "integrity": "sha512-74mWHlaesJSWGp1ihg76vAnfVq9NTv1YT0SYhAQ6zwFNdBkkP+CKKJmVOEHcdSnLXCXYiL5e7MaewblfiYLP7g==", | ||||||
|  |       "dependencies": { | ||||||
|  |         "@types/web-bluetooth": "^0.0.20", | ||||||
|  |         "@vueuse/metadata": "10.7.1", | ||||||
|  |         "@vueuse/shared": "10.7.1", | ||||||
|  |         "vue-demi": ">=0.14.6" | ||||||
|  |       }, | ||||||
|  |       "funding": { | ||||||
|  |         "url": "https://github.com/sponsors/antfu" | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "node_modules/@vueuse/core/node_modules/vue-demi": { | ||||||
|  |       "version": "0.14.6", | ||||||
|  |       "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.6.tgz", | ||||||
|  |       "integrity": "sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==", | ||||||
|  |       "hasInstallScript": true, | ||||||
|  |       "bin": { | ||||||
|  |         "vue-demi-fix": "bin/vue-demi-fix.js", | ||||||
|  |         "vue-demi-switch": "bin/vue-demi-switch.js" | ||||||
|  |       }, | ||||||
|  |       "engines": { | ||||||
|  |         "node": ">=12" | ||||||
|  |       }, | ||||||
|  |       "funding": { | ||||||
|  |         "url": "https://github.com/sponsors/antfu" | ||||||
|  |       }, | ||||||
|  |       "peerDependencies": { | ||||||
|  |         "@vue/composition-api": "^1.0.0-rc.1", | ||||||
|  |         "vue": "^3.0.0-0 || ^2.6.0" | ||||||
|  |       }, | ||||||
|  |       "peerDependenciesMeta": { | ||||||
|  |         "@vue/composition-api": { | ||||||
|  |           "optional": true | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "node_modules/@vueuse/metadata": { | ||||||
|  |       "version": "10.7.1", | ||||||
|  |       "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.7.1.tgz", | ||||||
|  |       "integrity": "sha512-jX8MbX5UX067DYVsbtrmKn6eG6KMcXxLRLlurGkZku5ZYT3vxgBjui2zajvUZ18QLIjrgBkFRsu7CqTAg18QFw==", | ||||||
|  |       "funding": { | ||||||
|  |         "url": "https://github.com/sponsors/antfu" | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "node_modules/@vueuse/shared": { | ||||||
|  |       "version": "10.7.1", | ||||||
|  |       "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-10.7.1.tgz", | ||||||
|  |       "integrity": "sha512-v0jbRR31LSgRY/C5i5X279A/WQjD6/JsMzGa+eqt658oJ75IvQXAeONmwvEMrvJQKnRElq/frzBR7fhmWY5uLw==", | ||||||
|  |       "dependencies": { | ||||||
|  |         "vue-demi": ">=0.14.6" | ||||||
|  |       }, | ||||||
|  |       "funding": { | ||||||
|  |         "url": "https://github.com/sponsors/antfu" | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "node_modules/@vueuse/shared/node_modules/vue-demi": { | ||||||
|  |       "version": "0.14.6", | ||||||
|  |       "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.6.tgz", | ||||||
|  |       "integrity": "sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==", | ||||||
|  |       "hasInstallScript": true, | ||||||
|  |       "bin": { | ||||||
|  |         "vue-demi-fix": "bin/vue-demi-fix.js", | ||||||
|  |         "vue-demi-switch": "bin/vue-demi-switch.js" | ||||||
|  |       }, | ||||||
|  |       "engines": { | ||||||
|  |         "node": ">=12" | ||||||
|  |       }, | ||||||
|  |       "funding": { | ||||||
|  |         "url": "https://github.com/sponsors/antfu" | ||||||
|  |       }, | ||||||
|  |       "peerDependencies": { | ||||||
|  |         "@vue/composition-api": "^1.0.0-rc.1", | ||||||
|  |         "vue": "^3.0.0-0 || ^2.6.0" | ||||||
|  |       }, | ||||||
|  |       "peerDependenciesMeta": { | ||||||
|  |         "@vue/composition-api": { | ||||||
|  |           "optional": true | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|     "node_modules/accepts": { |     "node_modules/accepts": { | ||||||
|       "version": "1.3.8", |       "version": "1.3.8", | ||||||
|       "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", |       "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", | ||||||
|  | @ -8077,6 +8203,40 @@ | ||||||
|       "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", |       "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", | ||||||
|       "dev": true |       "dev": true | ||||||
|     }, |     }, | ||||||
|  |     "node_modules/vue-observe-visibility": { | ||||||
|  |       "version": "2.0.0-alpha.1", | ||||||
|  |       "resolved": "https://registry.npmjs.org/vue-observe-visibility/-/vue-observe-visibility-2.0.0-alpha.1.tgz", | ||||||
|  |       "integrity": "sha512-flFbp/gs9pZniXR6fans8smv1kDScJ8RS7rEpMjhVabiKeq7Qz3D9+eGsypncjfIyyU84saU88XZ0zjbD6Gq/g==", | ||||||
|  |       "peerDependencies": { | ||||||
|  |         "vue": "^3.0.0" | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "node_modules/vue-resize": { | ||||||
|  |       "version": "2.0.0-alpha.1", | ||||||
|  |       "resolved": "https://registry.npmjs.org/vue-resize/-/vue-resize-2.0.0-alpha.1.tgz", | ||||||
|  |       "integrity": "sha512-7+iqOueLU7uc9NrMfrzbG8hwMqchfVfSzpVlCMeJQe4pyibqyoifDNbKTZvwxZKDvGkB+PdFeKvnGZMoEb8esg==", | ||||||
|  |       "peerDependencies": { | ||||||
|  |         "vue": "^3.0.0" | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "node_modules/vue-virtual-scroller": { | ||||||
|  |       "version": "2.0.0-beta.8", | ||||||
|  |       "resolved": "https://registry.npmjs.org/vue-virtual-scroller/-/vue-virtual-scroller-2.0.0-beta.8.tgz", | ||||||
|  |       "integrity": "sha512-b8/f5NQ5nIEBRTNi6GcPItE4s7kxNHw2AIHLtDp+2QvqdTjVN0FgONwX9cr53jWRgnu+HRLPaWDOR2JPI5MTfQ==", | ||||||
|  |       "dependencies": { | ||||||
|  |         "mitt": "^2.1.0", | ||||||
|  |         "vue-observe-visibility": "^2.0.0-alpha.1", | ||||||
|  |         "vue-resize": "^2.0.0-alpha.1" | ||||||
|  |       }, | ||||||
|  |       "peerDependencies": { | ||||||
|  |         "vue": "^3.2.0" | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "node_modules/vue-virtual-scroller/node_modules/mitt": { | ||||||
|  |       "version": "2.1.0", | ||||||
|  |       "resolved": "https://registry.npmjs.org/mitt/-/mitt-2.1.0.tgz", | ||||||
|  |       "integrity": "sha512-ILj2TpLiysu2wkBbWjAmww7TkZb65aiQO+DkVdUTBpBXq+MHYiETENkKFMtsJZX1Lf4pe4QOrTSjIfUwN5lRdg==" | ||||||
|  |     }, | ||||||
|     "node_modules/which": { |     "node_modules/which": { | ||||||
|       "version": "2.0.2", |       "version": "2.0.2", | ||||||
|       "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", |       "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", | ||||||
|  | @ -9968,6 +10128,11 @@ | ||||||
|       "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", |       "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", | ||||||
|       "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==" |       "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==" | ||||||
|     }, |     }, | ||||||
|  |     "@types/web-bluetooth": { | ||||||
|  |       "version": "0.0.20", | ||||||
|  |       "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz", | ||||||
|  |       "integrity": "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==" | ||||||
|  |     }, | ||||||
|     "@ungap/structured-clone": { |     "@ungap/structured-clone": { | ||||||
|       "version": "1.2.0", |       "version": "1.2.0", | ||||||
|       "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", |       "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", | ||||||
|  | @ -10079,6 +10244,64 @@ | ||||||
|       "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.12.tgz", |       "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.12.tgz", | ||||||
|       "integrity": "sha512-6p0Yin0pclvnER7BLNOQuod9Z+cxSYh8pSh7CzHnWNjAIP6zrTlCdHRvSCb1aYEx6i3Q3kvfuWU7nG16CgG1ag==" |       "integrity": "sha512-6p0Yin0pclvnER7BLNOQuod9Z+cxSYh8pSh7CzHnWNjAIP6zrTlCdHRvSCb1aYEx6i3Q3kvfuWU7nG16CgG1ag==" | ||||||
|     }, |     }, | ||||||
|  |     "@vueuse/components": { | ||||||
|  |       "version": "10.7.1", | ||||||
|  |       "resolved": "https://registry.npmjs.org/@vueuse/components/-/components-10.7.1.tgz", | ||||||
|  |       "integrity": "sha512-bAS5ff1uNhNSeIYL7R3qeR0DP5u+Lutb0mFiTZTMhcicjhBfGiDbahHqoOAr9M/wfpYKNluP1U107y2fUTw4yw==", | ||||||
|  |       "requires": { | ||||||
|  |         "@vueuse/core": "10.7.1", | ||||||
|  |         "@vueuse/shared": "10.7.1", | ||||||
|  |         "vue-demi": ">=0.14.6" | ||||||
|  |       }, | ||||||
|  |       "dependencies": { | ||||||
|  |         "vue-demi": { | ||||||
|  |           "version": "0.14.6", | ||||||
|  |           "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.6.tgz", | ||||||
|  |           "integrity": "sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==", | ||||||
|  |           "requires": {} | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "@vueuse/core": { | ||||||
|  |       "version": "10.7.1", | ||||||
|  |       "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.7.1.tgz", | ||||||
|  |       "integrity": "sha512-74mWHlaesJSWGp1ihg76vAnfVq9NTv1YT0SYhAQ6zwFNdBkkP+CKKJmVOEHcdSnLXCXYiL5e7MaewblfiYLP7g==", | ||||||
|  |       "requires": { | ||||||
|  |         "@types/web-bluetooth": "^0.0.20", | ||||||
|  |         "@vueuse/metadata": "10.7.1", | ||||||
|  |         "@vueuse/shared": "10.7.1", | ||||||
|  |         "vue-demi": ">=0.14.6" | ||||||
|  |       }, | ||||||
|  |       "dependencies": { | ||||||
|  |         "vue-demi": { | ||||||
|  |           "version": "0.14.6", | ||||||
|  |           "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.6.tgz", | ||||||
|  |           "integrity": "sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==", | ||||||
|  |           "requires": {} | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "@vueuse/metadata": { | ||||||
|  |       "version": "10.7.1", | ||||||
|  |       "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.7.1.tgz", | ||||||
|  |       "integrity": "sha512-jX8MbX5UX067DYVsbtrmKn6eG6KMcXxLRLlurGkZku5ZYT3vxgBjui2zajvUZ18QLIjrgBkFRsu7CqTAg18QFw==" | ||||||
|  |     }, | ||||||
|  |     "@vueuse/shared": { | ||||||
|  |       "version": "10.7.1", | ||||||
|  |       "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-10.7.1.tgz", | ||||||
|  |       "integrity": "sha512-v0jbRR31LSgRY/C5i5X279A/WQjD6/JsMzGa+eqt658oJ75IvQXAeONmwvEMrvJQKnRElq/frzBR7fhmWY5uLw==", | ||||||
|  |       "requires": { | ||||||
|  |         "vue-demi": ">=0.14.6" | ||||||
|  |       }, | ||||||
|  |       "dependencies": { | ||||||
|  |         "vue-demi": { | ||||||
|  |           "version": "0.14.6", | ||||||
|  |           "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.6.tgz", | ||||||
|  |           "integrity": "sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==", | ||||||
|  |           "requires": {} | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|     "accepts": { |     "accepts": { | ||||||
|       "version": "1.3.8", |       "version": "1.3.8", | ||||||
|       "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", |       "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", | ||||||
|  | @ -13679,6 +13902,35 @@ | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|  |     "vue-observe-visibility": { | ||||||
|  |       "version": "2.0.0-alpha.1", | ||||||
|  |       "resolved": "https://registry.npmjs.org/vue-observe-visibility/-/vue-observe-visibility-2.0.0-alpha.1.tgz", | ||||||
|  |       "integrity": "sha512-flFbp/gs9pZniXR6fans8smv1kDScJ8RS7rEpMjhVabiKeq7Qz3D9+eGsypncjfIyyU84saU88XZ0zjbD6Gq/g==", | ||||||
|  |       "requires": {} | ||||||
|  |     }, | ||||||
|  |     "vue-resize": { | ||||||
|  |       "version": "2.0.0-alpha.1", | ||||||
|  |       "resolved": "https://registry.npmjs.org/vue-resize/-/vue-resize-2.0.0-alpha.1.tgz", | ||||||
|  |       "integrity": "sha512-7+iqOueLU7uc9NrMfrzbG8hwMqchfVfSzpVlCMeJQe4pyibqyoifDNbKTZvwxZKDvGkB+PdFeKvnGZMoEb8esg==", | ||||||
|  |       "requires": {} | ||||||
|  |     }, | ||||||
|  |     "vue-virtual-scroller": { | ||||||
|  |       "version": "2.0.0-beta.8", | ||||||
|  |       "resolved": "https://registry.npmjs.org/vue-virtual-scroller/-/vue-virtual-scroller-2.0.0-beta.8.tgz", | ||||||
|  |       "integrity": "sha512-b8/f5NQ5nIEBRTNi6GcPItE4s7kxNHw2AIHLtDp+2QvqdTjVN0FgONwX9cr53jWRgnu+HRLPaWDOR2JPI5MTfQ==", | ||||||
|  |       "requires": { | ||||||
|  |         "mitt": "^2.1.0", | ||||||
|  |         "vue-observe-visibility": "^2.0.0-alpha.1", | ||||||
|  |         "vue-resize": "^2.0.0-alpha.1" | ||||||
|  |       }, | ||||||
|  |       "dependencies": { | ||||||
|  |         "mitt": { | ||||||
|  |           "version": "2.1.0", | ||||||
|  |           "resolved": "https://registry.npmjs.org/mitt/-/mitt-2.1.0.tgz", | ||||||
|  |           "integrity": "sha512-ILj2TpLiysu2wkBbWjAmww7TkZb65aiQO+DkVdUTBpBXq+MHYiETENkKFMtsJZX1Lf4pe4QOrTSjIfUwN5lRdg==" | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|     "which": { |     "which": { | ||||||
|       "version": "2.0.2", |       "version": "2.0.2", | ||||||
|       "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", |       "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", | ||||||
|  |  | ||||||
|  | @ -13,6 +13,8 @@ | ||||||
|     "@vitejs/plugin-vue": "^4.5.2", |     "@vitejs/plugin-vue": "^4.5.2", | ||||||
|     "@vue/compiler-sfc": "^3.3.10", |     "@vue/compiler-sfc": "^3.3.10", | ||||||
|     "@vue/server-renderer": "^3.3.10", |     "@vue/server-renderer": "^3.3.10", | ||||||
|  |     "@vueuse/components": "^10.7.1", | ||||||
|  |     "@vueuse/core": "^10.7.1", | ||||||
|     "compression": "^1.7.4", |     "compression": "^1.7.4", | ||||||
|     "config": "^3.3.9", |     "config": "^3.3.9", | ||||||
|     "convert": "^4.14.1", |     "convert": "^4.14.1", | ||||||
|  | @ -34,6 +36,7 @@ | ||||||
|     "vike": "^0.4.150", |     "vike": "^0.4.150", | ||||||
|     "vite": "^4.5.1", |     "vite": "^4.5.1", | ||||||
|     "vue": "^3.3.10", |     "vue": "^3.3.10", | ||||||
|  |     "vue-virtual-scroller": "^2.0.0-beta.8", | ||||||
|     "winston": "^3.11.0", |     "winston": "^3.11.0", | ||||||
|     "winston-daily-rotate-file": "^4.7.1" |     "winston-daily-rotate-file": "^4.7.1" | ||||||
|   }, |   }, | ||||||
|  |  | ||||||
|  | @ -1,14 +1,27 @@ | ||||||
| <template> | <template> | ||||||
|   <div v-if="is404"> | 	<div v-if="is404"> | ||||||
|     <h1>404 Page Not Found</h1> | 		<h1>404 Page Not Found</h1> | ||||||
|     <p>This page could not be found.</p> | 
 | ||||||
|   </div> | 		<p v-if="abortReason">{{ abortReason }}</p> | ||||||
|   <div v-else> | 		<p v-else>This page could not be found.</p> | ||||||
|     <h1>500 Internal Error</h1> | 	</div> | ||||||
|     <p>Something went wrong.</p> | 
 | ||||||
|   </div> | 	<div v-else> | ||||||
|  | 		<h1>500 Internal Error</h1> | ||||||
|  | 		<p>Something went wrong.</p> | ||||||
|  | 	</div> | ||||||
| </template> | </template> | ||||||
| 
 | 
 | ||||||
| <script setup> | <script setup> | ||||||
| defineProps(['is404']) | import { inject } from 'vue'; | ||||||
|  | 
 | ||||||
|  | defineProps({ | ||||||
|  | 	is404: { | ||||||
|  | 		type: Boolean, | ||||||
|  | 		default: false, | ||||||
|  | 	}, | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | const pageContext = inject('pageContext'); | ||||||
|  | const { abortReason } = pageContext; | ||||||
| </script> | </script> | ||||||
|  |  | ||||||
|  | @ -58,7 +58,6 @@ import Scenes from '#/components/scenes/scenes.vue'; | ||||||
| 
 | 
 | ||||||
| const pageContext = inject('pageContext'); | const pageContext = inject('pageContext'); | ||||||
| const { pageProps } = pageContext; | const { pageProps } = pageContext; | ||||||
| 
 |  | ||||||
| const { actor } = pageProps; | const { actor } = pageProps; | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -0,0 +1,125 @@ | ||||||
|  | <template> | ||||||
|  | 	<div class="page"> | ||||||
|  | 		<div | ||||||
|  | 			v-for="(section, index) in sections" | ||||||
|  | 			:key="`section-${index}`" | ||||||
|  | 		> | ||||||
|  | 			<h2 class="section-label">{{ section.label }}</h2> | ||||||
|  | 
 | ||||||
|  | 			<ul class="networks nolist"> | ||||||
|  | 				<li | ||||||
|  | 					v-for="network in section.networks" | ||||||
|  | 					:key="`network-${network.id}`" | ||||||
|  | 					:title="network.name" | ||||||
|  | 				> | ||||||
|  | 					<a | ||||||
|  | 						:href="`/${network.type}/${network.slug}`" | ||||||
|  | 						class="network" | ||||||
|  | 					> | ||||||
|  | 						<img | ||||||
|  | 							v-if="network.hasLogo" | ||||||
|  | 							:src="`/logos/${network.slug}/network.png`" | ||||||
|  | 							:alt="network.name" | ||||||
|  | 							class="logo" | ||||||
|  | 						> | ||||||
|  | 
 | ||||||
|  | 						<span v-else>{{ network.name }}</span> | ||||||
|  | 					</a> | ||||||
|  | 				</li> | ||||||
|  | 			</ul> | ||||||
|  | 		</div> | ||||||
|  | 	</div> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <script setup> | ||||||
|  | import { inject } from 'vue'; | ||||||
|  | 
 | ||||||
|  | const pageContext = inject('pageContext'); | ||||||
|  | 
 | ||||||
|  | const { pageProps } = pageContext; | ||||||
|  | const { networks } = pageProps; | ||||||
|  | 
 | ||||||
|  | const networksBySlug = Object.fromEntries(networks.map((network) => [network.slug, network])); | ||||||
|  | 
 | ||||||
|  | const popularNetworks = [ | ||||||
|  | 	'21sextury', | ||||||
|  | 	'adulttime', | ||||||
|  | 	'amateurallure', | ||||||
|  | 	'analvids', | ||||||
|  | 	'bamvisions', | ||||||
|  | 	'bang', | ||||||
|  | 	'bangbros', | ||||||
|  | 	'blowpass', | ||||||
|  | 	'brazzers', | ||||||
|  | 	'burningangel', | ||||||
|  | 	'digitalplayground', | ||||||
|  | 	'dogfartnetwork', | ||||||
|  | 	'dorcel', | ||||||
|  | 	'elegantangel', | ||||||
|  | 	'evilangel', | ||||||
|  | 	'fakehub', | ||||||
|  | 	'hookuphotshot', | ||||||
|  | 	'hussiepass', | ||||||
|  | 	'julesjordan', | ||||||
|  | 	'kink', | ||||||
|  | 	'mofos', | ||||||
|  | 	'naughtyamerica', | ||||||
|  | 	'newsensations', | ||||||
|  | 	'pervcity', | ||||||
|  | 	'pornpros', | ||||||
|  | 	'private', | ||||||
|  | 	'realitykings', | ||||||
|  | 	'teamskeet', | ||||||
|  | 	'vixen', | ||||||
|  | 	'xempire', | ||||||
|  | ].map((slug) => networksBySlug[slug]).filter(Boolean); | ||||||
|  | 
 | ||||||
|  | const sections = [ | ||||||
|  | 	{ | ||||||
|  | 		label: 'Popular', | ||||||
|  | 		networks: popularNetworks, | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		label: 'All network', | ||||||
|  | 		networks, | ||||||
|  | 	}, | ||||||
|  | ]; | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <style scoped> | ||||||
|  | .networks { | ||||||
|  | 	display: grid; | ||||||
|  | 	grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr)); | ||||||
|  | 	gap: .5rem; | ||||||
|  | 	padding: .5rem; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .section-label { | ||||||
|  | 	padding: 0 1rem; | ||||||
|  | 	margin-bottom: .5rem; | ||||||
|  | 	color: var(--shadow); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .network { | ||||||
|  | 	display: flex; | ||||||
|  | 	justify-content: center; | ||||||
|  | 	align-items: center; | ||||||
|  | 	aspect-ratio: 4/1; | ||||||
|  | 	padding: 1rem; | ||||||
|  | 	border-radius: .5rem; | ||||||
|  | 	background: var(--grey-dark-40); | ||||||
|  | 	color: var(--text-light); | ||||||
|  | 	font-size: 1.25rem; | ||||||
|  | 	font-weight: bold; | ||||||
|  | 
 | ||||||
|  | 	&:hover { | ||||||
|  | 		box-shadow: 0 0 3px var(--shadow); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .logo { | ||||||
|  | 	height: 100%; | ||||||
|  | 	width: 100%; | ||||||
|  | 	object-fit: contain; | ||||||
|  | } | ||||||
|  | </style> | ||||||
|  | @ -0,0 +1,14 @@ | ||||||
|  | import { fetchEntities } from '#/src/entities.js'; | ||||||
|  | 
 | ||||||
|  | export async function onBeforeRender(_pageContext) { | ||||||
|  | 	const networks = await fetchEntities({ type: 'primary' }); | ||||||
|  | 
 | ||||||
|  | 	return { | ||||||
|  | 		pageContext: { | ||||||
|  | 			title: 'Channels', | ||||||
|  | 			pageProps: { | ||||||
|  | 				networks, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 	}; | ||||||
|  | } | ||||||
|  | @ -0,0 +1 @@ | ||||||
|  | export default '/channels'; | ||||||
|  | @ -0,0 +1,246 @@ | ||||||
|  | <template> | ||||||
|  | 	<div class="page"> | ||||||
|  | 		<div class="header"> | ||||||
|  | 			<a | ||||||
|  | 				:href="entity.url" | ||||||
|  | 				target="_blank" | ||||||
|  | 				rel="noopener" | ||||||
|  | 				class="link link-child" | ||||||
|  | 			> | ||||||
|  | 				<template v-if="entity.hasLogo"> | ||||||
|  | 					<img | ||||||
|  | 						v-if="entity.type === 'network'" | ||||||
|  | 						class="logo logo-child" | ||||||
|  | 						:src="`/logos/${entity.slug}/thumbs/network.png`" | ||||||
|  | 					> | ||||||
|  | 
 | ||||||
|  | 					<img | ||||||
|  | 						v-else-if="entity.parent && !entity.independent" | ||||||
|  | 						class="logo logo-child" | ||||||
|  | 						:src="`/logos/${entity.parent.slug}/thumbs/${entity.slug}.png`" | ||||||
|  | 					> | ||||||
|  | 
 | ||||||
|  | 					<img | ||||||
|  | 						v-else | ||||||
|  | 						class="logo logo-child" | ||||||
|  | 						:src="`/logos/${entity.slug}/thumbs/${entity.slug}.png`" | ||||||
|  | 					> | ||||||
|  | 				</template> | ||||||
|  | 
 | ||||||
|  | 				<h2 | ||||||
|  | 					v-else | ||||||
|  | 					class="name" | ||||||
|  | 				>{{ entity.name }}</h2> | ||||||
|  | 			</a> | ||||||
|  | 
 | ||||||
|  | 			<a | ||||||
|  | 				v-if="entity.parent" | ||||||
|  | 				:href="`/${entity.parent.type}/${entity.parent.slug}`" | ||||||
|  | 				class="link link-parent" | ||||||
|  | 			> | ||||||
|  | 				<img | ||||||
|  | 					v-if="entity.parent.hasLogo" | ||||||
|  | 					class="logo logo-parent" | ||||||
|  | 					:src="`/logos/${entity.parent.slug}/thumbs/network.png`" | ||||||
|  | 				> | ||||||
|  | 
 | ||||||
|  | 				<img | ||||||
|  | 					v-if="entity.parent.hasLogo" | ||||||
|  | 					class="favicon" | ||||||
|  | 					:src="`/logos/${entity.parent.slug}/favicon.png`" | ||||||
|  | 				> | ||||||
|  | 
 | ||||||
|  | 				<h3 | ||||||
|  | 					v-else | ||||||
|  | 					class="name parent-name" | ||||||
|  | 				>{{ entity.parent.name }}</h3> | ||||||
|  | 			</a> | ||||||
|  | 		</div> | ||||||
|  | 
 | ||||||
|  | 		<div class="content"> | ||||||
|  | 			<div class="children-container"> | ||||||
|  | 				<ul | ||||||
|  | 					v-if="entity.children.length > 0" | ||||||
|  | 					ref="children" | ||||||
|  | 					class="children nolist" | ||||||
|  | 					:class="{ expanded }" | ||||||
|  | 				> | ||||||
|  | 					<li | ||||||
|  | 						v-for="channel in entity.children" | ||||||
|  | 						:key="`channel-${channel.id}`" | ||||||
|  | 						:title="channel.name" | ||||||
|  | 					> | ||||||
|  | 						<EntityTile :entity="channel" /> | ||||||
|  | 					</li> | ||||||
|  | 				</ul> | ||||||
|  | 
 | ||||||
|  | 				<div class="expand-container"> | ||||||
|  | 					<button | ||||||
|  | 						v-show="scrollable && !expanded" | ||||||
|  | 						class="expand" | ||||||
|  | 						@click="expanded = !expanded" | ||||||
|  | 					> | ||||||
|  | 						<span class="expand-text">Expand channels</span> | ||||||
|  | 						<Icon icon="arrow-down3" /> | ||||||
|  | 					</button> | ||||||
|  | 
 | ||||||
|  | 					<button | ||||||
|  | 						v-show="expanded" | ||||||
|  | 						class="expand" | ||||||
|  | 						@click="expanded = !expanded" | ||||||
|  | 					> | ||||||
|  | 						<span class="expand-text">Collapse channels</span> | ||||||
|  | 						<Icon icon="arrow-up3" /> | ||||||
|  | 					</button> | ||||||
|  | 				</div> | ||||||
|  | 			</div> | ||||||
|  | 
 | ||||||
|  | 			<Scenes /> | ||||||
|  | 		</div> | ||||||
|  | 	</div> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <script setup> | ||||||
|  | import { ref, computed, inject } from 'vue'; | ||||||
|  | 
 | ||||||
|  | import EntityTile from '#/components/entities/tile.vue'; | ||||||
|  | import Scenes from '#/components/scenes/scenes.vue'; | ||||||
|  | 
 | ||||||
|  | const { pageProps } = inject('pageContext'); | ||||||
|  | const { entity } = pageProps; | ||||||
|  | 
 | ||||||
|  | const children = ref(null); | ||||||
|  | const expanded = ref(false); | ||||||
|  | 
 | ||||||
|  | const scrollable = computed(() => children.value?.scrollWidth > children.value?.clientWidth); | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <style scoped> | ||||||
|  | .page { | ||||||
|  | 	display: flex; | ||||||
|  | 	flex-direction: column; | ||||||
|  | 	overflow-y: hidden; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .content { | ||||||
|  | 	display: flex; | ||||||
|  | 	flex-direction: column; | ||||||
|  | 	overflow-y: auto; | ||||||
|  | 	flex-grow: 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .header { | ||||||
|  | 	display: flex; | ||||||
|  | 	justify-content: space-between; | ||||||
|  | 	align-items: stretch; | ||||||
|  | 	color: var(--text-light); | ||||||
|  | 	background: var(--grey-dark-50); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .logo { | ||||||
|  | 	height: 2.5rem; | ||||||
|  | 	padding: .75rem 1rem; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .link-parent { | ||||||
|  | 	display: flex; | ||||||
|  | 	align-items: center; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .favicon { | ||||||
|  | 	display: none; | ||||||
|  | 	padding: .75rem 1rem; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .children-container { | ||||||
|  | 	position: relative; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .children { | ||||||
|  | 	background: var(--grey-dark-50); | ||||||
|  | 	display: flex; | ||||||
|  | 	/* | ||||||
|  | 	display: grid; | ||||||
|  | 	grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr)); | ||||||
|  | 	*/ | ||||||
|  | 	gap: .5rem; | ||||||
|  | 	padding: .5rem; | ||||||
|  | 	overflow-x: auto; | ||||||
|  | 	flex-shrink: 0; | ||||||
|  | 
 | ||||||
|  | 	&.expanded { | ||||||
|  | 		display: grid; | ||||||
|  | 		grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr)); | ||||||
|  | 		overflow-x: hidden; | ||||||
|  | 
 | ||||||
|  | 		.entity { | ||||||
|  | 			width: 100%; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .children::-webkit-scrollbar { | ||||||
|  | 	display: none; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .expand-container { | ||||||
|  | 	width: 100%; | ||||||
|  | 	display: flex; | ||||||
|  | 	justify-content: center; | ||||||
|  | 	position: absolute; | ||||||
|  | 	left: 0; | ||||||
|  | 	bottom: -.75rem; | ||||||
|  | 	z-index: 10; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .expand { | ||||||
|  | 	display: flex; | ||||||
|  | 	align-items: center; | ||||||
|  | 	padding: .5rem; | ||||||
|  | 	border: none; | ||||||
|  | 	background: var(--grey-dark-40); | ||||||
|  | 	color: var(--highlight-strong-30); | ||||||
|  | 	font-size: .9rem; | ||||||
|  | 	font-weight: bold; | ||||||
|  | 	border-radius: .25rem; | ||||||
|  | 	box-shadow: 0 0 3px var(--shadow); | ||||||
|  | 
 | ||||||
|  | 	.icon { | ||||||
|  | 		fill: var(--highlight-strong-30); | ||||||
|  | 		margin-left: 1rem; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	&:hover { | ||||||
|  | 		color: var(--text-light); | ||||||
|  | 		cursor: pointer; | ||||||
|  | 
 | ||||||
|  | 		.icon { | ||||||
|  | 			fill: var(--text-light); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @media(--small-10) { | ||||||
|  | 	.logo-parent { | ||||||
|  | 		display: none; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	.favicon { | ||||||
|  | 		display: inline-block; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @media(--compact) { | ||||||
|  | 	.logo { | ||||||
|  | 		height: 1.75rem; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	.expand-text { | ||||||
|  | 		display: none; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	.expand .icon { | ||||||
|  | 		margin-right: 1rem; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | </style> | ||||||
|  | @ -0,0 +1,51 @@ | ||||||
|  | import { render } from 'vike/abort'; /* eslint-disable-line import/extensions */ | ||||||
|  | 
 | ||||||
|  | import { fetchEntitiesById } from '#/src/entities.js'; | ||||||
|  | import { fetchScenes } from '#/src/scenes.js'; | ||||||
|  | import { curateScenesQuery } from '#/src/web/scenes.js'; | ||||||
|  | import redis from '#/src//redis.js'; | ||||||
|  | 
 | ||||||
|  | export async function onBeforeRender(pageContext) { | ||||||
|  | 	const entityId = await redis.hGet('traxxx:entities:id_by_slug', pageContext.routeParams.entityType === 'network' ? `_${pageContext.routeParams.entitySlug}` : pageContext.routeParams.entitySlug); | ||||||
|  | 
 | ||||||
|  | 	if (!entityId) { | ||||||
|  | 		throw render(404, `Cannot find ${pageContext.routeParams.entityType} '${pageContext.routeParams.entitySlug}'.`); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	const [[entity], entityScenes] = await Promise.all([ | ||||||
|  | 		fetchEntitiesById([Number(entityId)], { includeChildren: true }), | ||||||
|  | 		fetchScenes(await curateScenesQuery({ | ||||||
|  | 			...pageContext.urlQuery, | ||||||
|  | 			scope: pageContext.routeParams.scope || 'latest', | ||||||
|  | 			entityId: Number(entityId), | ||||||
|  | 		}), { | ||||||
|  | 			page: Number(pageContext.routeParams.page) || 1, | ||||||
|  | 			limit: Number(pageContext.urlParsed.search.limit) || 30, | ||||||
|  | 			aggregate: true, | ||||||
|  | 		}), | ||||||
|  | 	]); | ||||||
|  | 
 | ||||||
|  | 	const { | ||||||
|  | 		scenes, | ||||||
|  | 		aggActors, | ||||||
|  | 		aggTags, | ||||||
|  | 		aggChannels, | ||||||
|  | 		total, | ||||||
|  | 		limit, | ||||||
|  | 	} = entityScenes; | ||||||
|  | 
 | ||||||
|  | 	return { | ||||||
|  | 		pageContext: { | ||||||
|  | 			title: entity.name, | ||||||
|  | 			pageProps: { | ||||||
|  | 				entity, | ||||||
|  | 				scenes, | ||||||
|  | 				aggActors, | ||||||
|  | 				aggTags, | ||||||
|  | 				aggChannels, | ||||||
|  | 				total, | ||||||
|  | 				limit, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 	}; | ||||||
|  | } | ||||||
|  | @ -0,0 +1,23 @@ | ||||||
|  | import { match } from 'path-to-regexp'; | ||||||
|  | // import { resolveRoute } from 'vike/routing'; // eslint-disable-line import/extensions
 | ||||||
|  | 
 | ||||||
|  | const path = '/:entityType(channel|network)/:entitySlug/:scope?/:page?'; | ||||||
|  | const urlMatch = match(path, { decode: decodeURIComponent }); | ||||||
|  | 
 | ||||||
|  | export default (pageContext) => { | ||||||
|  | 	const matched = urlMatch(pageContext.urlPathname); | ||||||
|  | 
 | ||||||
|  | 	if (matched) { | ||||||
|  | 		return { | ||||||
|  | 			routeParams: { | ||||||
|  | 				entityType: matched.params.entityType, | ||||||
|  | 				entitySlug: matched.params.entitySlug, | ||||||
|  | 				scope: matched.params.scope || 'latest', | ||||||
|  | 				page: matched.params.page || '1', | ||||||
|  | 				path, | ||||||
|  | 			}, | ||||||
|  | 		}; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return false; | ||||||
|  | }; | ||||||
|  | @ -4,6 +4,7 @@ | ||||||
| 			:scenes="scenes" | 			:scenes="scenes" | ||||||
| 			:show-filters="false" | 			:show-filters="false" | ||||||
| 			:show-meta="false" | 			:show-meta="false" | ||||||
|  | 			:show-scope-tabs="true" | ||||||
| 		/> | 		/> | ||||||
| 	</div> | 	</div> | ||||||
| </template> | </template> | ||||||
|  |  | ||||||
|  | @ -1,12 +1,17 @@ | ||||||
| // https://vike.dev/onRenderClient
 | // https://vike.dev/onRenderClient
 | ||||||
| import { createApp } from './app'; | import { createApp } from './app.js'; | ||||||
| 
 | 
 | ||||||
| // This onRenderClient() hook only supports SSR, see https://vike.dev/render-modes for how to modify onRenderClient()
 | // This onRenderClient() hook only supports SSR, see https://vike.dev/render-modes for how to modify onRenderClient()
 | ||||||
| // to support SPA
 | // to support SPA
 | ||||||
| async function onRenderClient(pageContext) { | async function onRenderClient(pageContext) { | ||||||
| 	const { Page, pageProps } = pageContext; | 	const { Page, pageProps } = pageContext; | ||||||
| 	if (!Page) throw new Error('Client-side render() hook expects pageContext.Page to be defined'); | 
 | ||||||
|  | 	if (!Page) { | ||||||
|  | 		throw new Error('Client-side render() hook expects pageContext.Page to be defined'); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	const app = createApp(Page, pageProps, pageContext); | 	const app = createApp(Page, pageProps, pageContext); | ||||||
|  | 
 | ||||||
| 	app.mount('#app'); | 	app.mount('#app'); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,4 +1,5 @@ | ||||||
| import { createSSRApp, h } from 'vue'; | import { createSSRApp, h } from 'vue'; | ||||||
|  | import VueVirtualScroller from 'vue-virtual-scroller'; | ||||||
| 
 | 
 | ||||||
| import { setPageContext } from './usePageContext.js'; | import { setPageContext } from './usePageContext.js'; | ||||||
| 
 | 
 | ||||||
|  | @ -30,6 +31,8 @@ function createApp(Page, pageProps, pageContext) { | ||||||
| 
 | 
 | ||||||
| 	app.provide('pageContext', pageContext); | 	app.provide('pageContext', pageContext); | ||||||
| 
 | 
 | ||||||
|  | 	app.use(VueVirtualScroller); | ||||||
|  | 
 | ||||||
| 	app.component('Link', Link); | 	app.component('Link', Link); | ||||||
| 	app.component('Icon', Icon); | 	app.component('Icon', Icon); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -32,6 +32,20 @@ onMounted(() => { | ||||||
| }); | }); | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
|  | <style> | ||||||
|  | .scroller { | ||||||
|  | 	height: 30rem; | ||||||
|  | 	overflow-y: auto; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .item { | ||||||
|  | 	height: 32px; | ||||||
|  | 	padding: 0 12px; | ||||||
|  | 	display: flex; | ||||||
|  | 	align-items: center; | ||||||
|  | } | ||||||
|  | </style> | ||||||
|  | 
 | ||||||
| <style scoped> | <style scoped> | ||||||
| .container { | .container { | ||||||
| 	height: 100%; | 	height: 100%; | ||||||
|  |  | ||||||
|  | @ -35,13 +35,13 @@ export function curateActor(actor, context = {}) { | ||||||
| 		tattoos: actor.tattoos, | 		tattoos: actor.tattoos, | ||||||
| 		hasPiercings: actor.has_piercings, | 		hasPiercings: actor.has_piercings, | ||||||
| 		piercings: actor.piercings, | 		piercings: actor.piercings, | ||||||
| 		origin: { | 		origin: actor.birth_country_alpha2 && { | ||||||
| 			country: actor.birth_country_alpha2 && { | 			country: actor.birth_country_alpha2 && { | ||||||
| 				alpha2: actor.birth_country_alpha2, | 				alpha2: actor.birth_country_alpha2, | ||||||
| 				name: actor.birth_country_name, | 				name: actor.birth_country_name, | ||||||
| 			}, | 			}, | ||||||
| 		}, | 		}, | ||||||
| 		residence: { | 		residence: actor.residence_country_alpha2 && { | ||||||
| 			country: actor.residence_country_alpha2 && { | 			country: actor.residence_country_alpha2 && { | ||||||
| 				alpha2: actor.residence_country_alpha2, | 				alpha2: actor.residence_country_alpha2, | ||||||
| 				name: actor.residence_country_name, | 				name: actor.residence_country_name, | ||||||
|  |  | ||||||
|  | @ -14,15 +14,39 @@ function curateEntity(entity, context) { | ||||||
| 		name: entity.name, | 		name: entity.name, | ||||||
| 		slug: entity.slug, | 		slug: entity.slug, | ||||||
| 		type: entity.type, | 		type: entity.type, | ||||||
|  | 		url: entity.url, | ||||||
| 		isIndependent: entity.independent, | 		isIndependent: entity.independent, | ||||||
| 		hasLogo: entity.has_logo, | 		hasLogo: entity.has_logo, | ||||||
| 		parent: curateEntity(entity.parent, context), | 		parent: curateEntity(entity.parent, context), | ||||||
|  | 		children: context?.children?.filter((child) => child.parent_id === entity.id).map((child) => curateEntity({ ...child, parent: entity }, { parent: entity })) || [], | ||||||
| 		...context?.append?.[entity.id], | 		...context?.append?.[entity.id], | ||||||
| 	}; | 	}; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | export async function fetchEntities(options) { | ||||||
|  | 	const entities = await knex('entities') | ||||||
|  | 		.modify((builder) => { | ||||||
|  | 			if (options.type === 'primary') { | ||||||
|  | 				builder | ||||||
|  | 					.where('type', 'network') | ||||||
|  | 					.orWhere('independent', true) | ||||||
|  | 					.orWhereNull('parent_id'); | ||||||
|  | 
 | ||||||
|  | 				return; | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			if (options.type) { | ||||||
|  | 				builder.where('type', options.type); | ||||||
|  | 			} | ||||||
|  | 		}) | ||||||
|  | 		.orderBy(...(options.order || ['name', 'asc'])) | ||||||
|  | 		.limit(options.limit || 1000); | ||||||
|  | 
 | ||||||
|  | 	return entities.map((entityEntry) => curateEntity(entityEntry)); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| export async function fetchEntitiesById(entityIds, options = {}) { | export async function fetchEntitiesById(entityIds, options = {}) { | ||||||
| 	const [entities] = await Promise.all([ | 	const [entities, children] = await Promise.all([ | ||||||
| 		knex('entities') | 		knex('entities') | ||||||
| 			.select('entities.*', knex.raw('row_to_json(parents) as parent')) | 			.select('entities.*', knex.raw('row_to_json(parents) as parent')) | ||||||
| 			.whereIn('entities.id', entityIds) | 			.whereIn('entities.id', entityIds) | ||||||
|  | @ -33,10 +57,16 @@ export async function fetchEntitiesById(entityIds, options = {}) { | ||||||
| 				} | 				} | ||||||
| 			}) | 			}) | ||||||
| 			.groupBy('entities.id', 'parents.id'), | 			.groupBy('entities.id', 'parents.id'), | ||||||
|  | 		options.includeChildren ? knex('entities') | ||||||
|  | 			.whereIn('entities.parent_id', entityIds) | ||||||
|  | 			.orderBy('slug') : [], | ||||||
| 	]); | 	]); | ||||||
| 
 | 
 | ||||||
| 	if (options.order) { | 	if (options.order) { | ||||||
| 		return entities.map((entityEntry) => curateEntity(entityEntry, { append: options.append })); | 		return entities.map((entityEntry) => curateEntity(entityEntry, { | ||||||
|  | 			append: options.append, | ||||||
|  | 			children, | ||||||
|  | 		})); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	const curatedEntities = entityIds.map((entityId) => { | 	const curatedEntities = entityIds.map((entityId) => { | ||||||
|  | @ -47,7 +77,10 @@ export async function fetchEntitiesById(entityIds, options = {}) { | ||||||
| 			return null; | 			return null; | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		return curateEntity(entity, { append: options.append }); | 		return curateEntity(entity, { | ||||||
|  | 			append: options.append, | ||||||
|  | 			children, | ||||||
|  | 		}); | ||||||
| 	}).filter(Boolean); | 	}).filter(Boolean); | ||||||
| 
 | 
 | ||||||
| 	return curatedEntities; | 	return curatedEntities; | ||||||
|  |  | ||||||
|  | @ -183,6 +183,10 @@ function buildQuery(filters = {}) { | ||||||
| 		sort = [{ created_at: 'desc' }, { effective_date: 'asc' }]; | 		sort = [{ created_at: 'desc' }, { effective_date: 'asc' }]; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	if (filters.scope === 'likes') { | ||||||
|  | 		sort = [{ stashed: 'desc' }, { effective_date: 'desc' }]; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	if (filters.tagIds) { | 	if (filters.tagIds) { | ||||||
| 		filters.tagIds.forEach((tagId) => { | 		filters.tagIds.forEach((tagId) => { | ||||||
| 			query.bool.must.push({ equals: { 'any(tag_ids)': tagId } }); | 			query.bool.must.push({ equals: { 'any(tag_ids)': tagId } }); | ||||||
|  | @ -272,6 +276,7 @@ export async function fetchScenes(filters, rawOptions) { | ||||||
| 		offset: (options.page - 1) * options.limit, | 		offset: (options.page - 1) * options.limit, | ||||||
| 		sort, | 		sort, | ||||||
| 		aggs: buildAggregates(options), | 		aggs: buildAggregates(options), | ||||||
|  | 		max_matches: 5000, | ||||||
| 	}); | 	}); | ||||||
| 
 | 
 | ||||||
| 	const actorCounts = options.aggregateActors && countAggregations(result.aggregations?.actorIds?.buckets); | 	const actorCounts = options.aggregateActors && countAggregations(result.aggregations?.actorIds?.buckets); | ||||||
|  |  | ||||||
|  | @ -45,7 +45,6 @@ function getFilename(media, type, options) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export function getMediaPath(media, type, options) { | export function getMediaPath(media, type, options) { | ||||||
| 	console.log('media', media); |  | ||||||
| 	if (!media) { | 	if (!media) { | ||||||
| 		return null; | 		return null; | ||||||
| 	} | 	} | ||||||
|  | @ -53,7 +52,5 @@ export function getMediaPath(media, type, options) { | ||||||
| 	const path = getBasePath(media, type, options); | 	const path = getBasePath(media, type, options); | ||||||
| 	const filename = getFilename(media, type, options); | 	const filename = getFilename(media, type, options); | ||||||
| 
 | 
 | ||||||
| 	console.log(path, filename); |  | ||||||
| 
 |  | ||||||
| 	return `${path}/${filename}`; | 	return `${path}/${filename}`; | ||||||
| } | } | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue