Added dedicated stash page. Using preview tiles for stashes on user page.

This commit is contained in:
DebaucheryLibrarian 2021-03-19 02:36:31 +01:00
parent cc27f202af
commit f0265c2f5d
13 changed files with 470 additions and 36 deletions

View File

@ -27,7 +27,7 @@
> >
<img <img
:src="getPath(item, 'thumbnail', { local })" :src="getPath(item, 'thumbnail', { local })"
:style="{ 'background-image': `url('${getPath(item, 'lazy', { local })}')` }" :style="{ 'background-image': getBgPath(item, 'lazy', { local }) }"
:width="item.thumbnailWidth" :width="item.thumbnailWidth"
:height="item.thumbnailHeight" :height="item.thumbnailHeight"
:title="item.title" :title="item.title"

View File

@ -10,7 +10,7 @@
<ul class="rules"> <ul class="rules">
<li class="rule">You are at least 18 years old, and legally permitted to view adult material in your jurisdiction.</li> <li class="rule">You are at least 18 years old, and legally permitted to view adult material in your jurisdiction.</li>
<li class="rule">You do not regard erotic, sexual and pornographic material as obscene or offensive.</li> <li class="rule">You do not regard erotic and frankly pornographic media as obscene or offensive.</li>
<li class="rule">You understand that most sexual scenarios depicted on this website are fictional, performed by professional actors for the purpose of entertainment, and not representative of real-life interactions.</li> <li class="rule">You understand that most sexual scenarios depicted on this website are fictional, performed by professional actors for the purpose of entertainment, and not representative of real-life interactions.</li>
</ul> </ul>

View File

