Compare commits

..

4 Commits

16 changed files with 174 additions and 26 deletions

View File

@ -104,6 +104,7 @@ const favorited = ref(props.actor.stashes.some((actorStash) => actorStash.id ===
border-radius: .25rem;
box-shadow: 0 0 3px var(--shadow-weak-30);
overflow: hidden;
background: var(--background);
&:hover {
box-shadow: 0 0 3px var(--shadow-weak-20);

View File

@ -0,0 +1,51 @@
<template>
<div class="campaign">
<a
:href="campaign.url"
target="_blank"
class="campaign-link"
>
<img
v-if="campaign.banner"
:src="getBanner(campaign)"
class="campaign-banner"
>
</a>
</div>
</template>
<script setup>
const props = defineProps({
campaign: {
type: Object,
default: null,
},
});
console.log(props.campaign?.banner);
function getBanner(campaign) {
if (campaign.banner.entity.type === 'network' || !campaign.banner.entity.parent) {
return `/banners/${campaign.banner.entity.slug}/${campaign.banner.id}.${campaign.banner.type || 'jpg'}`;
}
if (campaign.banner.entity.type === 'channel' && campaign.banner.entity.parent?.type === 'network') {
return `/banners/${campaign.banner.entity.parent.slug}/${campaign.banner.entity.slug}/${campaign.banner.id}.${campaign.banner.type || 'jpg'}`;
}
return null;
}
</script>
<style scoped>
.campaign {
height: 100%;
display: flex;
justify-content: center;
}
.campaign-banner {
max-height: 100%;
max-width: 100%;
}
</style>

View File

@ -5,7 +5,7 @@
>
<img
v-if="entity.hasLogo"
:src="entity.type === 'network' || entity.isIndependent ? `/logos/${entity.slug}/network.png` : `/logos/${entity.parent?.slug}/${entity.slug}.png`"
:src="entity.type === 'network' || entity.isIndependent ? `/logos/${entity.slug}/thumbs/network.png` : `/logos/${entity.parent?.slug}/thumbs/${entity.slug}.png`"
:alt="entity.name"
class="logo"
>
@ -33,7 +33,7 @@ defineProps({
box-sizing: border-box;
padding: 1rem;
border-radius: .5rem;
background: var(--shadow-strong-40);
background: var(--shadow-strong-30);
color: var(--text-light);
font-size: 1.25rem;
font-weight: bold;

View File

@ -438,7 +438,7 @@ function blurSearch(event) {
width: 1.25rem;
padding-left: 1rem;
height: 100%;
fill: var(--glass-strong-20);
fill: var(--glass);
}
.notifs-trigger {

View File

@ -105,12 +105,21 @@
<ul
class="scenes nolist"
>
<li
v-for="scene in scenes"
:key="`scene-${scene.id}`"
>
<SceneTile :scene="scene" />
</li>
<template v-for="item in campaignScenes">
<li
v-if="item === 'campaign' && campaign"
:key="`campaign-${item.id}`"
>
<Campaign :campaign="campaign" />
</li>
<li
v-else
:key="`scene-${item.id}`"
>
<SceneTile :scene="item" />
</li>
</template>
</ul>
<Pagination
@ -127,7 +136,7 @@
</template>
<script setup>
import { ref, inject } from 'vue';
import { ref, computed, inject } from 'vue';
import { parse } from 'path-to-regexp';
import navigate from '#/src/navigate.js';
@ -140,6 +149,7 @@ import ActorsFilter from '#/components/filters/actors.vue';
import TagsFilter from '#/components/filters/tags.vue';
import ChannelsFilter from '#/components/filters/channels.vue';
import SceneTile from '#/components/scenes/tile.vue';
import Campaign from '#/components/campaigns/campaign.vue';
import Pagination from '#/components/pagination/pagination.vue';
import Ellipsis from '#/components/loading/ellipsis.vue';
@ -196,6 +206,11 @@ const filters = ref({
actors: queryActors,
});
const campaign = pageProps.campaigns?.scenes;
const campaignIndex = pageProps.campaigns?.index;
const campaignScenes = computed(() => scenes.value.flatMap((scene, index) => (index === campaignIndex ? ['campaign', scene] : scene)));
function getPath(targetScope, preserveQuery) {
const path = parse(routeParams.path).map((segment) => {
if (segment.name === 'scope') {
@ -282,6 +297,7 @@ function updateFilter(prop, value, reload = true) {
display: flex;
background: var(--background-base-10);
position: relative;
flex-grow: 1;
}
.scenes-header {

4
package-lock.json generated
View File

@ -1,11 +1,11 @@
{
"name": "traxxx-web",
"version": "0.24.0",
"version": "0.25.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"version": "0.24.0",
"version": "0.25.0",
"dependencies": {
"@brillout/json-serializer": "^0.5.8",
"@dicebear/collection": "^7.0.5",

View File

@ -77,5 +77,5 @@
"postcss-custom-media": "^10.0.2",
"postcss-nesting": "^12.0.2"
},
"version": "0.24.0"
"version": "0.25.0"
}

View File

@ -115,7 +115,7 @@ async function login() {
}
onMounted(() => {
userInput.value.focus();
userInput.value?.focus();
});
</script>

View File

@ -87,6 +87,7 @@ const popularNetworks = [
'hussiepass',
'julesjordan',
'kink',
'mikeadriano',
'mofos',
'naughtyamerica',
'newsensations',

View File

@ -119,13 +119,11 @@ const scrollable = computed(() => children.value?.scrollWidth > children.value?.
.page {
display: flex;
flex-direction: column;
overflow-y: hidden;
}
.content {
display: flex;
flex-direction: column;
overflow-y: auto;
flex-grow: 1;
}
@ -133,12 +131,20 @@ const scrollable = computed(() => children.value?.scrollWidth > children.value?.
display: flex;
justify-content: space-between;
align-items: stretch;
position: sticky;
top: 0;
z-index: 1;
color: var(--text-light);
background: var(--grey-dark-50);
.link {
display: flex;
align-items: center;
}
}
.logo {
height: 2.5rem;
height: 2rem;
max-width: 15rem;
padding: .75rem 1rem;
object-fit: contain;
@ -152,6 +158,7 @@ const scrollable = computed(() => children.value?.scrollWidth > children.value?.
.favicon {
display: none;
padding: .75rem 1rem;
height: 1.5rem;
}
.children-container {
@ -222,7 +229,11 @@ const scrollable = computed(() => children.value?.scrollWidth > children.value?.
}
}
@media(--small-10) {
@media(--compact) {
.logo {
height: 1.25rem;
}
.logo-parent {
display: none;
}
@ -230,12 +241,6 @@ const scrollable = computed(() => children.value?.scrollWidth > children.value?.
.favicon {
display: inline-block;
}
}
@media(--compact) {
.logo {
height: 1.75rem;
}
.expand-text {
display: none;
@ -245,4 +250,16 @@ const scrollable = computed(() => children.value?.scrollWidth > children.value?.
margin-right: 1rem;
}
}
@media(--small-20) {
.logo {
height: 1rem;
padding: .5rem 1rem;
}
.favicon {
height: 1rem;
padding: .5rem 1rem;
}
}
</style>

View File

@ -709,6 +709,7 @@ function copySummary() {
.detail {
display: flex;
align-items: stretch;
flex-direction: row;
}
.input {

View File

@ -1,5 +1,6 @@
import { fetchScenes } from '#/src/scenes.js';
import { curateScenesQuery } from '#/src/web/scenes.js';
import { getRandomCampaign } from '#/src/campaigns.js';
export async function onBeforeRender(pageContext) {
const withQuery = Object.hasOwn(pageContext.urlParsed.search, 'q');
@ -18,10 +19,14 @@ export async function onBeforeRender(pageContext) {
tagFilter: pageContext.tagFilter,
}), {
page: Number(pageContext.routeParams.page) || 1,
limit: Number(pageContext.urlParsed.search.limit) || 30,
limit: Number(pageContext.urlParsed.search.limit) || 29,
aggregate: withQuery,
}, pageContext.user);
// const campaignIndex = Math.floor(Math.random() * (scenes.length - 5)) + 5;
const campaignIndex = Math.floor((Math.random() * (0.5 - 0.2) + 0.2) * scenes.length);
const sceneCampaign = await getRandomCampaign({ minRatio: 0.75, maxRatio: 1.25 });
return {
pageContext: {
title: pageContext.routeParams.scope,
@ -32,6 +37,10 @@ export async function onBeforeRender(pageContext) {
aggActors,
limit,
total,
campaigns: {
index: campaignIndex,
scenes: sceneCampaign,
},
},
},
};

View File

@ -47,6 +47,8 @@ export function curateActor(actor, context = {}) {
name: actor.birth_country_name,
alias: actor.birth_country_alias,
},
city: actor.birth_city,
state: actor.birth_state,
},
residence: actor.residence_country_alpha2 && {
country: actor.residence_country_alpha2 && {
@ -54,6 +56,8 @@ export function curateActor(actor, context = {}) {
name: actor.residence_country_name,
alias: actor.residence_country_alias,
},
city: actor.residence_city,
state: actor.residence_state,
},
avatar: curateMedia(actor.avatar),
profiles: context.profiles?.map((profile) => ({

47
src/campaigns.js Normal file
View File

@ -0,0 +1,47 @@
import { knexOwner as knex } from './knex.js';
import { curateEntity } from './entities.js';
function curateCampaign(campaign) {
if (!campaign) {
return null;
}
return {
id: campaign.id,
url: campaign.url,
banner: campaign.banner && {
id: campaign.banner.id,
type: campaign.banner.type,
entity: campaign.banner_entity && curateEntity({ ...campaign.banner_entity, parent: campaign.banner_parent_entity }),
tags: campaign.banner_tags?.map((tag) => ({
id: tag.id,
slug: tag.slug,
name: tag.name,
})) || [],
},
affiliateId: campaign.affiliate_id,
};
}
export async function getRandomCampaign(options = {}) {
const campaign = await knex.raw(`
SELECT
campaigns.*, row_to_json(banners) as banner, row_to_json(entities) as banner_entity, row_to_json(parents) as banner_parent_entity, json_agg(tags) filter (where tags.id is not null) as banner_tags
FROM campaigns
LEFT JOIN banners ON banners.id = campaigns.banner_id
LEFT JOIN banners_tags ON banners_tags.banner_id = banners.id
LEFT JOIN tags ON tags.id = banners_tags.tag_id
LEFT JOIN entities ON entities.id = banners.entity_id
LEFT JOIN entities as parents ON parents.id = entities.parent_id
WHERE campaigns.banner_id IS NOT NULL
AND ratio >= :minRatio
AND ratio <= :maxRatio
GROUP BY campaigns.id, banners.id, entities.id, parents.id
ORDER BY RANDOM()
`, {
minRatio: options.minRatio || 0,
maxRatio: options.maxRatio || 1000,
});
return curateCampaign(campaign.rows[0]);
}

View File

@ -12,5 +12,6 @@ export function curateMedia(media) {
width: media.width,
height: media.height,
index: media.index,
credit: media.credit,
};
}

2
static

@ -1 +1 @@
Subproject commit 31759c4a58d789bf6cfa7c96cdae32af42bdf86c
Subproject commit 24d11363ef7632591ec6f49240079259f777ab9e