Compare commits

...

24 Commits

Author SHA1 Message Date
ThePendulum 1f444e58ce Allowing image sources to specify queue method. Using 5s queue for Whale Member to avoid CDN time-outs. 2020-07-01 04:47:05 +02:00
ThePendulum 53870fda89 Improved release detail bar behavior. 2020-07-01 00:25:27 +02:00
ThePendulum 240f53047d Minor UI changes. 2020-06-30 04:41:12 +02:00
ThePendulum b803afa973 Added selectable tag function for actors. Implemented experimental filtering by tag. 2020-06-30 04:33:47 +02:00
ThePendulum 3fba2d8a77 Improved 'is new' postgres function to deal with skipped batch IDs. 2020-06-30 02:08:48 +02:00
ThePendulum ff384fb734 Fixed search documents to coalesce empty parent entities. 2020-06-30 01:52:17 +02:00
ThePendulum 08dc06c810 Improved release media layout. 2020-06-30 01:07:48 +02:00
ThePendulum b22fdd841b Using scroll component for release banner, adding expand button. 2020-06-29 04:43:39 +02:00
ThePendulum 8f9eb91b13 Using query instead of parameters for tag filter URI. Added generic scrolling component, using for actor photos and entity children. Removed pagination from filter bar. 2020-06-29 03:55:10 +02:00
ThePendulum 98c19b560f Updated mindgeek scraper for entities. Various fixes. 2020-06-28 22:29:18 +02:00
ThePendulum 41d7d2fa34 Fixed actor description logos. 2020-06-28 04:22:19 +02:00
ThePendulum f4029f0ef7 Resetting scroll status when navigating between entities. 2020-06-28 04:02:44 +02:00
ThePendulum 087d349cec Hiding scroll buttons on small screens. Fixed channel count on overview. 2020-06-28 03:58:16 +02:00
ThePendulum 4bf4183a2a Only show entity children expand when overflowing. 2020-06-28 03:28:11 +02:00
ThePendulum 6d337e7cb2 Added scroll buttons to entity children. 2020-06-28 03:19:09 +02:00
ThePendulum 7d31dd8d52 Fixed seed files for stand-alone channel entities. 2020-06-28 00:44:53 +02:00
ThePendulum 3462d7af2a Entity refactor. Facilitating channels without parent. 2020-06-28 00:15:13 +02:00
ThePendulum 0e8b4caac3 Added generic entity page. 2020-06-27 04:50:13 +02:00
ThePendulum af56378ee2 Refactored various modules for entities. Updated and refactored Kink scraper. 2020-06-27 02:57:30 +02:00
ThePendulum 4959dfd14f Refactored deep and store modules to use entities. 2020-06-25 02:26:25 +02:00
ThePendulum f0a89df6ab Refactoring to use entities over sites and networks. 2020-06-17 04:07:24 +02:00
ThePendulum 1907ce1e54 Changed sites from argument query to group by network. 2020-06-15 03:58:35 +02:00
ThePendulum 79465d9634 Added Teen Core Club. Changed network to entity in GraphQL query. 2020-06-08 03:41:12 +02:00
ThePendulum 09d849eb9d Adding networks and sites as entities, 2020-06-04 01:03:02 +02:00
336 changed files with 3490 additions and 3286 deletions

View File