@ -55,14 +55,14 @@
v-show="me && isStashed" v-show="me && isStashed"
icon="heart7" icon="heart7"
class="stash stashed noselect" class="stash stashed noselect"
@click="unstashRelease" @click="unstashScene"
/> />
<Icon <Icon
v-show="me && !isStashed" v-show="me && !isStashed"
icon="heart8" icon="heart8"
class="stash unstashed noselect" class="stash unstashed noselect"
@click="stashRelease" @click="stashScene"
/> />
</div> </div>
@ -251,8 +251,8 @@ async function fetchRelease(scroll = true) {
} }
} }
async function stashRelease() { async function stashScene() {
this.$store.dispatch(this.$route.name === 'movie' ? 'stashMovie' : 'stashRelease', { this.$store.dispatch(this.$route.name === 'movie' ? 'stashMovie' : 'stashScene', {
sceneId: this.release.id, sceneId: this.release.id,
movieId: this.release.id, movieId: this.release.id,
stashId: this.$store.getters.favorites.id, stashId: this.$store.getters.favorites.id,
@ -261,8 +261,8 @@ async function stashRelease() {
this.fetchRelease(false); this.fetchRelease(false);
} }
async function unstashRelease() { async function unstashScene() {
this.$store.dispatch(this.$route.name === 'movie' ? 'unstashMovie' : 'unstashRelease', { this.$store.dispatch(this.$route.name === 'movie' ? 'unstashMovie' : 'unstashScene', {
sceneId: this.release.id, sceneId: this.release.id,
movieId: this.release.id, movieId: this.release.id,
stashId: this.$store.getters.favorites.id, stashId: this.$store.getters.favorites.id,
@ -323,8 +323,8 @@ export default {
mounted: fetchRelease, mounted: fetchRelease,
methods: { methods: {
fetchRelease, fetchRelease,
stashRelease, stashScene,
unstashRelease, unstashScene,
}, },
}; };
</script> </script>

View File

@ -0,0 +1,115 @@
<template>
<div
v-if="stash"
class="stash content"
>
<div class="stash-header">
<h2 class="stash-name">{{ stash.name }}</h2>
<router-link
v-if="stash.user"
:to="{ name: 'user', params: { username: stash.user.username } }"
class="stash-username nolink"
><Icon icon="user3" />{{ stash.user.username }}</router-link>
</div>
<div class="content-inner">
<ul
v-if="stash.scenes?.length > 0"
class="stash-section stash-scenes nolist"
>
<li
v-for="item in stash.scenes"
:key="item.id"
><Scene :release="item.scene" /></li>
</ul>
<ul
v-if="stash.actors?.length > 0"
class="stash-section stash-actors nolist"
>
<li
v-for="item in stash.actors"
:key="item.id"
><Actor :actor="item.actor" /></li>
</ul>
</div>
</div>
</template>
<script>
import Actor from '../actors/tile.vue';
import Scene from '../releases/scene-tile.vue';
async function fetchStash() {
this.stash = await this.$store.dispatch('fetchStash', this.$route.params.stashId);
}
async function mounted() {
this.fetchStash();
}
export default {
components: {
Actor,
Scene,
},
data() {
return {
stash: null,
};
},
mounted,
methods: {
fetchStash,
},
};
</script>
<style lang="scss" scoped>
.stash-header {
display: flex;
justify-content: space-between;
align-items: center;
background: var(--profile);
color: var(--text-light);
}
.stash-name,
.stash-username {
display: inline-flex;
align-items: center;
padding: .5rem 1rem;
margin: 0;
font-weight: bold;
.icon {
fill: var(--text-light);
margin: -.1rem .5rem 0 0;
}
}
.stash-section {
padding: 1rem;
&:not(:last-child) {
border-bottom: solid 1px var(--shadow-hint);
}
}
.stash-actors,
.stash-scenes {
display: grid;
flex-grow: 1;
grid-gap: .5rem;
box-sizing: border-box;
}
.stash-actors {
grid-template-columns: repeat(auto-fill, minmax(10rem, 1fr));
}
.stash-scenes {
grid-template-columns: repeat(auto-fill, minmax(22rem, 1fr));
}
</style>

View File

@ -11,7 +11,7 @@
> >
<img <img
:src="getPath(photo, 'thumbnail', { local: true })" :src="getPath(photo, 'thumbnail', { local: true })"
:style="{ 'background-image': `url(${getPath(photo, 'lazy', { local: true })})` }" :style="{ 'background-image': getBgPath(photo, 'lazy', { local: true }) }"
:alt="photo.comment" :alt="photo.comment"
:width="photo.thumbnailWidth" :width="photo.thumbnailWidth"
:height="photo.thumbnailHeight" :height="photo.thumbnailHeight"

View File

@ -0,0 +1,87 @@
<template>
<router-link
:to="`/actor/${actor.id}/${actor.slug}`"
class="actor nolink"
>
<div class="avatar">
<img
v-if="actor.avatar"
:src="getPath(actor.avatar)"
class="avatar-image"
>
<Icon
v-else-if="actor.gender"
:icon="actor.gender"
class="avatar-fallback"
/>
</div>
<span class="name">{{ actor.name }}</span>
</router-link>
</template>
<script>
async function unstashActor(actorId, stashId) {
await this.$store.dispatch('unstashActor', { actorId, stashId });
this.$emit('unstash');
}
export default {
props: {
actor: {
type: Object,
default: null,
},
stash: {
type: Object,
default: null,
},
},
methods: {
unstashActor,
},
};
</script>
<style lang="scss" scoped>
.actor {
height: 2.5rem;
display: inline-flex;
align-items: center;
border: solid 1px var(--shadow-hint);
&:hover {
border: solid 1px var(--primary);
}
}
.avatar {
display: inline-flex;
align-items: center;
justify-content: center;
width: 2rem;
height: 100%;
background: var(--profile);
}
.avatar-image {
width: 100%;
height: 100%;
object-fit: cover;
object-position: 50% 0;
}
.avatar-fallback {
fill: var(--lighten-weak);
}
.name {
display: inline-flex;
align-items: center;
height: 100%;
padding: 0 .5rem;
font-weight: bold;
}
</style>

View File

@ -0,0 +1,108 @@
<template>
<a
:href="`/scene/${scene.id}/${scene.slug || ''}`"
target="_blank"
rel="noopener noreferrer"
class="scene nolink"
>
<img
:src="getPath(scene.poster, 'thumbnail')"
class="scene-poster"
>
<div class="scene-header">
<span class="scene-actors nolist">{{ scene.actors.map(actor => actor.name).join(', ') }}</span>
</div>
<div class="scene-footer">
<img
:src="`/img/logos/${scene.entity.parent.slug}/favicon_light.png`"
class="scene-favicon"
>
<span class="scene-title">{{ scene.title }}</span>
</div>
</a>
</template>
<script>
export default {
props: {
scene: {
type: Object,
default: null,
},
stash: {
type: Object,
default: null,
},
},
};
</script>
<style lang="scss" scoped>
.scene {
width: 14rem;
height: 100%;
position: relative;
font-size: 0;
}
.scene-poster {
width: 100%;
height: 100%;
object-fit: cover;
object-position: 50% 0;
}
.scene-header,
.scene-footer {
width: 100%;
height: 1.25rem;
display: flex;
align-items: center;
position: absolute;
left: 0;
background: var(--shadow-weak);
color: var(--text-light);
font-size: .7rem;
font-weight: bold;
overflow: hidden;
}
.scene-header {
top: 0;
}
.scene-footer {
bottom: 0;
}
.scene-title {
padding: .25rem .5rem;
text-shadow: 0 0 2px var(--shadow);
}
.scene-actors {
padding: 0 .5rem;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.scene-unstash {
fill: var(--lighten-strong);
padding: .25rem;
filter: drop-shadow(0 0 1px var(--shadow));
&:hover {
fill: var(--text-light);
}
}
.scene-favicon {
width: 1rem;
height: 1rem;
padding: .1rem 0 0 .25rem;
}
</style>

View File

@ -19,16 +19,28 @@
:key="stash.id" :key="stash.id"
class="stash" class="stash"
> >
<h4 class="stash-name">{{ stash.name }}</h4> <router-link
:to="{ name: 'stash', params: { stashId: stash.id, stashSlug: stash.slug } }"
class="stash-link stash-section"
>
<h4 class="stash-name">{{ stash.name }}</h4>
</router-link>
<ul <ul
v-if="stash.scenes?.length > 0" v-if="stash.scenes?.length > 0"
class="stash-section stash-scenes nolist" class="stash-section stash-scenes nolist"
> >
<li <li
v-for="item in stash.scenes" v-for="{ scene } in stash.scenes"
:key="item.id" :key="scene.id"
><Scene :release="item.scene" /></li> class="stash-scene"
>
<ScenePreview
:scene="scene"
:stash="stash"
@unstash="fetchUser"
/>
</li>
</ul> </ul>
<ul <ul
@ -36,9 +48,16 @@
class="stash-section stash-actors nolist" class="stash-section stash-actors nolist"
> >
<li <li
v-for="item in stash.actors" v-for="{ actor } in stash.actors"
:key="item.id" :key="actor.id"
><Actor :actor="item.actor" /></li> class="stash-actor"
>
<ActorPreview
:actor="actor"
:stash="stash"
@unstash="fetchUser"
/>
</li>
</ul> </ul>
</li> </li>
</ul> </ul>
@ -47,18 +66,22 @@
</template> </template>
<script> <script>
import Actor from '../actors/tile.vue'; import ActorPreview from './actor-preview.vue';
import Scene from '../releases/scene-tile.vue'; import ScenePreview from './scene-preview.vue';
async function mounted() { async function fetchUser() {
this.user = await this.$store.dispatch('fetchUser', this.$route.params.username); this.user = await this.$store.dispatch('fetchUser', this.$route.params.username);
this.pageTitle = this.user?.username; this.pageTitle = this.user?.username;
} }
async function mounted() {
await this.fetchUser();
}
export default { export default {
components: { components: {
Actor, ActorPreview,
Scene, ScenePreview,
}, },
data() { data() {
return { return {
@ -69,6 +92,9 @@ export default {
}; };
}, },
mounted, mounted,
methods: {
fetchUser,
},
}; };
</script> </script>
@ -89,25 +115,34 @@ export default {
margin: 0 0 1rem 0; margin: 0 0 1rem 0;
} }
.stashes {
display: grid;
grid-template-columns: 1fr 1fr;
}
.heading { .heading {
color: var(--primary); color: var(--primary);
} }
.stash { .stash {
width: 100%; min-width: 0;
background: var(--background); background: var(--background);
margin: 0 0 1rem 0; margin: 0 0 1rem 0;
box-shadow: 0 0 3px var(--shadow-weak); box-shadow: 0 0 3px var(--shadow-weak);
} }
.stash-link.stash-section {
display: block;
text-decoration: none;
}
.stash-name { .stash-name {
color: var(--shadow-strong); color: var(--shadow-strong);
padding: 1rem .5rem 0 .5rem;
margin: 0; margin: 0;
} }
.stash-section { .stash-section {
padding: 1rem .5rem; padding: .5rem;
&:not(:last-child) { &:not(:last-child) {
border-bottom: solid 1px var(--shadow-hint); border-bottom: solid 1px var(--shadow-hint);
@ -116,17 +151,27 @@ export default {
.stash-actors, .stash-actors,
.stash-scenes { .stash-scenes {
display: grid; display: flex;
flex-grow: 1; overflow-x: auto;
grid-gap: .5rem; scroll-behavior: smooth;
box-sizing: border-box; scrollbar-width: none;
}
.stash-actors { &::-webkit-scrollbar {
grid-template-columns: repeat(auto-fill, minmax(10rem, 1fr)); display: none;
}
} }
.stash-scenes { .stash-scenes {
grid-template-columns: repeat(auto-fill, minmax(22rem, 1fr)); height: 8rem;
grid-gap: .5rem;
}
.stash-actors {
grid-gap: 1rem;
}
.stash-actor,
.stash-scene {
flex-shrink: 0;
} }
</style> </style>

View File

@ -58,6 +58,10 @@ async function init() {
} }
function getPath(media, type, options) { function getPath(media, type, options) {
if (!media) {
return null;
}
const path = getBasePath(media, type, options); const path = getBasePath(media, type, options);
const filename = getFilename(media, type, options); const filename = getFilename(media, type, options);

View File

@ -12,6 +12,7 @@ import Actors from '../components/actors/actors.vue';
import Movies from '../components/releases/movies.vue'; import Movies from '../components/releases/movies.vue';
import Tag from '../components/tags/tag.vue'; import Tag from '../components/tags/tag.vue';
import Tags from '../components/tags/tags.vue'; import Tags from '../components/tags/tags.vue';
import Stash from '../components/stashes/stash.vue';
import Search from '../components/search/search.vue'; import Search from '../components/search/search.vue';
import Stats from '../components/stats/stats.vue'; import Stats from '../components/stats/stats.vue';
import NotFound from '../components/errors/404.vue'; import NotFound from '../components/errors/404.vue';
@ -201,6 +202,11 @@ const routes = [
component: Tags, component: Tags,
name: 'tags', name: 'tags',
}, },
{
path: '/stash/:stashId/:stashSlug?',
component: Stash,
name: 'stash',
},
{ {
path: '/search', path: '/search',
component: Search, component: Search,

View File

@ -1,6 +1,62 @@
import { post, del } from '../api'; import { graphql, post, del } from '../api';
import { releaseFields } from '../fragments';
import { curateStash } from '../curate';
function initStashesActions(_store, _router) { function initStashesActions(_store, _router) {
async function fetchStash(context, stashId) {
const { stash } = await graphql(`
query Stash(
$stashId: Int!
) {
stash(id: $stashId) {
id
name
slug
public
user {
id
username
}
actors: stashesActors {
comment
actor {
id
name
slug
gender
age
ageFromBirth
dateOfBirth
birthCity
birthState
birthCountry: countryByBirthCountryAlpha2 {
alpha2
name
alias
}
avatar: avatarMedia {
id
path
thumbnail
lazy
}
}
}
scenes: stashesScenes {
comment
scene {
${releaseFields}
}
}
}
}
`, {
stashId: Number(stashId),
});
return curateStash(stash);
}
async function stashActor(context, { actorId, stashId }) { async function stashActor(context, { actorId, stashId }) {
await post(`/stashes/${stashId}/actors`, { actorId }); await post(`/stashes/${stashId}/actors`, { actorId });
} }
@ -26,6 +82,7 @@ function initStashesActions(_store, _router) {
} }
return { return {
fetchStash,
stashActor, stashActor,
stashScene, stashScene,
stashMovie, stashMovie,

View File

@ -42,7 +42,7 @@ function initUsersActions(_store, _router) {
} }
} }
} }
scenes: stashesScenes { scenes: stashesScenes(first: 20) {
comment comment
scene { scene {
${releaseFields} ${releaseFields}

View File

@ -1087,6 +1087,10 @@ exports.up = knex => Promise.resolve()
table.unique(['stash_id', 'scene_id']); table.unique(['stash_id', 'scene_id']);
table.string('comment'); table.string('comment');
table.datetime('created_at')
.notNullable()
.defaultTo(knex.fn.now());
})) }))
.then(() => knex.schema.createTable('stashes_movies', (table) => { .then(() => knex.schema.createTable('stashes_movies', (table) => {
table.integer('stash_id') table.integer('stash_id')
@ -1104,6 +1108,10 @@ exports.up = knex => Promise.resolve()
table.unique(['stash_id', 'movie_id']); table.unique(['stash_id', 'movie_id']);
table.string('comment'); table.string('comment');
table.datetime('created_at')
.notNullable()
.defaultTo(knex.fn.now());
})) }))
.then(() => knex.schema.createTable('stashes_actors', (table) => { .then(() => knex.schema.createTable('stashes_actors', (table) => {
table.integer('stash_id') table.integer('stash_id')
@ -1121,6 +1129,10 @@ exports.up = knex => Promise.resolve()
table.unique(['stash_id', 'actor_id']); table.unique(['stash_id', 'actor_id']);
table.string('comment'); table.string('comment');
table.datetime('created_at')
.notNullable()
.defaultTo(knex.fn.now());
})) }))
// SEARCH // SEARCH
.then(() => { // eslint-disable-line arrow-body-style .then(() => { // eslint-disable-line arrow-body-style