Fixed pagination. Added entity page channel tile expand.
This commit is contained in:
125
pages/entities/+Page.vue
Normal file
125
pages/entities/+Page.vue
Normal file
@@ -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>
|
||||
14
pages/entities/+onBeforeRender.js
Normal file
14
pages/entities/+onBeforeRender.js
Normal file
@@ -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,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
1
pages/entities/+route.js
Normal file
1
pages/entities/+route.js
Normal file
@@ -0,0 +1 @@
|
||||
export default '/channels';
|
||||
246
pages/entities/@entitySlug/+Page.vue
Normal file
246
pages/entities/@entitySlug/+Page.vue
Normal file
@@ -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>
|
||||
51
pages/entities/@entitySlug/+onBeforeRender.js
Normal file
51
pages/entities/@entitySlug/+onBeforeRender.js
Normal file
@@ -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,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
23
pages/entities/@entitySlug/+route.js
Normal file
23
pages/entities/@entitySlug/+route.js
Normal file
@@ -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;
|
||||
};
|
||||
Reference in New Issue
Block a user