@ -5,7 +5,7 @@
>
<div class="actor-header">
<h2 class="header-name">
<span v-if="actor.network">{{ actor.name }} ({{ actor.network.name }})</span>
<span v-if="actor.entity">{{ actor.name }} ({{ actor.entity.name }})</span>
<span v-else="">{{ actor.name }}</span>
<Gender
@ -248,20 +248,20 @@
>
<p
v-for="description in actor.descriptions"
:key="`description-${description.network.id}`"
:key="`description-${description.entity.id}`"
class="description"
>
{{ description.text }}
<router-link :to="{ name: 'network', params: { networkSlug: description.network.slug } }">
<router-link :to="`/${description.entity.type}/${description.entity.slug}`">
<img
v-if="description.site"
:src="`/img/logos/${description.network.slug}/thumbs/${description.site.slug}.png`"
v-if="description.entity.type === 'network'"
:src="`/img/logos/${description.entity.slug}/thumbs/network.png`"
class="description-logo"
>
<img
v-else
:src="`/img/logos/${description.network.slug}/thumbs/network.png`"
:src="`/img/logos/${description.entity.parent.slug}/thumbs/${description.entity.slug}.png`"
class="description-logo"
>
</router-link>
@ -283,15 +283,25 @@
</div>
<div class="actor-content">
<Photos
<Scroll
v-if="actor.avatar || (actor.photos && actor.photos.length > 0)"
:actor="actor"
/>
class="scroll-light"
>
<Photos :actor="actor" />
<template v-slot:expanded>
<Photos
class="expanded"
:actor="actor"
/>
</template>
</Scroll>
<FilterBar
:fetch-releases="fetchActor"
:items-total="totalCount"
:items-per-page="limit"
:available-tags="actor.tags"
/>
<Releases :releases="releases" />
@ -307,10 +317,11 @@
</template>
<script>
import Photos from './photos.vue';
import Pagination from '../pagination/pagination.vue';
import FilterBar from '../header/filter-bar.vue';
import Releases from '../releases/releases.vue';
import Photos from './photos.vue';
import Scroll from '../scroll/scroll.vue';
import Gender from './gender.vue';
import Social from './social.vue';
@ -348,6 +359,7 @@ export default {
FilterBar,
Pagination,
Photos,
Scroll,
Releases,
Gender,
Social,
@ -633,19 +645,66 @@ export default {
background: var(--background-dim);
}
.releases {
flex-grow: 1;
border-top: solid 1px var(--crease);
padding: 1rem;
}
.profile-social {
display: none;
}
.expand,
.collapse-header {
.expand {
display: none;
justify-content: center;
align-items: center;
padding: .5rem .25rem;
font-weight: bold;
font-size: .9rem;
cursor: pointer;
.icon {
fill: $shadow;
}
&:hover {
background: $shadow-hint;
.icon {
fill: $shadow-strong;
}
}
}
.expand-sidebar:hover {
background: $shadow-hint;
}
.expand-header {
display: none;
&:hover {
background: $shadow-hint;
}
}
.collapse-header {
display: none;
width: 100%;
justify-content: center;
align-items: center;
padding: 0;
background: $profile;
.icon {
width: 100%;
fill: $highlight;
padding: .5rem 0;
}
&:hover .icon {
background: $highlight-hint;
fill: $text-contrast;
}
}
.scroll {
border-bottom: solid 1px var(--shadow-hint);
}
@media(max-width: $breakpoint4) {

View File

@ -14,6 +14,7 @@
:to="{ name: 'actors', params: { gender: 'male', letter } }"
:class="{ selected: gender === 'male' }"
class="gender-link male"
replace
><Gender gender="male" /></router-link>
</li>
<li class="gender">
@ -21,6 +22,7 @@
:to="{ name: 'actors', params: { gender: 'trans', letter } }"
:class="{ selected: gender === 'trans' }"
class="gender-link transsexual"
replace
><Gender gender="transsexual" /></router-link>
</li>
<li class="gender">
@ -28,6 +30,7 @@
:to="{ name: 'actors', params: { gender: 'other', letter } }"
:class="{ selected: gender === 'other' }"
class="gender-link other"
replace
><Icon icon="question5" /></router-link>
</li>
</ul>
@ -42,6 +45,7 @@
:to="{ name: 'actors', params: { gender, letter: letterX } }"
:class="{ selected: letterX === letter }"
class="letter-link"
replace
>{{ letterX || 'All' }}</router-link>
</li>
</ul>
@ -73,7 +77,7 @@
</template>
<script>
import Actor from '../tile/actor.vue';
import Actor from './tile.vue';
import Gender from './gender.vue';
import Pagination from '../pagination/pagination.vue';
@ -179,7 +183,7 @@ export default {
.tiles {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr));
grid-gap: 0 .5rem;
grid-gap: .5rem;
padding: 1rem;
flex-grow: 1;
}

View File

@ -5,17 +5,17 @@
:class="{
avatar: !!actor.avatar,
empty: actor.photos.length === 0,
wide: actor.photos.length > 2
}"
>
<a
v-if="actor.avatar"
v-show="actor.avatar"
:href="`/media/${actor.avatar.path}`"
target="_blank"
rel="noopener noreferrer"
class="avatar-link photo-link"
>
<img
:src="sfw ? `/img/${actor.avatar.sfw.thumbnail}` : `/media/${actor.avatar.thumbnail}`"
:data-src="sfw ? `/img/${actor.avatar.sfw.thumbnail}` : `/media/${actor.avatar.thumbnail}`"
:data-loading="sfw ? `/img/${actor.avatar.sfw.lazy}` : `/media/${actor.avatar.lazy}`"
:title="actor.avatar.copyright && `© ${actor.avatar.copyright}`"
@ -32,9 +32,10 @@
class="photo-link"
>
<img
:src="sfw ? `/img/${photo.sfw.thumbnail}` : `/media/${photo.thumbnail}`"
:data-src="sfw ? `/img/${photo.sfw.thumbnail}` : `/media/${photo.thumbnail}`"
:data-loading="sfw ? `/img/${photo.sfw.lazy}` : `/media/${photo.lazy}`"
:title="photo.copyright && `© ${photo.copyright}`"
:title="`© ${photo.copyright || photo.entity.name}`"
class="photo"
>
</a>
@ -63,15 +64,33 @@ export default {
@import 'theme';
.photos {
width: 100%;
display: flex;
box-sizing: border-box;
padding: 1rem;
border-bottom: solid 1px var(--darken-hint);
padding: .5rem 1rem;
font-size: 0;
overflow-x: scroll;
scroll-behavior: smooth;
scrollbar-width: none;
&.expanded {
flex-wrap: wrap;
justify-content: center;
/*
display: grid;
grid-gap: .5rem;
grid-template-columns: repeat(auto-fill, minmax(13rem, 1fr));
*/
.photo-link {
margin: 0 .5rem .5rem 0;
}
.photo {
height: 18rem;
}
}
&.empty {
display: none;
}
@ -86,14 +105,13 @@ export default {
}
.photo-link {
height: 16rem;
flex-shrink: 0;
margin: 0 .5rem 0 0;
}
.photo {
height: 100%;
height: 15rem;
box-shadow: 0 0 3px $shadow-weak;
object-fit: cover;
}
@media(max-width: $breakpoint3) {
@ -105,6 +123,16 @@ export default {
.avatar-link {
display: inline-block;
}
&.expanded {
grid-template-columns: repeat(auto-fill, minmax(10rem, 1fr));
}
}
}
@media(max-width: $breakpoint0) {
.photos.expanded {
grid-template-columns: repeat(auto-fill, minmax(8rem, 1fr));
}
}
</style>

View File

@ -106,7 +106,7 @@
</template>
<script>
import Gender from '../actors/gender.vue';
import Gender from './gender.vue';
function sfw() {
return this.$store.state.ui.sfw;
@ -139,7 +139,6 @@ export default {
width: 100%;
display: inline-block;
position: relative;
margin: 0 .5rem .5rem 0;
box-shadow: 0 0 3px var(--darken-weak);
background: var(--background);
overflow: hidden;

View File

@ -80,11 +80,11 @@ export default {
flex-direction: column;
flex-grow: 1;
overflow-y: auto;
overflow-x: hidden;
}
.content-inner {
flex-grow: 1;
padding: 1rem;
border-top: solid 1px var(--crease);
flex-grow: 1;
overflow-y: auto;
}
</style>

View File

@ -0,0 +1,64 @@
<template>
<div class="children">
<EntityTile
v-for="child in entity.children"
:key="`child-${child.id}`"
:entity="child"
/>
</div>
</template>
<script>
import EntityTile from './tile.vue';
export default {
components: {
EntityTile,
},
props: {
entity: {
type: Object,
default: null,
},
},
};
</script>
<style lang="scss" scoped>
@import 'theme';
.children {
display: flex;
box-sizing: border-box;
padding: 1rem;
border-bottom: solid 1px var(--darken-hint);
overflow-x: auto;
scroll-behavior: smooth;
scrollbar-width: none;
.tile {
width: 15rem;
margin: 0 1rem 0 0;
}
&.expanded {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
grid-gap: 1rem;
.tile {
width: 100%;
}
}
&::-webkit-scrollbar {
display: none;
}
}
@media(max-width: $breakpoint0) {
.children.expanded {
grid-template-columns: repeat(auto-fill, minmax(10rem, 1fr));
}
}
</style>

View File

@ -0,0 +1,202 @@
<template>
<div
v-if="entity"
class="entity content"
>
<div class="info">
<a
:href="entity.url"
target="_blank"
rel="noopener"
class="link link-child"
>
<template v-if="entity.hasLogo">
<img
v-if="$route.name === 'network'"
class="logo logo-child"
:src="`/img/logos/${entity.slug}/thumbs/network.png`"
>
<img
v-else-if="entity.parent"
class="logo logo-child"
:src="`/img/logos/${entity.parent.slug}/thumbs/${entity.slug}.png`"
>
<img
v-else
class="logo logo-child"
:src="`/img/logos/${entity.slug}/thumbs/${entity.slug}.png`"
>
</template>
<h2
v-else
class="name"
>{{ entity.name }}</h2>
</a>
<router-link
v-if="entity.parent"
:to="`/${entity.parent.type}/${entity.parent.slug}`"
class="link link-parent"
>
<img
v-if="entity.parent.hasLogo"
class="logo logo-parent"
:src="`/img/logos/${entity.parent.slug}/thumbs/network.png`"
>
<h3
v-else
class="name parent-name"
>{{ entity.parent.name }}</h3>
</router-link>
</div>
<div
ref="content"
class="content-inner"
>
<Scroll
v-if="entity.children.length > 0"
class="scroll-dark"
>
<Children :entity="entity" />
<template v-slot:expanded>
<Children
class="expanded"
:entity="entity"
/>
</template>
</Scroll>
<FilterBar
:fetch-releases="fetchEntity"
:items-total="totalCount"
:items-per-page="limit"
/>
<div class="releases">
<Releases :releases="entity.releases" />
<Pagination
:items-total="totalCount"
:items-per-page="limit"
class="pagination-top"
/>
</div>
</div>
</div>
</template>
<script>
import Vue from 'vue';
import FilterBar from '../header/filter-bar.vue';
import Pagination from '../pagination/pagination.vue';
import Releases from '../releases/releases.vue';
import Children from './children.vue';
import Scroll from '../scroll/scroll.vue';
async function fetchEntity() {
const { entity, totalCount } = await this.$store.dispatch('fetchEntityBySlugAndType', {
entitySlug: this.$route.params.entitySlug,
entityType: this.$route.name,
limit: this.limit,
range: this.$route.params.range,
pageNumber: Number(this.$route.params.pageNumber),
});
this.entity = entity;
this.totalCount = totalCount;
this.pageTitle = entity.name;
Vue.nextTick(() => {
this.$refs.content.scrollTop = 0;
});
}
async function mounted() {
await this.fetchEntity();
}
async function route() {
await this.fetchEntity();
}
export default {
components: {
FilterBar,
Pagination,
Children,
Releases,
Scroll,
},
data() {
return {
entity: null,
totalCount: null,
limit: 20,
};
},
watch: {
$route: route,
},
mounted,
methods: {
fetchEntity,
},
};
</script>
<style lang="scss" scoped>
@import 'theme';
.info {
height: 2.5rem;
display: flex;
justify-content: space-between;
padding: 1rem;
background: var(--profile);
border-bottom: solid 1px var(--lighten-hint);
.link {
display: flex;
align-items: center;
text-decoration: none;
flex-grow: 1;
}
.link-child {
margin: 0 2rem 0 0;
}
.link-parent {
flex-direction: row-reverse;
}
}
.logo {
height: 100%;
width: 100%;
max-width: 20rem;
object-fit: contain;
object-position: 0 50%;
}
.name {
color: var(--text-light);
display: flex;
align-items: center;
padding: 0;
white-space: nowrap;
font-size: 1.5rem;
}
.logo-parent {
object-position: 100% 50%;
}
</style>

View File

@ -0,0 +1,79 @@
<template>
<router-link
:to="`/${entity.type}/${entity.slug}`"
:title="entity.name"
class="tile"
>
<template v-if="entity.hasLogo">
<img
v-if="entity.type === 'network'"
:src="`/img/logos/${entity.slug}/thumbs/network.png`"
:alt="entity.name"
class="logo"
>
<img
v-else-if="entity.parent"
:src="`/img/logos/${entity.parent.slug}/thumbs/${entity.slug}.png`"
:alt="entity.name"
class="logo"
>
<img
v-else
:src="`/img/logos/${entity.slug}/thumbs/${entity.slug}.png`"
:alt="entity.name"
class="logo"
>
</template>
<span
v-else
class="name"
>{{ entity.name }}</span>
</router-link>
</template>
<script>
export default {
props: {
entity: {
type: Object,
default: null,
},
},
};
</script>
<style lang="scss" scoped>
@import 'theme';
.tile {
height: 6rem;
background: $tile;
display: flex;
flex-shrink: 0;
justify-content: center;
align-items: center;
box-sizing: border-box;
padding: .5rem 1rem;
border-radius: .25rem;
box-shadow: 0 0 3px rgba(0, 0, 0, .25);
text-align: center;
text-decoration: none;
}
.logo {
max-width: 100%;
max-height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.name {
color: var(--text-light);
font-size: 1.25rem;
font-weight: bold;
}
</style>

View File

@ -0,0 +1,63 @@
<template>
<div>
<div
v-show="expanded"
class="expand-button expanded noselect"
@click="$emit('expand', false)"
><Icon icon="arrow-up3" /></div>
<div
v-show="!expanded"
class="expand-button noselect"
@click="$emit('expand', true)"
><Icon icon="arrow-down3" /></div>
</div>
</template>
<script>
export default {
props: {
expanded: {
type: Boolean,
default: false,
},
},
};
</script>
<style lang="scss" scoped>
.expand-button {
width: 100%;
display: flex;
align-items: center;
justify-content: center;
padding: .5rem;
.icon {
fill: var(--shadow);
}
&:hover {
cursor: pointer;
background: var(--shadow-hint);
.icon {
fill: var(--text);
}
}
}
.expand-dark {
.icon {
fill: var(--lighten);
}
&:hover {
background: var(--lighten-hint);
.icon {
fill: var(--text-light);
}
}
}
</style>

View File

@ -20,37 +20,18 @@
>New</router-link>
</span>
<Pagination
:items-total="itemsTotal"
:items-per-page="itemsPerPage"
<Filters
class="filters"
:filter="filter"
:available-tags="availableTags"
@set-filter="setFilter"
/>
<span class="tags">
<Filters
class="filters-block"
:filter="filter"
@set-filter="setFilter"
/>
<v-popover class="filters-compact">
<Icon icon="filter" />
<div slot="popover">
<Filters
:compact="true"
:filter="filter"
@set-filter="setFilter"
/>
</div>
</v-popover>
</span>
</div>
</template>
<script>
import { mapState } from 'vuex';
import Filters from './filters.vue';
import Pagination from '../pagination/pagination.vue';
function filter(state) {
return state.ui.filter;
@ -85,7 +66,6 @@ async function setBatch(newBatch) {
export default {
components: {
Filters,
Pagination,
},
props: {
fetchReleases: {
@ -104,6 +84,10 @@ export default {
type: Number,
default: 10,
},
availableTags: {
type: Array,
default: () => [],
},
},
computed: {
...mapState({
@ -141,25 +125,14 @@ export default {
.sort {
display: flex;
align-items: center;
margin: 0 0 -1px 0;
}
.filters-block {
display: inline-block;
}
.filters-compact {
font-size: 1rem;
font-weight: bold;
display: none;
margin: 0 0 0 .5rem;
}
.range-button {
color: var(--shadow);
display: inline-block;
padding: .75rem 1rem 1rem 1rem;
padding: .8rem 1rem;
border: none;
position: relative;
font-size: .8rem;
font-weight: bold;
text-decoration: none;
@ -175,26 +148,22 @@ export default {
color: var(--primary);
background: var(--background-soft);
border-color: var(--crease);
&::after {
/* hide grey border for tab effect, negative margin shows grey crease on mobile */
content: '';
width: 100%;
height: 2px;
background: var(--background-soft);
position: absolute;
left: 0;
bottom: -1px;
}
}
}
.tags {
.filters {
display: inline-block;
flex-shrink: 0;
}
@media(max-width: $breakpoint2) {
.pagination {
display: none;
}
}
@media(max-width: $breakpoint) {
.filters-container {
display: none;
}
.filters-compact {
display: inline-block;
}
}
</style>

View File

@ -1,28 +1,28 @@
<template>
<v-popover class="filters-container">
<div class="filters">
<Icon icon="filter" />
<div
v-if="selectedTags.length > 0"
class="applied"
>{{ selectedTags.join(', ') }}</div>
>{{ selectedTags.length }} selected</div>
<div
v-else
class="applied empty"
>Filter by tags</div>
<Icon icon="filter" />
</div>
<div slot="popover">
<ul class="tags nolist">
<li
v-for="tag in tags"
:key="`tag-${tag}`"
v-for="tag in availableTags"
:key="`tag-${tag.id}`"
class="tag"
:class="{ selected: selectedTags.includes(tag) }"
:class="{ selected: selectedTags.includes(tag.slug) }"
>
<router-link :to="{ params: { tags: getNewRange(tag) } }">
<router-link :to="{ query: { ...getNewRange(tag.slug) } }">
<Icon
icon="checkmark"
class="include"
@ -30,9 +30,9 @@
</router-link>
<router-link
:to="{ params: { tags: selectedTags.length === 1 && selectedTags.includes(tag) ? 'all-tags' : tag } }"
:to="{ query: { ...(selectedTags.length === 1 && selectedTags.includes(tag.slug) ? null : { tags: tag.slug }) } }"
class="name"
>{{ tag }}</router-link>
>{{ tag.name }}</router-link>
</li>
</ul>
</div>
@ -40,33 +40,20 @@
</template>
<script>
const tags = [
'airtight',
'anal',
'blowjob',
'double-penetration',
'facial',
'gangbang',
'lesbian',
'mff',
'mfm',
'orgy',
];
function getNewRange(tag) {
if (this.selectedTags.includes(tag)) {
if (this.selectedTags.length === 1) {
return 'all-tags';
if (this.selectedTags.length > 1) {
return { tags: this.selectedTags.filter(selectedTag => selectedTag !== tag).join(',') };
}
return this.selectedTags.filter(selectedTag => selectedTag !== tag).join('+');
return {};
}
return this.selectedTags.concat(tag).join('+');
return { tags: this.selectedTags.concat(tag).join(',') };
}
function selectedTags() {
return this.$route.params.tags.split('+').filter(selectedTag => selectedTag !== 'all-tags');
return this.$route.query.tags ? this.$route.query.tags.split(',') : [];
}
export default {
@ -79,11 +66,10 @@ export default {
type: Boolean,
default: false,
},
},
data() {
return {
tags,
};
availableTags: {
type: Array,
default: () => [],
},
},
computed: {
selectedTags,
@ -105,17 +91,28 @@ export default {
fill: var(--shadow);
margin: 0 .5rem 0 0;
}
&:hover {
cursor: pointer;
.applied {
color: var(--shadow-strong);
}
.icon {
fill: var(--shadow-strong);
}
}
}
.applied {
width: 12rem;
background: var(--background);
padding: .5rem;
border: solid 1px var(--shadow-hint);
flex-grow: 1;
padding: .75rem .5rem;
font-size: 1rem;
white-space: nowrap;
overflow-x: hidden;
overflow: hidden;
text-overflow: ellipsis;
text-align: right;
&.empty {
color: var(--shadow);
@ -142,6 +139,7 @@ export default {
}
.name {
min-width: 8rem;
display: flex;
justify-content: space-between;
flex-grow: 1;
@ -165,4 +163,10 @@ export default {
fill: var(--success);
}
}
@media(max-width: $breakpoint0) {
.applied {
display: none;
}
}
</style>

View File

@ -43,7 +43,7 @@
:href="href"
:class="{ active: isActive }"
@click="navigate"
>Sites</a>
>Channels</a>
</router-link>
</li>

View File

@ -1,13 +1,17 @@
<template>
<div class="content">
<FilterBar
:fetch-releases="fetchReleases"
:is-home="true"
:items-total="totalCount"
:items-per-page="limit"
/>
<div
ref="content"
class="content-inner"
>
<FilterBar
:fetch-releases="fetchReleases"
:is-home="true"
:items-total="totalCount"
:items-per-page="limit"
:content="$refs.content"
/>
<div class="content-inner">
<Releases :releases="releases" />
<Pagination
@ -34,6 +38,8 @@ async function fetchReleases() {
this.totalCount = totalCount;
this.releases = releases;
this.$refs.content.scrollTop = 0;
}
async function route() {
@ -57,7 +63,7 @@ export default {
releases: [],
networks: [],
pageTitle: null,
limit: 15,
limit: 20,
totalCount: 0,
from: null,
};

View File

@ -132,28 +132,19 @@ import Sites from '../sites/sites.vue';
import Network from '../tile/network.vue';
async function fetchNetwork() {
const { network, totalCount } = await this.$store.dispatch('fetchNetworkBySlug', {
networkSlug: this.$route.params.networkSlug,
const { entity, totalCount } = await this.$store.dispatch('fetchEntityBySlugAndType', {
entitySlug: this.$route.params.networkSlug,
entityType: 'network',
limit: this.limit,
range: this.$route.params.range,
pageNumber: Number(this.$route.params.pageNumber),
});
this.network = network;
this.totalCount = totalCount;
if (this.network.studios) {
this.studios = this.network.studios.map(studio => ({
...studio,
network: this.network,
}));
}
this.networks = this.network.networks;
this.sites = this.network.sites
.filter(site => !site.independent);
this.network = entity;
this.networks = this.network.children;
this.releases = this.network.releases;
this.totalCount = totalCount;
}
async function route() {

View File

@ -2,13 +2,13 @@
<div class="networks">
<form
class="search"
@submit.prevent="searchSites"
@submit.prevent="searchEntities"
>
<input
v-model="query"
:placeholder="`Find ${siteCount} sites in ${networks.length} networks`"
:placeholder="`Find ${channelCount} channels in ${entities.length} networks`"
class="query"
@input="searchSites"
@input="searchEntities"
>
<button
@ -19,12 +19,12 @@
<div
v-if="query.length"
class="network-tiles"
class="entity-tiles"
>
<Site
v-for="site in searchResults"
:key="`site-tile-${site.slug}`"
:site="site"
<Entity
v-for="entity in searchResults"
:key="`${entity.type}-tile-${entity.slug}`"
:entity="entity"
/>
<span v-if="searchResults.length === 0">No results for "{{ query }}"</span>
@ -32,56 +32,62 @@
<div
v-if="query.length === 0"
class="network-tiles"
class="entity-tiles"
>
<Network
v-for="network in networks"
:key="`network-tile-${network.slug}`"
:network="network"
<Entity
v-for="entity in entities"
:key="`entity-tile-${entity.slug}`"
:entity="entity"
/>
</div>
</div>
</template>
<script>
import Network from '../tile/network.vue';
import Site from '../tile/site.vue';
import Entity from '../entities/tile.vue';
async function searchSites() {
this.searchResults = await this.$store.dispatch('searchSites', {
async function searchEntities() {
this.searchResults = await this.$store.dispatch('searchEntities', {
query: this.query,
limit: 20,
limit: 50,
});
}
async function mounted() {
this.networks = await this.$store.dispatch('fetchNetworks');
this.entities = await this.$store.dispatch('fetchEntities', {
type: 'network',
entitySlugs: [
'bamvisions',
'evilangel',
'legalporno',
],
});
this.pageTitle = 'Networks';
}
function siteCount() {
return this.networks.map(network => network.sites).flat().length;
function channelCount() {
return this.entities.reduce((acc, entity) => acc + entity.childrenTotal, 0);
}
export default {
components: {
Network,
Site,
Entity,
},
data() {
return {
query: '',
pageTitle: null,
networks: [],
entities: [],
searchResults: [],
};
},
computed: {
siteCount,
channelCount,
},
mounted,
methods: {
searchSites,
searchEntities,
},
};
</script>
@ -135,7 +141,7 @@ export default {
}
}
.network-tiles {
.entity-tiles {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
grid-gap: 1rem;

View File

@ -89,7 +89,6 @@ export default {
<style lang="scss" scoped>
.pagination {
width: 100%;
display: flex;
justify-content: center;
}
@ -99,7 +98,7 @@ export default {
}
.pagination-bottom {
margin: 1rem 0 0 0;
margin: .5rem 0 1rem 0;
}
.pagination-button {

View File

@ -1,16 +1,16 @@
<template>
<div
class="banner"
@wheel.prevent="scrollBanner"
>
<div class="trailer">
<div class="media">
<div
v-if="release.trailer || release.teaser"
class="trailer-container"
>
<video
v-if="release.trailer"
:src="`/media/${release.trailer.path}`"
:poster="release.poster && (sfw ? `/img/${release.poster.sfw.thumbnail}` : `/media/${release.poster.thumbnail}`)"
:alt="release.title"
:class="{ sfw: sfw && paused }"
class="item trailer-video"
class="item trailer"
controls
@playing="playing = true; paused = false;"
@pause="playing = false; paused = true;"
@ -22,7 +22,7 @@
:poster="release.poster && (sfw ? `/img/${release.poster.sfw.thumbnail}` : `/media/${release.poster.thumbnail}`)"
:alt="release.title"
:class="{ sfw: sfw && paused }"
class="item trailer-video"
class="item trailer"
controls
@playing="playing = true; paused = false;"
@pause="playing = false; paused = true;"
@ -32,7 +32,7 @@
v-else-if="release.teaser && /^image\//.test(release.teaser.mime)"
:src="sfw ? `/img/${release.teaser.sfw.thumbnail}` : `/media/${release.teaser.path}`"
:alt="release.title"
class="item trailer-video"
class="item trailer"
>
<a
@ -69,7 +69,7 @@
<a
v-for="photo in photos"
:key="`banner-${photo.index}`"
:key="`media-${photo.index}`"
:href="`/media/${photo.path}`"
:class="{ sfw }"
class="item-link"
@ -104,16 +104,14 @@ function photos() {
}
if (this.release.poster) {
// no trailer, add poster to photos
return [this.release.poster].concat(this.release.photos);
}
// no poster available
return this.release.photos;
}
function scrollBanner(event) {
event.currentTarget.scrollLeft += event.deltaY; // eslint-disable-line no-param-reassign
}
export default {
props: {
release: {
@ -131,24 +129,38 @@ export default {
photos,
sfw,
},
methods: {
scrollBanner,
},
};
</script>
<style lang="scss" scoped>
@import 'theme';
.banner {
background: var(--empty);
.media {
background: var(--background-dim);
flex-shrink: 0;
white-space: nowrap;
overflow-x: auto;
scrollbar-width: none;
box-shadow: 0 0 3px var(--shadow);
scroll-behavior: smooth;
font-size: 0;
&.expanded {
display: flex;
flex-wrap: wrap;
.item-link,
.trailer {
margin: 0 .5rem .5rem 0;
max-width: 100%;
height: 18rem;
flex: 1;
}
.item {
height: 100%;
}
}
&::-webkit-scrollbar {
display: none;
}
@ -182,11 +194,12 @@ export default {
}
.item-link,
.trailer {
.trailer-container {
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
box-shadow: 0 0 3px var(--shadow-weak);
.warning {
display: none;
@ -223,15 +236,19 @@ export default {
.item {
height: 18rem;
vertical-align: middle;
object-fit: cover;
}
.trailer-container {
height: 18rem;
width: 32rem;
max-width: 100%;
}
.trailer {
max-width: 100vw;
}
.trailer-video {
max-width: 100%;
object-fit: cover;
max-width: 100%;
max-height: 100%;
object-fit: cover;
&.sfw {
filter: blur(2rem);
@ -249,9 +266,10 @@ export default {
}
}
@media(max-width: $breakpoint2) {
.trailer-video {
object-fit: contain;
@media(max-width: $breakpoint0) {
.media:not(.expanded) .item,
.trailer-container {
height: 56vw; /* 16:9 ratio for full-width video */
}
}
</style>

View File

@ -3,77 +3,49 @@
v-if="release"
class="content"
>
<Banner :release="release" />
<Expand
v-if="expanded"
class="expand"
:expanded="expanded"
@expand="(state) => expanded = state"
/>
<Scroll
class="scroll-light"
:expandable="false"
>
<Media
:release="release"
:class="{ expanded }"
/>
</Scroll>
<div class="details">
<div class="column">
<a
v-if="release.date"
v-tooltip.bottom="release.url && `View scene on ${release.site.name}`"
:title="release.url && `View scene on ${release.site.name}`"
:href="release.url"
:class="{ link: release.url }"
target="_blank"
rel="noopener noreferrer"
class="tidbit date"
>
<Icon
v-if="isAfter(new Date(), release.date)"
icon="calendar2"
/>
<Icon
v-else
v-tooltip.bottom="'To be released'"
icon="sun3"
/>
<span class="showable">{{ formatDate(release.date, 'MMM D, YYYY') }}</span>
<span class="hideable">{{ formatDate(release.date, 'MMMM D, YYYY') }}</span>
</a>
<span
v-if="release.shootId"
v-tooltip.bottom="`Shoot #`"
class="tidbit shoot hideable"
>
<Icon icon="clapboard-play" />
{{ release.shootId }}
</span>
<span
v-if="release.duration"
v-tooltip.bottom="`Duration`"
class="tidbit duration hideable"
>
<Icon icon="stopwatch" />
<span
v-if="release.duration >= 3600"
class="duration-segment"
>{{ Math.floor(release.duration / 3600).toString().padStart(2, '0') }}:</span>
<span class="duration-segment">{{ Math.floor((release.duration % 3600) / 60).toString().padStart(2, '0') }}:</span>
<span class="duration-segment">{{ (release.duration % 60).toString().padStart(2, '0') }}</span>
</span>
<span class="tidbit site">
<div class="tidbits">
<a
v-if="release.site.independent"
:href="`/network/${release.network.slug}`"
v-if="release.date"
v-tooltip.bottom="release.url && `View scene on ${release.entity.name}`"
:title="release.url && `View scene on ${release.entity.name}`"
:href="release.url"
:class="{ link: release.url }"
target="_blank"
rel="noopener noreferrer"
class="tidbit date"
>
<img
:src="`/img/logos/${release.network.slug}/thumbs/network.png`"
:title="release.network.name"
class="logo logo-site"
>
<span class="showable">{{ formatDate(release.date, 'MMM D, YYYY') }}</span>
<span class="hideable">{{ formatDate(release.date, 'MMMM D, YYYY') }}</span>
<Icon icon="share2" />
</a>
</div>
<template v-else>
<a :href="`/network/${release.network.slug}`">
<div class="site">
<template v-if="release.entity.parent">
<a :href="`/network/${release.entity.parent.slug}`">
<img
:src="`/img/logos/${release.network.slug}/thumbs/network.png`"
:title="release.network.name"
:alt="release.network.name"
:src="`/img/logos/${release.entity.parent.slug}/thumbs/network.png`"
:title="release.entity.parent.name"
:alt="release.entity.parent.name"
class="logo logo-network"
>
</a>
@ -81,22 +53,72 @@
<span class="chain">presents</span>
<a
:href="`/site/${release.site.slug}`"
:href="`/${release.entity.type}/${release.entity.slug}`"
>
<img
:src="`/img/logos/${release.network.slug}/thumbs/${release.site.slug}.png`"
:title="release.site.name"
v-if="release.entity.type === 'network'"
:src="`/img/logos/${release.entity.slug}/thumbs/network.png`"
:title="release.entity.name"
class="logo logo-site"
>
<img
v-else
:src="`/img/logos/${release.entity.parent.slug}/thumbs/${release.entity.slug}.png`"
:title="release.entity.name"
class="logo logo-site"
>
</a>
</template>
</span>
<a
v-else
:href="`/channel/${release.entity.slug}`"
>
<img
:src="`/img/logos/${release.entity.slug}/thumbs/network.png`"
:title="release.entity.name"
class="logo logo-site"
>
</a>
</div>
</div>
</div>
<Expand
v-if="release.photos.length > 0 || release.trailer || release.teaser"
class="expand-bottom"
:expanded="expanded"
@expand="(state) => expanded = state"
/>
<div class="info column">
<h2 class="row title">{{ release.title }}</h2>
<div class="row title-container">
<h2 class="title">{{ release.title }}</h2>
<span
v-if="release.shootId"
class="title-shoot"
>{{ release.shootId }}</span>
</div>
<div
v-if="release.tags.length > 0"
class="row"
>
<ul class="tags nolist">
<li
v-for="tag in release.tags"
:key="`tag-${tag.slug}`"
class="tag"
>
<a
:href="`/tag/${tag.slug}`"
class="link"
>{{ tag.name }}</a>
</li>
</ul>
</div>
<div class="row associations">
<ul
@ -131,67 +153,47 @@
</div>
<div
v-if="release.tags.length > 0"
v-if="release.description"
class="row"
>
<Icon icon="price-tags3" />
<span class="row-label">Description</span>
<ul class="tags nolist">
<li
v-for="tag in release.tags"
:key="`tag-${tag.slug}`"
class="tag"
>
<a
:href="`/tag/${tag.slug}`"
class="link"
>{{ tag.name }}</a>
</li>
</ul>
<p class="description">{{ release.description }}</p>
</div>
<div
v-if="release.duration"
class="row duration showable"
class="row"
>
<Icon icon="stopwatch" />
<span class="row-label">Duration</span>
<span
v-if="release.duration >= 3600"
class="duration-segment"
>{{ Math.floor(release.duration / 3600).toString().padStart(2, '0') }}:</span>
<span class="duration-segment">{{ Math.floor((release.duration % 3600) / 60).toString().padStart(2, '0') }}:</span>
<span class="duration-segment">{{ (release.duration % 60).toString().padStart(2, '0') }}</span>
<div class="duration">
<span
v-if="release.duration >= 3600"
class="duration-segment"
>{{ Math.floor(release.duration / 3600).toString().padStart(2, '0') }}:</span>
<span class="duration-segment">{{ Math.floor((release.duration % 3600) / 60).toString().padStart(2, '0') }}:</span>
<span class="duration-segment">{{ (release.duration % 60).toString().padStart(2, '0') }}</span>
</div>
</div>
<p
v-if="release.description"
class="row description"
>
<Icon icon="info2" />
{{ release.description }}
</p>
<div
v-if="release.studio"
class="row"
>
<Icon icon="video-camera2" />
<span class="row-label">Studio</span>
<a
v-if="release.studio"
:href="release.studio.url"
target="_blank"
rel="noopener noreferrer"
class="link"
>{{ release.studio.name }}</a>
<router-link
:to="`/studio/${release.studio.slug}`"
class="link studio"
>{{ release.studio.name }}</router-link>
</div>
<div
v-if="release.shootId"
class="row showable"
class="row"
>
<Icon icon="clapboard-play" />
<span class="row-label">Shoot #</span>
<a
:href="release.url"
@ -203,7 +205,7 @@
</div>
<span class="row">
<Icon icon="drawer-in" />
<span class="row-label">Added</span>
<a
:href="`/added/${formatDate(release.dateAdded, 'YYYY-MM-DD')}`"
@ -211,71 +213,50 @@
target="_blank"
rel="noopener noreferrer"
class="link added"
>{{ formatDate(release.dateAdded, 'MMMM D, YYYY') }}</a>
>{{ formatDate(release.createdAt, 'MMMM D, YYYY HH:mm') }}</a>
</span>
<div class="row">
<Icon icon="paste2" />
<input
class="filename"
:value="filename"
@focus="copyFilename"
>
</div>
</div>
</div>
</template>
<script>
import config from 'config';
import format from 'template-format';
// import config from 'config';
// import format from 'template-format';
import Banner from './banner.vue';
import Actor from '../tile/actor.vue';
import Release from '../tile/release.vue';
import Media from './media.vue';
import Actor from '../actors/tile.vue';
import Release from './tile.vue';
import Releases from './releases.vue';
import Scroll from '../scroll/scroll.vue';
import Expand from '../expand/expand.vue';
function pageTitle() {
return this.release && this.release.title;
}
function copyFilename(event) {
event.target.setSelectionRange(0, event.target.value.length);
document.execCommand('copy');
}
async function mounted() {
this.release = await this.$store.dispatch('fetchReleaseById', this.$route.params.releaseId);
this.filename = format(config.filename.pattern, {
...this.release,
shootId: this.release.shootId || '',
date: this.formatDate(this.release.date, config.filename.date),
}, {
spreadSeparator: config.filename.separator,
});
}
export default {
components: {
Actor,
Banner,
Media,
Release,
Releases,
Scroll,
Expand,
},
data() {
return {
release: null,
filename: null,
expanded: false,
};
},
computed: {
pageTitle,
},
mounted,
methods: {
copyFilename,
},
};
</script>
@ -289,30 +270,6 @@ export default {
box-sizing: border-box;
}
.info {
padding: 1rem;
border-left: solid 1px var(--shadow-hint);
border-right: solid 1px var(--shadow-hint);
flex-grow: 1;
}
.row {
display: flex;
align-items: center;
margin: 0 0 1rem 0;
&.associations {
align-items: start;
}
.icon {
display: inline-block;
width: 1rem;
fill: var(--shadow-strong);
margin: 0 1rem 0 0;
}
}
.details {
background: var(--profile);
color: var(--text-light);
@ -320,7 +277,9 @@ export default {
cursor: default;
.column {
height: 100%;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 1rem;
}
@ -330,33 +289,35 @@ export default {
}
}
.tidbits {
flex-shrink: 0;
height: 100%;
}
.tidbit {
display: inline-block;
display: inline-flex;
align-items: center;
height: 100%;
&:not(:last-child) {
border-right: solid 1px var(--lighten-hint);
}
.icon {
fill: var(--lighten-weak);
margin: 0 .25rem 0 0;
}
&.date,
&.duration,
&.shoot {
&.date {
flex-shrink: 0;
padding: 1.25rem 1rem 1.25rem 0;
margin: 0 1rem 0 0;
padding: 0 2rem 0 0;
font-weight: bold;
.icon {
fill: var(--lighten);
margin: -.2rem 0 0 .5rem;
}
}
}
.site {
display: inline-flex;
flex-grow: 1;
align-items: center;
justify-content: flex-end;
padding: .25rem 0;
font-size: 0;
}
@ -366,8 +327,9 @@ export default {
}
.logo-site {
height: 3rem;
height: 2.5rem;
max-width: 15rem;
margin: .25rem 0;
object-fit: contain;
object-position: 100% 50%;
}
@ -386,12 +348,63 @@ export default {
font-size: .8rem;
}
.expand-bottom {
border-bottom: solid 1px var(--shadow-hint);
}
.info {
padding: 1rem;
border-left: solid 1px var(--shadow-hint);
border-right: solid 1px var(--shadow-hint);
flex-grow: 1;
}
.row {
margin: 0 0 1rem 0;
&.associations {
align-items: start;
}
.icon {
display: inline-block;
width: 1rem;
fill: var(--shadow-strong);
margin: 0 1rem 0 0;
}
}
.row-label {
display: block;
margin: 0 0 .5rem 0;
color: var(--shadow);
font-weight: bold;
.icon {
margin: 0 .5rem 0 0;
fill: var(--shadow);
}
}
.title-container {
display: flex;
justify-content: space-between;
}
.title {
margin: 0 0 1.5rem 0;
margin: 0;
}
.title-shoot {
margin: 0 0 0 .5rem;
color: var(--shadow);
font-size: .9rem;
font-weight: bold;
}
.description {
line-height: 1.5;
margin: -.25rem 0 0 0;
}
.duration {
@ -405,28 +418,13 @@ export default {
.actors {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(10rem, 1fr));
grid-gap: 1rem;
grid-gap: .5rem;
flex-grow: 1;
flex-wrap: wrap;
}
.actor {
margin: 0 1rem .5rem 0;
}
.movies {
}
.filename {
width: 100%;
padding: .5rem;
color: var(--text);
border: solid 1px var(--shadow-weak);
background: var(--background);
}
.link {
display: inline-block;
display: inline-flex;
color: var(--link);
text-decoration: none;
@ -462,6 +460,10 @@ export default {
.chain {
display: none;
}
.logo-site {
width: 100%;
}
}
@media(max-width: $breakpoint) {
@ -477,11 +479,6 @@ export default {
display: inline-block;
}
.logo-site {
width: 15rem;
max-width: 100%;
}
.actors {
grid-template-columns: repeat(auto-fill, minmax(8rem, 1fr));
}

View File

@ -6,6 +6,7 @@
><span class="range">{{ range }}</span> releases for '{{ context }}'</h3>
<ul
v-if="releases.length > 0"
:key="sfw"
v-lazy-container="{ selector: '.thumbnail' }"
class="nolist tiles"
@ -35,7 +36,7 @@
</template>
<script>
import ReleaseTile from '../tile/release.vue';
import ReleaseTile from './tile.vue';
function range() {
return this.$route.params.range;
@ -82,14 +83,30 @@ export default {
}
}
.releases {
border-top: solid 1px var(--crease);
&.embedded {
border: none;
.tiles {
padding: 0;
}
}
}
.tiles {
width: 100%;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(22rem, 1fr));
grid-gap: 1rem;
box-sizing: border-box;
padding: 1rem;
}
.empty {
display: inline-block;
padding: 1rem;
color: var(--shadow-strong);
font-weight: bold;
}

View File

@ -6,43 +6,41 @@
>
<span class="poster">
<span class="details">
<router-link
v-if="release.site && release.site.independent"
:to="`/network/${release.network.slug}`"
class="site site-link"
><img
:src="`/img/logos/${release.network.slug}/favicon.png`"
class="favicon"
>{{ release.network.name }}</router-link>
<span
v-else-if="release.network"
v-if="release.entity.type !== 'network' && release.entity.parent"
class="site"
>
<router-link
v-tooltip.bottom="`Part of ${release.network.name}`"
:title="`Part of ${release.network.name}`"
:to="`/network/${release.network.slug}`"
v-tooltip.bottom="`Part of ${release.entity.parent.name}`"
:title="`Part of ${release.entity.parent.name}`"
:to="`/${release.entity.parent.type}/${release.entity.parent.slug}`"
class="site-link"
><img
:src="`/img/logos/${release.network.slug}/favicon.png`"
:src="`/img/logos/${release.entity.parent.slug}/favicon.png`"
class="favicon"
></router-link>
<router-link
v-tooltip.bottom="`More from ${release.site.name}`"
:title="`More from ${release.site.name}`"
:to="`/site/${release.site.slug}`"
v-tooltip.bottom="`More from ${release.entity.name}`"
:title="`More from ${release.entity.name}`"
:to="`/${release.entity.type}/${release.entity.slug}`"
class="site-link"
>{{ release.site.name }}</router-link>
>{{ release.entity.name }}</router-link>
</span>
<span v-else />
<router-link
v-else
:to="`/${release.entity.type}/${release.entity.slug}`"
class="site site-link"
><img
:src="`/img/logos/${release.entity.slug}/favicon.png`"
class="favicon"
>{{ release.entity.name }}</router-link>
<a
v-if="release.date"
v-tooltip.bottom="release.url && `View scene on ${release.site.name}`"
:title="release.url && `View scene on ${release.site.name}`"
v-tooltip.bottom="release.url && `View scene on ${release.entity.name}`"
:title="release.url && `View scene on ${release.entity.name}`"
:href="release.url"
:class="{ upcoming: isAfter(release.date, new Date()), new: release.isNew }"
target="_blank"
@ -250,7 +248,7 @@ export default {
}
.date {
&.upcoming:before {
&.new:before {
content: '';
background: var(--primary);
width: .5rem;
@ -260,10 +258,6 @@ export default {
bottom: 0;
left: -.5rem;
}
&.new {
font-weight: bold;
}
}
.site {

View File

@ -0,0 +1,258 @@
<template>
<div class="scroll">
<Expand
v-if="expanded"
:expanded="expanded"
class="expand-dark"
@expand="(state) => $emit('expand', state)"
/>
<div class="scrollable">
<Expand
v-if="expanded"
:expanded="expanded"
class="expand-light"
@expand="(state) => $emit('expand', state)"
/>
<div
v-show="enabled && !expanded"
class="scroll-button scroll-left noselect"
:class="{ 'scroll-start': scrollAtStart }"
@click="scroll('left')"
><Icon icon="arrow-left3" /></div>
<slot />
<div
v-show="enabled && !expanded"
class="scroll-button scroll-right noselect"
:class="{ 'scroll-end': scrollAtEnd }"
@click="scroll('right')"
><Icon icon="arrow-right3" /></div>
</div>
<Expand
v-if="expanded || (expandable && scrollable)"
:expanded="expanded"
class="expand-dark"
@expand="(state) => $emit('expand', state)"
/>
<Expand
v-if="expanded || (expandable && scrollable)"
:expanded="expanded"
class="expand-light"
@expand="(state) => $emit('expand', state)"
/>
</div>
</template>
<script>
import Expand from '../expand/expand.vue';
function updateScroll() {
this.scrollable = this.target.scrollWidth > this.target.clientWidth;
this.scrollAtStart = this.target.scrollLeft === 0;
this.scrollAtEnd = this.target.scrollWidth - this.target.clientWidth === this.target.scrollLeft;
}
function scroll(direction) {
if (direction === 'right') {
this.target.scrollLeft = this.target.scrollLeft + this.target.clientWidth - 100;
}
if (direction === 'left') {
this.target.scrollLeft = this.target.scrollLeft - this.target.clientWidth + 100;
}
}
function mounted() {
this.target = this.$slots.default[0].elm;
this.target.addEventListener('scroll', () => {
this.updateScroll();
});
window.addEventListener('resize', this.updateScroll);
this.updateScroll();
setTimeout(() => this.updateScroll(), 500); // allow CSS to calculate
}
function beforeDestroy() {
this.target.removeEventListener('scroll', this.updateScroll);
window.removeEventListener('resize', this.updateScroll);
}
function updated() {
this.updateScroll();
}
export default {
components: {
Expand,
},
props: {
enabled: {
type: Boolean,
default: true,
},
expandable: {
type: Boolean,
default: true,
},
expanded: {
type: Boolean,
default: false,
},
},
data() {
return {
target: null,
scrollable: true,
scrollAtStart: true,
scrollAtEnd: false,
};
},
mounted,
updated,
beforeDestroy,
methods: {
scroll,
updateScroll,
},
};
</script>
<style lang="scss" scoped>
@import 'theme';
.scroll {
background: var(--profile);
&.expanded {
padding: 0;
.scroll {
display: none;
}
}
}
.scrollable {
position: relative;
}
.expand-light {
display: none;
}
.scroll-light {
background: var(--background-dim);
.scroll-button {
.icon {
fill: var(--darken);
}
&.scroll-start .icon,
&.scroll-end .icon {
fill: var(--darken-weak);
}
&:hover:not(.scroll-start):not(.scroll-end) .icon {
fill: var(--text-dark);
}
}
.scroll-left {
background: linear-gradient(to right, var(--background-dim) 50%, transparent);
}
.scroll-right {
background: linear-gradient(to left, var(--background-dim) 50%, transparent);
}
.expand-dark {
display: none;
}
.expand-light {
display: block;
}
}
.scroll-dark {
background: var(--profile);
.scroll-button {
.icon {
fill: var(--lighten);
}
&.scroll-start .icon,
&.scroll-end .icon {
fill: var(--darken-weak);
}
&:hover:not(.scroll-start):not(.scroll-end) .icon {
fill: var(--text-light);
}
}
.scroll-left {
background: linear-gradient(to right, var(--profile) 50%, transparent);
}
.scroll-right {
background: linear-gradient(to left, var(--profile) 50%, transparent);
}
.expand-light {
display: none;
}
.expand-dark {
display: block;
}
}
.scroll-button {
height: 100%;
display: flex;
align-items: center;
box-sizing: border-box;
position: absolute;
top: 0;
bottom: 0;
z-index: 10;
&.scroll-start,
&.scroll-end {
/* use over v-show so button stays visible while still hovered */
display: none;
}
&:hover {
display: flex;
cursor: pointer;
}
}
.scroll-left {
left: 0;
padding: 1rem 2rem 1rem 1rem;
}
.scroll-right {
right: 0;
padding: 1rem 1rem 1rem 2rem;
}
@media(max-width: $breakpoint) {
.scroll-button {
display: none;
}
}
</style>

View File

@ -30,13 +30,14 @@
<Releases
v-if="!loading && releases.length > 0"
class="embedded"
:releases="releases"
/>
</div>
</template>
<script>
import Actor from '../tile/actor.vue';
import Actor from '../actors/tile.vue';
import Releases from '../releases/releases.vue';
async function search() {
@ -93,6 +94,10 @@ export default {
<style lang="scss" scoped>
@import 'theme';
.content-inner {
padding: 1rem;
}
.summary {
display: block;
margin: 0 0 1rem 0;

View File

@ -11,7 +11,7 @@
/>
<router-link
to="/home"
to="/updates"
class="logo-link"
@click.native="toggleSidebar(false)"
>
@ -29,7 +29,7 @@
<li class="nav-item">
<router-link
v-slot="{ href, isActive, navigate }"
to="/home"
to="/updates"
@click.native="toggleSidebar(false)"
>
<a
@ -67,7 +67,7 @@
:href="href"
:class="{ active: isActive }"
@click="navigate"
>Sites</a>
>Channels</a>
</router-link>
</li>

View File

@ -23,7 +23,7 @@
</template>
<script>
import Tag from '../tile/tag.vue';
import Tag from './tile.vue';
function sfw() {
return this.$store.state.ui.sfw;
@ -45,6 +45,7 @@ async function mounted() {
'double-penetration',
'facial',
'creampie',
'squirting',
],
appearance: [
'asian',
@ -100,6 +101,7 @@ async function mounted() {
],
misc: [
'gaping',
'squirting',
'oil',
],
};
@ -145,7 +147,7 @@ export default {
.tiles {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(23rem, .33fr));
grid-gap: 1rem;
grid-gap: .5rem;
margin: 0 0 1.5rem 0;
}
@ -156,13 +158,19 @@ export default {
@media(max-width: $breakpoint3) {
.tiles {
grid-template-columns: repeat(auto-fill, minmax(21rem, .5fr));
grid-template-columns: repeat(auto-fill, minmax(20rem, .5fr));
}
}
@media(max-width: $breakpoint) {
.tiles {
grid-template-columns: repeat(auto-fill, minmax(20rem, 1fr));
grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
}
}
@media(max-width: $breakpoint0) {
.tiles {
grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr));
}
}
</style>

View File

@ -1,11 +1,9 @@
<template>
<router-link
:to="{ name: 'tag', params: { tagSlug: tag.slug } }"
:to="{ name: 'tag', params: { tagSlug: tag.slug, range: 'latest' } }"
:title="tag.name"
class="tile"
>
<span class="title">{{ tag.name }}</span>
<template v-if="tag.poster">
<img
v-if="!lazy && !sfw"
@ -41,6 +39,8 @@
class="poster"
>
</template>
<span class="title">{{ tag.name }}</span>
</router-link>
</template>
@ -71,20 +71,19 @@ export default {
.tile {
color: var(--text-light);
background: var(--profile);
display: flex;
flex-direction: column;
justify-content: flex-end;
box-sizing: border-box;
position: relative;
text-align: center;
text-decoration: none;
text-decoration: none;
box-shadow: 0 0 3px var(--darken-weak);
overflow: hidden;
}
.poster {
width: 100%;
height: 17rem;
height: 100%;
object-fit: cover;
object-position: 50% 100%;
}
@ -97,9 +96,25 @@ export default {
position: absolute;
bottom: 0;
background: var(--darken);
overflow: hidden;
white-space: nowrap;
font-size: 1rem;
font-weight: bold;
text-transform: capitalize;
text-shadow: 0 0 3px var(--darken-strong);
text-overflow: ellipsis;
}
@media(max-width: $breakpoint) {
.tile {
position: initial;
}
.title {
position: initial;
color: var(--text);
background: var(--background);
text-shadow: none;
}
}
</style>

View File

@ -1,74 +0,0 @@
<template>
<a
:href="`/network/${network.slug}`"
:title="network.name"
class="tile"
:class="{ sfw }"
>
<img
:src="`/img/logos/${network.slug}/thumbs/network.png`"
:alt="network.name"
class="logo"
>
</a>
</template>
<script>
function sfw() {
return this.$store.state.ui.sfw;
}
export default {
props: {
network: {
type: Object,
default: null,
},
},
computed: {
sfw,
},
};
</script>
<style lang="scss" scoped>
@import 'theme';
.tile {
height: 6rem;
background: var(--profile);
display: flex;
flex-shrink: 0;
flex-direction: column;
align-items: center;
box-sizing: border-box;
padding: .5rem 1rem;
box-shadow: 0 0 3px rgba(0, 0, 0, .25);
text-align: center;
}
.link {
text-decoration: none;
}
.logo {
width: 100%;
height: 100%;
color: $text;
display: flex;
align-items: center;
justify-content: center;
object-fit: contain;
font-size: 1rem;
font-weight: bold;
/* filter: $logo-highlight; */
}
.title {
color: var(--text);
height: 100%;
display: flex;
align-items: center;
margin: 0;
}
</style>

View File

@ -1,67 +0,0 @@
<template>
<a
:href="`/site/${site.slug}`"
:title="site.name"
class="tile"
>
<img
:src="`/img/logos/${site.network.slug}/thumbs/${site.slug}.png`"
:alt="site.name"
class="logo"
>
</a>
</template>
<script>
export default {
props: {
site: {
type: Object,
default: null,
},
},
};
</script>
<style lang="scss" scoped>
@import 'theme';
.tile {
height: 6rem;
background: $tile;
display: flex;
flex-shrink: 0;
flex-direction: column;
align-items: center;
box-sizing: border-box;
padding: .5rem 1rem;
border-radius: .25rem;
box-shadow: 0 0 3px rgba(0, 0, 0, .25);
text-align: center;
}
.link {
text-decoration: none;
}
.logo {
width: 100%;
height: 5rem;
color: $text-contrast;
display: flex;
align-items: center;
justify-content: center;
object-fit: contain;
font-size: 1rem;
font-weight: bold;
filter: $logo-highlight;
}
.title {
color: $text;
height: 100%;
display: flex;
align-items: center;
margin: 0;
}
</style>

View File

@ -51,7 +51,7 @@ $female: #f0a;
--lighten-strong: rgba(255, 255, 255, .7);
--lighten-extreme: rgba(255, 255, 255, .9);
--lighten-weak: rgba(255, 255, 255, .2);
--lighten-hint: rgba(255, 255, 255, .1);
--lighten-hint: rgba(255, 255, 255, .05);
--logo-shadow: drop-shadow(1px 0 0 $shadow-weak) drop-shadow(-1px 0 0 $shadow-weak) drop-shadow(0 1px 0 $shadow-weak) drop-shadow(0 -1px 0 $shadow-weak);
--logo-highlight: drop-shadow(0 0 1px $highlight);

View File

@ -9,6 +9,10 @@
border-radius: 16px;
}
&:not(.popover) .tooltip-inner {
padding: .25rem .5rem;
}
.tooltip-arrow {
width: 0;
height: 0;

View File

@ -37,56 +37,3 @@ body {
fill: $primary;
}
}
.expand {
display: flex;
justify-content: center;
align-items: center;
padding: .5rem .25rem;
font-weight: bold;
font-size: .9rem;
cursor: pointer;
.icon {
fill: $shadow;
}
&:hover {
background: $shadow-hint;
.icon {
fill: $shadow-strong;
}
}
}
.expand-sidebar:hover {
background: $shadow-hint;
}
.expand-header {
display: none;
&:hover {
background: $shadow-hint;
}
}
.collapse-header {
width: 100%;
justify-content: center;
align-items: center;
padding: 0;
background: $profile;
.icon {
width: 100%;
fill: $highlight;
padding: .5rem 0;
}
&:hover .icon {
background: $highlight-hint;
fill: $text-contrast;
}
}

View File

@ -0,0 +1,5 @@
<!-- Generated by IcoMoon.io -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
<title>arrow-left2</title>
<path d="M10.5 16l2-2-6-6 6-6-2-2-8 8 8 8z"></path>
</svg>

After

Width:  |  Height:  |  Size: 217 B

View File

@ -0,0 +1,5 @@
<!-- Generated by IcoMoon.io -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
<title>arrow-right2</title>
<path d="M5.5 0l-2 2 6 6-6 6 2 2 8-8-8-8z"></path>
</svg>

After

Width:  |  Height:  |  Size: 217 B

View File

@ -1,3 +1,4 @@
import config from 'config';
import { graphql, get } from '../api';
import {
releaseFields,
@ -8,7 +9,7 @@ import {
import { curateActor, curateRelease } from '../curate';
import getDateRange from '../get-date-range';
function initActorActions(store, _router) {
function initActorActions(store, router) {
async function fetchActorById({ _commit }, {
actorId,
limit = 10,
@ -16,6 +17,7 @@ function initActorActions(store, _router) {
range = 'latest',
}) {
const { before, after, orderBy } = getDateRange(range);
const includeTags = router.currentRoute.query.tags ? router.currentRoute.query.tags.split(',') : [];
const { actor, connection: { releases, totalCount } } = await graphql(`
query Actor(
@ -25,7 +27,9 @@ function initActorActions(store, _router) {
$after:Date = "1900-01-01",
$before:Date = "2100-01-01",
$orderBy:[ReleasesActorsOrderBy!]
$exclude: [String!]
$selectableTags: [String],
$excludeTags: [String!]
${includeTags.length > 0 ? '$includeTags: [String!]' : ''}
) {
actor(id: $actorId) {
id
@ -57,10 +61,17 @@ function initActorActions(store, _router) {
description
createdAt
updatedAt
network {
entity {
id
name
slug
type
parent {
id
name
slug
type
}
}
avatar: avatarMedia {
id
@ -80,15 +91,17 @@ function initActorActions(store, _router) {
profiles: actorsProfiles {
description
descriptionHash
network {
entity {
id
slug
name
}
site {
id
slug
name
type
parent {
id
name
slug
type
}
}
avatar: avatarMedia {
id
@ -130,22 +143,28 @@ function initActorActions(store, _router) {
name
slug
}
tags(selectableTags: $selectableTags) {
id
name
slug
priority
}
releasesConnection: releasesActorsConnection(
filter: {
release: {
date: {
lessThan: $before,
greaterThan: $after,
},
releasesTagsConnection: {
none: {
tag: {
slug: {
in: $exclude
}
}
}
}
releasesTagsConnection: {
none: {
tag: {
slug: {
in: $excludeTags
}
}
}
}
}
}
first: $limit
@ -162,12 +181,12 @@ function initActorActions(store, _router) {
${releaseActorsFragment}
${releaseTagsFragment}
${releasePosterFragment}
site {
entity {
id
name
slug
url
network {
parent {
id
name
slug
@ -182,10 +201,10 @@ function initActorActions(store, _router) {
first: $limit
offset: $offset
orderBy: $orderBy
condition: {
actorId: $actorId
}
filter: {
actorId: {
equalTo: $actorId
}
or: [
{
release: {
@ -196,6 +215,17 @@ function initActorActions(store, _router) {
}
},
]
${includeTags.length > 0 ? `release: {
releasesTagsConnection: {
some: {
tag: {
slug: {
in: $includeTags
}
}
}
}
}` : ''}
}
) {
releases: nodes {
@ -212,8 +242,10 @@ function initActorActions(store, _router) {
offset: Math.max(0, (pageNumber - 1)) * limit,
after,
before,
selectableTags: config.selectableTags,
orderBy: orderBy === 'DATE_DESC' ? 'RELEASE_BY_RELEASE_ID__DATE_DESC' : 'RELEASE_BY_RELEASE_ID__DATE_ASC',
exclude: store.state.ui.filter,
excludeTags: store.state.ui.filter,
includeTags,
});
return {
@ -265,10 +297,17 @@ function initActorActions(store, _router) {
dateOfBirth
dateOfDeath
gender
network {
entity {
id
name
slug
type
parent {
id
name
slug
type
}
}
avatar: avatarMedia {
id

View File

@ -2,9 +2,28 @@ export default {
api: {
url: `${window.location.origin}/api`,
},
filename: {
pattern: '{site.name} - {title} ({actors.$n.name}, {date} {shootId})',
separator: ', ',
date: 'DD-MM-YYYY',
},
selectableTags: [
'airtight',
'anal',
'blowjob',
'creampie',
'deepthroat',
'double-anal',
'double-penetration',
'double-vaginal',
'facefucking',
'facial',
'fisting',
'gaping',
'gangbang',
'interracial',
'lesbian',
'mff',
'mfm',
'orgy',
'pov',
'solo',
'squirting',
'swallowing',
],
};

View File

@ -31,16 +31,15 @@ function curateActor(actor, release) {
if (actor.profiles && actor.profiles.length > 0) {
const photos = actor.profiles
.map(profile => profile.avatar)
.filter(avatar => avatar && (!curatedActor.avatar || avatar.hash !== curatedActor.avatar.hash));
.map(profile => ({ entity: profile.entity, ...profile.avatar }))
.filter(avatar => avatar.id && (!curatedActor.avatar || avatar.hash !== curatedActor.avatar.hash));
const descriptions = actor.profiles.reduce((acc, profile) => ({
...acc,
...(profile.description && {
[profile.descriptionHash]: {
text: profile.description,
network: profile.network,
site: profile.site,
entity: profile.entity,
},
}),
}), {});
@ -68,7 +67,6 @@ function curateRelease(release) {
tags: release.tags ? release.tags.map(({ tag }) => tag) : [],
};
if (release.site) curatedRelease.network = release.site.network;
if (release.scenes) curatedRelease.scenes = release.scenes.map(({ scene }) => curateRelease(scene));
if (release.movies) curatedRelease.movies = release.movies.map(({ movie }) => curateRelease(movie));
if (release.photos) curatedRelease.photos = release.photos.map(({ media }) => media);
@ -100,6 +98,7 @@ function curateSite(site, network) {
function curateNetwork(network, releases) {
const curatedNetwork = {
...network,
id: network.id,
name: network.name,
slug: network.slug,
@ -116,6 +115,26 @@ function curateNetwork(network, releases) {
return curatedNetwork;
}
function curateEntity(entity, parent, releases) {
const curatedEntity = {
...entity,
children: [],
};
if (entity.children) {
if (entity.children.nodes) {
curatedEntity.children = entity.children.nodes.map(childEntity => curateEntity(childEntity, curatedEntity));
}
curatedEntity.childrenTotal = entity.children.totalCount;
}
if (entity.parent || parent) curatedEntity.parent = curateEntity(entity.parent || parent);
if (releases) curatedEntity.releases = releases.map(release => curateRelease(release));
return curatedEntity;
}
function curateTag(tag) {
const curatedTag = {
...tag,
@ -130,6 +149,7 @@ function curateTag(tag) {
export {
curateActor,
curateEntity,
curateRelease,
curateSite,
curateNetwork,

View File

@ -0,0 +1,219 @@
import { graphql } from '../api';
// import { sitesFragment, releaseFields } from '../fragments';
import { releaseFields } from '../fragments';
import { curateEntity } from '../curate';
import getDateRange from '../get-date-range';
function initEntitiesActions(store, _router) {
async function fetchEntityBySlugAndType({ _commit }, {
entitySlug,
entityType,
limit = 10,
pageNumber = 1,
range = 'latest',
}) {
const { before, after, orderBy } = getDateRange(range);
const { entity, connection: { releases, totalCount } } = await graphql(`
query Entity(
$entitySlug: String!
$entityType: String! = "channel"
$limit: Int = 10,
$offset: Int = 0,
$after: Date = "1900-01-01",
$before: Date = "2100-01-01",
$afterTime: Datetime = "1900-01-01",
$beforeTime: Datetime = "2100-01-01",
$orderBy: [ReleasesOrderBy!]
$exclude: [String!]
) {
entity: entityBySlugAndType(slug: $entitySlug, type: $entityType) {
id
name
slug
url
hasLogo
children: childEntitiesConnection(
orderBy: [PRIORITY_DESC, NAME_ASC],
) {
nodes {
id
name
slug
url
type
priority
hasLogo
}
}
parent {
id
name
slug
type
url
hasLogo
}
}
connection: releasesConnection(
first: $limit
offset: $offset
orderBy: $orderBy
filter: {
and: [
{
or: [
{
entity: {
or: [
{ slug: { equalTo: $entitySlug } },
{ parent: { slug: { equalTo: $entitySlug } } },
{ parent: { parent: { slug: { equalTo: $entitySlug } } } }
]
}
}
{
studio: {
slug: { equalTo: $entitySlug },
}
}
]
}
{
or: [
{
date: {
lessThan: $before,
greaterThan: $after
}
},
{
date: {
isNull: true
},
createdAt: {
lessThan: $beforeTime,
greaterThan: $afterTime,
}
}
]
}
]
releasesTagsConnection: {
none: {
tag: {
slug: {
in: $exclude
}
}
}
}
}
) {
releases: nodes {
${releaseFields}
}
totalCount
}
}
`, {
entitySlug,
entityType,
limit,
offset: Math.max(0, (pageNumber - 1)) * limit,
after,
before,
orderBy,
afterTime: store.getters.after,
beforeTime: store.getters.before,
exclude: store.state.ui.filter,
});
return {
entity: curateEntity(entity, null, releases),
totalCount,
};
}
async function fetchEntities({ _commit }, { type, entitySlugs }) {
const { entities } = await graphql(`
query Entities(
$type: String! = "network"
$entitySlugs: [String!] = []
) {
entities(
orderBy: NAME_ASC
filter: {
or: [
{
type: {
equalTo: $type
}
}
{
slug: {
in: $entitySlugs
}
}
]
}
) {
id
name
slug
type
url
hasLogo
children: childEntitiesConnection {
totalCount
}
}
}
`, {
type,
entitySlugs,
});
return entities.map(entity => curateEntity(entity));
}
async function searchEntities({ _commit }, { query, limit = 20 }) {
const { entities } = await graphql(`
query SearchEntities(
$query: String!
$limit:Int = 20,
) {
entities: searchEntities(
search: $query,
first: $limit
) {
name
slug
type
url
hasLogo
parent {
name
slug
type
url
hasLogo
}
}
}
`, {
query,
limit,
});
return entities.map(entity => curateEntity(entity));
}
return {
fetchEntityBySlugAndType,
fetchEntities,
searchEntities,
};
}
export default initEntitiesActions;

View File

@ -0,0 +1,13 @@
import state from './state';
import mutations from './mutations';
import actions from './actions';
function initEntitiesStore(store, router) {
return {
state,
mutations,
actions: actions(store, router),
};
}
export default initEntitiesStore;

View File

@ -0,0 +1 @@
export default {};

View File

@ -0,0 +1 @@
export default {};

View File

@ -1,31 +1,33 @@
const siteFragment = `
site {
entity {
id
name
slug
url
independent
network {
type
parent {
id
name
slug
url
type
}
}
`;
const sitesFragment = `
sites {
entities {
id
name
slug
url
independent
network {
type
parent {
id
name
slug
url
type
}
}
`;
@ -49,12 +51,12 @@ const actorFields = `
lazy
}
}
network {
network: entity {
id
name
slug
}
originCountry: countryByBirthCountryAlpha2 {
birthCountry: countryByBirthCountryAlpha2 {
alpha2
name
alias

View File

@ -1,5 +1,6 @@
import { graphql } from '../api';
import { sitesFragment, releaseFields } from '../fragments';
// import { sitesFragment, releaseFields } from '../fragments';
import { releaseFields } from '../fragments';
import { curateNetwork } from '../curate';
import getDateRange from '../get-date-range';
@ -24,50 +25,26 @@ function initNetworksActions(store, _router) {
$orderBy: [ReleasesOrderBy!]
$exclude: [String!]
) {
network: networkBySlug(slug: $networkSlug) {
network: entityBySlugAndType(slug: $networkSlug, type: "network") {
id
name
slug
url
networks: childNetworks(
orderBy: NAME_ASC,
) {
id
name
slug
url
}
sites(
sites: childEntities(
orderBy: [PRIORITY_DESC, NAME_ASC],
filter: {
show: {
equalTo: true,
},
},
) {
id
name
slug
url
independent
type
priority
network {
id
name
slug
url
}
}
studios {
id
name
slug
url
}
parent {
id
name
slug
type
url
}
}
@ -76,10 +53,10 @@ function initNetworksActions(store, _router) {
offset: $offset
orderBy: $orderBy
filter: {
site: {
entity: {
or: [
{ network: { slug: { equalTo: $networkSlug } } },
{ network: { parent: { slug: { equalTo: $networkSlug } } } }
{ parent: { slug: { equalTo: $networkSlug } } },
{ parent: { parent: { slug: { equalTo: $networkSlug } } } }
]
}
or: [
@ -137,12 +114,19 @@ function initNetworksActions(store, _router) {
async function fetchNetworks({ _commit }) {
const { networks } = await graphql(`
query Networks {
networks(orderBy: NAME_ASC) {
networks: entities(
orderBy: NAME_ASC
filter: {
type: {
equalTo: "network"
}
}
) {
id
name
slug
type
url
${sitesFragment}
}
}
`);

View File

@ -4,7 +4,7 @@ import VueRouter from 'vue-router';
import Home from '../components/home/home.vue';
import Release from '../components/releases/release.vue';
import Site from '../components/sites/site.vue';
import Network from '../components/networks/network.vue';
import Entity from '../components/entities/entity.vue';
import Networks from '../components/networks/networks.vue';
import Actor from '../components/actors/actor.vue';
import Actors from '../components/actors/actors.vue';
@ -22,7 +22,7 @@ const routes = [
name: 'updates',
params: {
range: 'latest',
tags: 'all-tags',
tags: 'all',
pageNumber: 1,
},
},
@ -33,13 +33,12 @@ const routes = [
name: 'updates',
params: {
range: 'latest',
tags: 'all-tags',
pageNumber: 1,
},
},
},
{
path: '/updates/:tags/:range/:pageNumber',
path: '/updates/:range/:pageNumber',
component: Home,
name: 'updates',
},
@ -61,53 +60,63 @@ const routes = [
params: {
...from.params,
range: 'latest',
tags: 'all-tags',
pageNumber: 1,
},
}),
},
{
path: '/actor/:actorId/:actorSlug/:tags/:range/:pageNumber',
path: '/actor/:actorId/:actorSlug/:range/:pageNumber',
component: Actor,
name: 'actorRange',
},
{
path: '/site/:siteSlug',
path: '/channel/:entitySlug',
component: Site,
name: 'site',
redirect: from => ({
name: 'siteRange',
name: 'channel',
params: {
...from.params,
range: 'latest',
tags: 'all-tags',
pageNumber: 1,
},
}),
},
{
path: '/site/:siteSlug/:tags/:range/:pageNumber',
component: Site,
name: 'siteRange',
path: '/channel/:entitySlug/:range/:pageNumber',
component: Entity,
name: 'channel',
},
{
path: '/network/:networkSlug',
component: Network,
path: '/network/:entitySlug',
redirect: from => ({
name: 'network',
params: {
...from.params,
range: 'latest',
pageNumber: 1,
},
}),
},
{
path: '/network/:entitySlug/:range/:pageNumber',
component: Entity,
name: 'network',
},
{
path: '/studio/:entitySlug',
redirect: from => ({
name: 'networkRange',
name: 'studio',
params: {
...from.params,
range: 'latest',
tags: 'all-tags',
pageNumber: 1,
},
}),
},
{
path: '/network/:networkSlug/:tags/:range/:pageNumber',
component: Network,
name: 'networkRange',
path: '/studio/:entitySlug/:range/:pageNumber',
component: Entity,
name: 'studio',
},
{
path: '/tag/:tagSlug',
@ -116,12 +125,11 @@ const routes = [
params: {
...from.params,
range: 'latest',
tags: 'all-tags',
},
}),
},
{
path: '/tag/:tagSlug/:tags/:range',
path: '/tag/:tagSlug/:range',
component: Tag,
name: 'tag',
},
@ -133,7 +141,7 @@ const routes = [
...from.params,
gender: 'female',
letter: 'all',
tags: 'all-tags',
tags: 'all',
range: 'latest',
pageNumber: 1,
},

View File

@ -22,18 +22,18 @@ function initSitesActions(store, _router) {
$orderBy:[ReleasesOrderBy!]
$exclude: [String!]
) {
site: siteBySlug(slug: $siteSlug) {
site: entityBySlugAndType(slug: $siteSlug, type: 2) {
name
slug
url
tags: sitesTags {
tags: entitiesTags {
tag {
id
slug
name
}
}
network {
network: parent {
id
name
slug
@ -70,7 +70,7 @@ function initSitesActions(store, _router) {
offset: $offset
orderBy: $orderBy
filter: {
site: {
entity: {
slug: {
equalTo: $siteSlug
}
@ -122,7 +122,7 @@ function initSitesActions(store, _router) {
$after:Date = "1900-01-01",
$before:Date = "2100-01-01",
) {
site {
site: entity {
name
slug
url
@ -137,38 +137,9 @@ function initSitesActions(store, _router) {
return sites;
}
async function searchSites({ _commit }, { query, limit = 20 }) {
const { sites } = await graphql(`
query SearchSites(
$query: String!
$limit:Int = 20,
) {
sites: searchSites(
search: $query,
first: $limit
) {
name
slug
url
network {
name
slug
url
}
}
}
`, {
query,
limit,
});
return sites;
}
return {
fetchSiteBySlug,
fetchSites,
searchSites,
};
}

View File

@ -4,6 +4,7 @@ import Vuex from 'vuex';
import initUiStore from './ui/ui';
import initAuthStore from './auth/auth';
import initReleasesStore from './releases/releases';
import initEntitiesStore from './entities/entities';
import initSitesStore from './sites/sites';
import initNetworksStore from './networks/networks';
import initActorsStore from './actors/actors';
@ -18,6 +19,7 @@ function initStore(router) {
store.registerModule('auth', initAuthStore(store, router));
store.registerModule('releases', initReleasesStore(store, router));
store.registerModule('actors', initActorsStore(store, router));
store.registerModule('entities', initEntitiesStore(store, router));
store.registerModule('sites', initSitesStore(store, router));
store.registerModule('networks', initNetworksStore(store, router));
store.registerModule('tags', initTagsStore(store, router));

View File

@ -43,12 +43,12 @@ function initUiActions(_store, _router) {
url
type
isNew
site {
entity {
id
slug
name
url
network {
parent {
id
slug
name
@ -105,7 +105,7 @@ function initUiActions(_store, _router) {
dateOfBirth
dateOfDeath
gender
network {
entity {
id
name
slug
@ -124,7 +124,7 @@ function initUiActions(_store, _router) {
alias
}
}
network {
entity {
id
name
slug

View File

@ -14,54 +14,49 @@ module.exports = {
// include: [],
// exclude: [],
exclude: [
['21sextreme', [
// no longer updated
'mightymistress',
'dominatedgirls',
'homepornreality',
'peeandblow',
'cummingmatures',
'mandyiskinky',
'speculumplays',
'creampiereality',
]],
['aziani', [
'amberathome',
'marycarey',
'racqueldevonshire',
]],
// 21sextreme, no longer updated
'mightymistress',
'dominatedgirls',
'homepornreality',
'peeandblow',
'cummingmatures',
'mandyiskinky',
'speculumplays',
'creampiereality',
// aziani
'amberathome',
'marycarey',
'racqueldevonshire',
// boobpedia
'boobpedia',
['blowpass', ['sunlustxxx']],
['ddfnetwork', [
'fuckinhd',
'bustylover',
]],
['famedigital', [
'daringsex',
'lowartfilms',
]],
// blowpass
'sunlustxxx',
// ddfnetwork
'fuckinhd',
'bustylover',
// famedigital
'daringsex',
'lowartfilms',
// freeones
'freeones',
['pornpros', [
'milfhumiliation',
'humiliated',
'flexiblepositions',
'publicviolations',
'amateurviolations',
'squirtdisgrace',
'cumdisgrace',
'webcamhackers',
'collegeteens',
]],
['score', [
'bigboobbundle',
'milfbundle',
'pornmegaload',
'scorelandtv',
'scoretv',
]],
['mindgeek', [
'pornhub',
]],
// pornpros
'milfhumiliation',
'humiliated',
'flexiblepositions',
'publicviolations',
'amateurviolations',
'squirtdisgrace',
'cumdisgrace',
'webcamhackers',
'collegeteens',
// score
'bigboobbundle',
'milfbundle',
'pornmegaload',
'scorelandtv',
'scoretv',
// mindgeek
'pornhub',
],
profiles: [
[

15
docs/associated-tags.sql Normal file
View File

@ -0,0 +1,15 @@
SELECT tags.name FROM actors
LEFT JOIN releases_actors ON releases_actors.actor_id = actors.id
LEFT JOIN releases_tags ON releases_tags.release_id = releases_actors.release_id
LEFT JOIN tags ON tags.id = releases_tags.tag_id
WHERE actors.slug = 'vina-sky'
GROUP BY tags.id
ORDER BY tags.name;
SELECT tags.*
FROM releases_actors
LEFT JOIN releases_tags ON releases_tags.release_id = releases_actors.release_id
LEFT JOIN tags ON tags.id = releases_tags.tag_id
WHERE releases_actors.actor_id = actor.id
GROUP BY tags.id
ORDER BY tags.name;

View File

@ -97,6 +97,9 @@ exports.up = knex => Promise.resolve()
table.integer('priority', 2)
.defaultTo(0);
table.boolean('filter')
.defaultTo(false);
table.boolean('secondary')
.defaultTo(false);
@ -140,52 +143,36 @@ exports.up = knex => Promise.resolve()
table.unique(['tag_id', 'media_id']);
}))
.then(() => knex.schema.createTable('networks', (table) => {
.then(() => knex.schema.createTable('entities_types', (table) => {
table.text('type')
.primary();
}))
.then(() => { // eslint-disable-line arrow-body-style
// allow vim fold
return knex('entities_types').insert([
{ type: 'network' },
{ type: 'channel' },
{ type: 'studio' },
{ type: 'info' },
]);
})
.then(() => knex.schema.createTable('entities', (table) => {
table.increments('id', 12);
table.text('name');
table.text('alias');
table.text('url');
table.text('description');
table.json('parameters');
table.integer('parent_id', 12)
.references('id')
.inTable('networks');
table.text('slug', 32)
.unique();
table.datetime('created_at')
.defaultTo(knex.fn.now());
}))
.then(() => knex.schema.createTable('networks_social', (table) => {
table.increments('id', 16);
table.text('url');
table.text('platform');
table.integer('network_id', 12)
.notNullable()
.references('id')
.inTable('networks');
table.unique(['url', 'network_id']);
table.datetime('created_at')
.defaultTo(knex.fn.now());
}))
.then(() => knex.schema.createTable('sites', (table) => {
table.increments('id', 12);
table.integer('network_id', 12)
.notNullable()
.references('id')
.inTable('networks');
.inTable('entities');
table.text('name');
table.text('slug', 32)
.unique();
table.text('slug', 32);
table.text('type')
.notNullable()
.references('type')
.inTable('entities_types')
.defaultTo('channel');
table.unique(['slug', 'type']);
table.text('alias');
@ -196,58 +183,40 @@ exports.up = knex => Promise.resolve()
table.integer('priority', 3)
.defaultTo(0);
table.boolean('show')
table.boolean('has_logo')
.defaultTo(true);
table.datetime('created_at')
.defaultTo(knex.fn.now());
}))
.then(() => knex.schema.createTable('sites_tags', (table) => {
.then(() => knex.schema.createTable('entities_tags', (table) => {
table.integer('tag_id', 12)
.notNullable()
.references('id')
.inTable('tags');
table.integer('site_id', 12)
table.integer('entity_id', 12)
.notNullable()
.references('id')
.inTable('sites');
.inTable('entities');
table.boolean('inherit')
.defaultTo(false);
table.unique(['tag_id', 'site_id']);
table.unique(['tag_id', 'entity_id']);
}))
.then(() => knex.schema.createTable('sites_social', (table) => {
.then(() => knex.schema.createTable('entities_social', (table) => {
table.increments('id', 16);
table.text('url');
table.text('platform');
table.integer('site_id', 12)
table.integer('entity_id', 12)
.notNullable()
.references('id')
.inTable('sites');
.inTable('entities');
table.unique(['url', 'site_id']);
table.datetime('created_at')
.defaultTo(knex.fn.now());
}))
.then(() => knex.schema.createTable('studios', (table) => {
table.increments('id', 12);
table.integer('network_id', 12)
.notNullable()
.references('id')
.inTable('networks');
table.text('name');
table.text('url');
table.text('description');
table.text('slug', 32)
.unique();
table.unique(['url', 'entity_id']);
table.datetime('created_at')
.defaultTo(knex.fn.now());
@ -270,9 +239,9 @@ exports.up = knex => Promise.resolve()
table.text('real_name');
table.integer('network_id', 12)
table.integer('entity_id', 12)
.references('id')
.inTable('networks');
.inTable('entities');
table.integer('alias_for', 12)
.references('id')
@ -337,15 +306,11 @@ exports.up = knex => Promise.resolve()
.references('id')
.inTable('actors');
table.integer('network_id', 12)
table.integer('entity_id', 12)
.references('id')
.inTable('networks');
.inTable('entities');
table.integer('site_id', 12)
.references('id')
.inTable('sites');
table.unique(['actor_id', 'network_id', 'site_id']);
table.unique(['actor_id', 'entity_id']);
table.integer('priority', 4)
.defaultTo(1);
@ -624,24 +589,21 @@ exports.up = knex => Promise.resolve()
.then(() => knex.schema.createTable('releases', (table) => {
table.increments('id', 16);
table.integer('site_id', 12)
table.integer('entity_id', 12)
.references('id')
.inTable('sites');
table.integer('network_id', 12)
.references('id')
.inTable('networks');
.inTable('entities')
.notNullable();
table.integer('studio_id', 12)
.references('id')
.inTable('studios');
.inTable('entities');
table.text('type', 10)
.defaultTo('scene');
table.text('shoot_id');
table.text('entry_id');
table.unique(['site_id', 'network_id', 'entry_id', 'type']);
table.unique(['entity_id', 'entry_id', 'type']);
table.text('url', 1000);
table.text('title');
@ -797,18 +759,10 @@ exports.up = knex => Promise.resolve()
.references('id')
.inTable('releases');
}))
// SEARCH
.then(() => { // eslint-disable-line arrow-body-style
// allow vim fold
return knex.raw(`
ALTER TABLE releases
ADD CONSTRAINT ensure_site_or_network CHECK (site_id IS NOT NULL OR network_id IS NOT NULL);
ALTER TABLE releases_search
ADD COLUMN document tsvector;
CREATE UNIQUE INDEX unique_actor_slugs_network ON actors (slug, network_id);
CREATE UNIQUE INDEX unique_actor_slugs ON actors (slug, (network_id IS NULL));
CREATE TEXT SEARCH DICTIONARY traxxx_dict (
TEMPLATE = pg_catalog.simple,
stopwords = traxxx
@ -818,12 +772,28 @@ exports.up = knex => Promise.resolve()
COPY = english
);
ALTER TABLE releases_search
ADD COLUMN document tsvector;
ALTER TEXT SEARCH CONFIGURATION traxxx
ALTER MAPPING FOR word, numword, hword, numhword, hword_part, hword_numpart, asciiword, asciihword, hword_asciipart WITH traxxx_dict, simple, english_stem;
`);
})
// INDEXES
.then(() => { // eslint-disable-line arrow-body-style
// allow vim fold
return knex.raw(`
CREATE UNIQUE INDEX unique_actor_slugs_network ON actors (slug, entity_id);
CREATE UNIQUE INDEX unique_actor_slugs ON actors (slug, (entity_id IS NULL));
CREATE UNIQUE INDEX releases_search_unique ON releases_search (release_id);
CREATE INDEX releases_search_index ON releases_search USING GIN (document);
`);
})
// FUNCTIONS
.then(() => { // eslint-disable-line arrow-body-style
// allow vim fold
return knex.raw(`
CREATE FUNCTION search_releases(query text) RETURNS SETOF releases AS $$
SELECT * FROM releases WHERE releases.id IN (
SELECT release_id FROM releases_search AS search
@ -832,8 +802,8 @@ exports.up = knex => Promise.resolve()
);
$$ LANGUAGE SQL STABLE;
CREATE FUNCTION search_sites(search text) RETURNS SETOF sites AS $$
SELECT * FROM sites
CREATE FUNCTION search_entities(search text) RETURNS SETOF entities AS $$
SELECT * FROM entities
WHERE
name ILIKE ('%' || search || '%') OR
slug ILIKE ('%' || search || '%') OR
@ -846,10 +816,33 @@ exports.up = knex => Promise.resolve()
AND name ILIKE ('%' || search || '%')
$$ LANGUAGE SQL STABLE;
CREATE FUNCTION releases_is_new(release releases) RETURNS boolean AS $$
SELECT NOT EXISTS(SELECT true FROM batches WHERE batches.id = release.created_batch_id + 1 LIMIT 1);
$$ LANGUAGE sql STABLE;
CREATE FUNCTION actors_tags(actor actors, selectable_tags text[]) RETURNS SETOF tags AS $$
SELECT tags.*
FROM releases_actors
LEFT JOIN
releases_tags ON releases_tags.release_id = releases_actors.release_id
LEFT JOIN
tags ON tags.id = releases_tags.tag_id
WHERE
releases_actors.actor_id = actor.id
AND
CASE WHEN array_length(selectable_tags, 1) IS NOT NULL
THEN tags.slug = ANY(selectable_tags)
ELSE true
END
GROUP BY tags.id
ORDER BY tags.name;
$$ LANGUAGE SQL STABLE;
CREATE FUNCTION releases_is_new(release releases) RETURNS boolean AS $$
SELECT EXISTS(SELECT true WHERE (SELECT id FROM batches ORDER BY created_at DESC LIMIT 1) = release.created_batch_id);
$$ LANGUAGE sql STABLE;
`);
})
// VIEWS AND COMMENTS
.then(() => { // eslint-disable-line arrow-body-style
// allow vim fold
return knex.raw(`
CREATE VIEW movie_actors AS
SELECT releases_movies.movie_id, releases_actors.actor_id FROM releases_movies
LEFT JOIN releases ON releases.id = releases_movies.scene_id
@ -897,6 +890,8 @@ exports.down = (knex) => { // eslint-disable-line arrow-body-style
DROP TABLE IF EXISTS actors_piercings CASCADE;
DROP TABLE IF EXISTS body CASCADE;
DROP TABLE IF EXISTS entities_tags CASCADE;
DROP TABLE IF EXISTS entities_social CASCADE;
DROP TABLE IF EXISTS sites_tags CASCADE;
DROP TABLE IF EXISTS sites_social CASCADE;
DROP TABLE IF EXISTS networks_social CASCADE;
@ -915,10 +910,17 @@ exports.down = (knex) => { // eslint-disable-line arrow-body-style
DROP TABLE IF EXISTS countries CASCADE;
DROP TABLE IF EXISTS networks CASCADE;
DROP TABLE IF EXISTS entities_types CASCADE;
DROP TABLE IF EXISTS entities CASCADE;
DROP FUNCTION IF EXISTS search_sites;
DROP FUNCTION IF EXISTS search_entities;
DROP FUNCTION IF EXISTS search_actors;
DROP FUNCTION IF EXISTS get_random_sfw_media_id;
DROP FUNCTION IF EXISTS releases_is_new;
DROP FUNCTION IF EXISTS actors_tags;
DROP TEXT SEARCH CONFIGURATION IF EXISTS traxxx;
DROP TEXT SEARCH DICTIONARY IF EXISTS traxxx_dict;
`);

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.3 KiB

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.3 KiB

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.1 KiB

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

Some files were not shown because too many files have changed in this diff Show More