Integrated channel filter, partially restored actor bio.
This commit is contained in:
629
components/actors/bio.vue
Normal file
629
components/actors/bio.vue
Normal file
@@ -0,0 +1,629 @@
|
||||
<template>
|
||||
<div
|
||||
class="content-inner actor-inner"
|
||||
@scroll="events.emit('scroll', $event)"
|
||||
>
|
||||
<div
|
||||
class="profile"
|
||||
:class="{ expanded, 'with-avatar': !!actor.avatar }"
|
||||
>
|
||||
<a
|
||||
v-if="actor.avatar"
|
||||
:href="getMediaPath(actor.avatar)"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="avatar-link"
|
||||
>
|
||||
<img
|
||||
:src="getMediaPath(actor.avatar, 'thumbnail')"
|
||||
:title="actor.avatar.credit && `© ${actor.avatar.credit}`"
|
||||
class="avatar"
|
||||
>
|
||||
</a>
|
||||
|
||||
<ul class="bio nolist">
|
||||
<li
|
||||
v-if="actor.dateOfBirth"
|
||||
class="bio-item"
|
||||
>
|
||||
<dfn class="bio-label"><Icon icon="cake" />Date of birth</dfn>
|
||||
|
||||
<span class="birthdate">{{ formatDate(actor.dateOfBirth, 'MMMM d, yyyy') }}<span
|
||||
v-if="!actor.dateOfDeath"
|
||||
class="age"
|
||||
>{{ actor.ageFromBirth }}</span></span>
|
||||
</li>
|
||||
|
||||
<li
|
||||
v-else-if="actor.age && !actor.dateOfDeath"
|
||||
class="bio-item"
|
||||
>
|
||||
<dfn class="bio-label"><Icon icon="cake" />Age</dfn>
|
||||
|
||||
<span
|
||||
:title="'Exact date of birth or age unknown'"
|
||||
class="birthdate"
|
||||
>> {{ actor.age }}</span>
|
||||
</li>
|
||||
|
||||
<li
|
||||
v-if="actor.dateOfDeath"
|
||||
class="bio-item"
|
||||
>
|
||||
<dfn class="bio-label"><Icon icon="tombstone" />Date of death</dfn>
|
||||
|
||||
<span class="birthdate">{{ formatDate(actor.dateOfDeath, 'MMMM d, yyyy') }}<span
|
||||
v-if="actor.ageAtDeath"
|
||||
class="age"
|
||||
>{{ actor.ageAtDeath }}</span></span>
|
||||
</li>
|
||||
|
||||
<li
|
||||
v-if="actor.orientation"
|
||||
class="bio-item"
|
||||
>
|
||||
<dfn class="bio-label"><Icon icon="heart7" />Orientation</dfn>
|
||||
|
||||
<span class="orientation">{{ actor.orientation }}</span>
|
||||
</li>
|
||||
|
||||
<li
|
||||
v-if="actor.origin"
|
||||
class="bio-item birth"
|
||||
>
|
||||
<dfn class="bio-label"><Icon icon="home2" />Born in</dfn>
|
||||
|
||||
<span>
|
||||
<span
|
||||
v-if="actor.origin.city"
|
||||
class="city"
|
||||
>{{ actor.origin.city }}</span><span
|
||||
v-if="actor.origin.state && (!actor.origin.city || (actor.origin.country && actor.origin.country.alpha2 === 'US'))"
|
||||
class="state"
|
||||
>{{ actor.origin.city ? `, ${actor.origin.state}` : actor.origin.state }}</span>
|
||||
|
||||
<span
|
||||
v-if="actor.origin.country"
|
||||
class="country birthcountry"
|
||||
>
|
||||
<img
|
||||
class="flag"
|
||||
:src="`/img/flags/${actor.origin.country.alpha2.toLowerCase()}.svg`"
|
||||
>{{ actor.origin.country.alias || actor.origin.country.name }}
|
||||
</span>
|
||||
</span>
|
||||
</li>
|
||||
|
||||
<li
|
||||
v-if="actor.residence"
|
||||
class="bio-item residence hideable"
|
||||
>
|
||||
<dfn class="bio-label"><Icon icon="location" />Lives in</dfn>
|
||||
|
||||
<span>
|
||||
<span
|
||||
v-if="actor.residence.city"
|
||||
class="city"
|
||||
>{{ actor.residence.city }}</span><span
|
||||
v-if="actor.residence.state && actor.residence.country && actor.residence.country.alpha2 === 'US'"
|
||||
class="state"
|
||||
>{{ actor.residence.city ? `, ${actor.residence.state}` : actor.residence.state }}</span>
|
||||
|
||||
<span
|
||||
v-if="actor.residence.country"
|
||||
class="country"
|
||||
>
|
||||
<img
|
||||
class="flag"
|
||||
:src="`/img/flags/${actor.residence.country.alpha2.toLowerCase()}.svg`"
|
||||
>{{ actor.residence.country.alias || actor.residence.country.name }}
|
||||
</span>
|
||||
</span>
|
||||
</li>
|
||||
|
||||
<li
|
||||
v-if="actor.ethnicity"
|
||||
class="bio-item ethnicity hideable"
|
||||
>
|
||||
<dfn class="bio-label"><Icon icon="earth2" />Ethnicity</dfn>
|
||||
<span>{{ actor.ethnicity }}</span>
|
||||
</li>
|
||||
|
||||
<li
|
||||
v-if="actor.bust || actor.waist || actor.hip"
|
||||
title="bust-waist-hip"
|
||||
class="bio-item figure"
|
||||
>
|
||||
<dfn class="bio-label"><Icon icon="ruler" />Figure</dfn>
|
||||
<span class="bio-value">
|
||||
<Icon
|
||||
v-if="actor.naturalBoobs === false"
|
||||
:title="'Enhanced boobs'"
|
||||
icon="magic-wand2"
|
||||
class="enhanced"
|
||||
/>{{ actor.bust || '??' }}{{ actor.cup || '?' }}-{{ actor.waist || '??' }}-{{ actor.hip || '??' }}
|
||||
</span>
|
||||
</li>
|
||||
|
||||
<li
|
||||
v-if="actor.penisLength || actor.penisGirth || actor.circumcised"
|
||||
class="bio-item penis"
|
||||
>
|
||||
<dfn class="bio-label"><Icon icon="pencil-ruler" />Dick</dfn>
|
||||
|
||||
<span>
|
||||
<Icon
|
||||
v-if="actor.circumcised"
|
||||
:title="'Circumcised'"
|
||||
icon="page-break"
|
||||
class="circumcised"
|
||||
/>
|
||||
|
||||
<template v-if="actor.penisLengthMetric && actor.penisGirthMetric">
|
||||
<span>{{ actor.penisLengthMetric }} * {{ actor.penisGirthMetric }} cm</span>
|
||||
<span class="bio-segment">{{ actor.penisLengthImperial }}" * {{ actor.penisGirthImperial }}"</span>
|
||||
</template>
|
||||
|
||||
<template v-else-if="actor.penisLengthMetric">
|
||||
<span>{{ actor.penisLengthMetric }} cm</span>
|
||||
<span class="bio-segment">{{ actor.penisLengthImperial }}"</span>
|
||||
</template>
|
||||
</span>
|
||||
</li>
|
||||
|
||||
<li
|
||||
v-if="actor.height"
|
||||
class="bio-item height"
|
||||
>
|
||||
<dfn class="bio-label"><Icon icon="height" />Height</dfn>
|
||||
<span>
|
||||
<span class="height-metric">{{ actor.height.metric }} cm</span>
|
||||
<span class="height-imperial">{{ actor.height.imperial[0] }}' {{ actor.height.imperial[1] }}"</span>
|
||||
</span>
|
||||
</li>
|
||||
|
||||
<li
|
||||
v-if="actor.weight"
|
||||
class="bio-item weight hideable"
|
||||
>
|
||||
<dfn class="bio-label"><Icon icon="scale" />Weight</dfn>
|
||||
|
||||
<span>
|
||||
<span class="weight-metric">{{ actor.weight.metric }} kg</span>
|
||||
<span class="weight-imperial">{{ actor.weight.imperial }} lbs</span>
|
||||
</span>
|
||||
</li>
|
||||
|
||||
<li
|
||||
v-if="actor.eyes"
|
||||
class="bio-item eyes hideable"
|
||||
>
|
||||
<dfn class="bio-label"><Icon icon="eye" />Eyes</dfn>
|
||||
<span>{{ actor.eyes }}</span>
|
||||
</li>
|
||||
|
||||
<li
|
||||
v-if="actor.hairColor"
|
||||
class="bio-item hair hideable"
|
||||
>
|
||||
<dfn class="bio-label"><Icon icon="haircut" />Hair</dfn>
|
||||
<span><span v-if="actor.hairLength">{{ actor.hairLength }}, </span>{{ actor.hairColor }}</span>
|
||||
</li>
|
||||
|
||||
<li
|
||||
v-if="actor.hasTattoos"
|
||||
class="bio-item tattoos hideable"
|
||||
>
|
||||
<dfn class="bio-label"><Icon icon="lotus" />Tattoos</dfn>
|
||||
|
||||
<span
|
||||
v-if="actor.tattoos"
|
||||
:title="actor.tattoos"
|
||||
class="bio-value"
|
||||
>{{ actor.tattoos }}</span>
|
||||
<span v-else>Yes</span>
|
||||
</li>
|
||||
|
||||
<li
|
||||
v-if="actor.hasPiercings"
|
||||
class="bio-item piercings hideable"
|
||||
>
|
||||
<dfn class="bio-label"><Icon icon="trophy4" />Piercings</dfn>
|
||||
|
||||
<span
|
||||
v-if="actor.piercings"
|
||||
:title="actor.piercings"
|
||||
class="bio-value"
|
||||
>{{ actor.piercings }}</span>
|
||||
<span v-else>Yes</span>
|
||||
</li>
|
||||
|
||||
<li class="bio-item scraped hideable">Updated {{ formatDate(actor.updatedAt, 'yyyy-MM-dd hh:mm') }}, ID: {{ actor.id }}</li>
|
||||
</ul>
|
||||
|
||||
<div class="descriptions-container">
|
||||
<div
|
||||
v-if="actor.descriptions && actor.descriptions.length > 0"
|
||||
class="descriptions"
|
||||
>
|
||||
<p
|
||||
v-for="description in actor.descriptions"
|
||||
:key="`description-${description.entity.id}`"
|
||||
class="description"
|
||||
>
|
||||
{{ description.text }}
|
||||
<a :href="`/${description.entity.type}/${description.entity.slug}`">
|
||||
<img
|
||||
v-if="description.entity.type === 'network' || !description.entity.parent || description.entity.independent"
|
||||
:src="`/img/logos/${description.entity.slug}/thumbs/network.png`"
|
||||
class="description-logo"
|
||||
>
|
||||
|
||||
<img
|
||||
v-else
|
||||
:src="`/img/logos/${description.entity.parent.slug}/thumbs/${description.entity.slug}.png`"
|
||||
class="description-logo"
|
||||
>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
|
||||
import { getMediaPath } from '#/utils/media-path.js';
|
||||
import { formatDate } from '#/utils/format.js';
|
||||
|
||||
const expanded = ref(true);
|
||||
|
||||
defineProps({
|
||||
actor: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.header-gender .icon {
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style scoped>
|
||||
.profile {
|
||||
background: var(--grey-dark-40);
|
||||
color: var(--highlight-strong-30);
|
||||
width: 100%;
|
||||
max-height: 18rem;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-shrink: 0;
|
||||
|
||||
&.with-avatar {
|
||||
height: 18rem; /* profile overlaps avatar in chrome */
|
||||
}
|
||||
|
||||
.avatar-link {
|
||||
padding: 0 0 1rem 1rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
height: 100%;
|
||||
flex-shrink: 0;
|
||||
border: solid 3px var(--highlight-hint);
|
||||
margin: 0 .5rem 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
.bio {
|
||||
flex-grow: 1;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-wrap: wrap;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.bio-header {
|
||||
width: calc(50% - 2rem);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0 .5rem .5rem 0;
|
||||
margin: 0 0 0 1rem;
|
||||
}
|
||||
|
||||
.bio-item {
|
||||
width: calc(50% - 4rem);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
box-sizing: border-box;
|
||||
padding: .25rem 0 ;
|
||||
margin: 0 0 .25rem 1rem;
|
||||
line-height: 1.75;
|
||||
text-align: right;
|
||||
font-size: .9rem;
|
||||
font-weight: 600;
|
||||
overflow: hidden;
|
||||
|
||||
&:not(:last-of-type) {
|
||||
border-bottom: solid 1px var(--highlight-weak-40);
|
||||
}
|
||||
}
|
||||
|
||||
.bio-label,
|
||||
.bio-value {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.bio-label {
|
||||
color: var(--highlight-strong-20);
|
||||
margin: 0 1rem 0 0;
|
||||
flex-shrink: 0;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
|
||||
.icon {
|
||||
fill: var(--highlight);
|
||||
margin: -.25rem .5rem 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
.bio-value {
|
||||
margin: 0 0 0 2rem;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
|
||||
.icon {
|
||||
margin: -.25rem 0 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
.flag {
|
||||
height: 1rem;
|
||||
margin: .25rem .25rem 0 0;
|
||||
}
|
||||
|
||||
.bio-name {
|
||||
display: inline-block;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.birthdate {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.age {
|
||||
font-weight: bold;
|
||||
padding: 0 0 0 .5rem;
|
||||
border-left: solid 1px var(--highlight-weak);
|
||||
margin: 0 0 0 .5rem;
|
||||
}
|
||||
|
||||
.country {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.figure .bio-label .icon {
|
||||
margin: -.5rem .5rem 0 0;
|
||||
}
|
||||
|
||||
.height-imperial,
|
||||
.weight-imperial,
|
||||
.penis-girth-imperial,
|
||||
.penis-length-imperial,
|
||||
.bio-segment {
|
||||
padding: 0 0 0 .5rem;
|
||||
border-left: solid 1px var(--highlight-weak);
|
||||
margin: 0 0 0 .5rem;
|
||||
}
|
||||
|
||||
.enhanced.icon,
|
||||
.circumcised.icon {
|
||||
fill: var(--primary);
|
||||
padding: 0 .5rem;
|
||||
}
|
||||
|
||||
.enhanced.icon {
|
||||
transform: scaleX(-1);
|
||||
}
|
||||
|
||||
.ethnicity,
|
||||
.hair,
|
||||
.eyes,
|
||||
.orientation {
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.alias:not(:last-child)::after {
|
||||
content: ',\00a0';
|
||||
}
|
||||
|
||||
.scraped {
|
||||
color: var(--highlight-weak);
|
||||
font-size: .8rem;
|
||||
}
|
||||
|
||||
.descriptions-container {
|
||||
max-width: 30rem;
|
||||
max-height: 100%;
|
||||
position: relative;
|
||||
display: block;
|
||||
flex-grow: 1;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
width: 100%;
|
||||
height: 1.5rem;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
background: linear-gradient(transparent, 25%, var(--profile) 75%);
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
.descriptions {
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
scrollbar-width: none;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.description {
|
||||
margin: 0;
|
||||
padding: 0 1rem;
|
||||
border-left: solid 3px var(--highlight-hint);
|
||||
line-height: 1.5;
|
||||
font-size: .9rem;
|
||||
}
|
||||
|
||||
.description-logo {
|
||||
display: block;
|
||||
width: 12rem;
|
||||
max-height: 1.5rem;
|
||||
margin: .5rem 0 1.5rem 0;
|
||||
object-fit: contain;
|
||||
object-position: 0 50%;
|
||||
}
|
||||
|
||||
.actor-content {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
flex-direction: column;
|
||||
background: var(--background-soft);
|
||||
}
|
||||
|
||||
.heading {
|
||||
padding: 0;
|
||||
margin: 0 0 1rem 0;
|
||||
}
|
||||
|
||||
.profile-social {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.expand {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.scroll {
|
||||
background: var(--background-dim);
|
||||
border-bottom: solid 1px var(--shadow-hint);
|
||||
}
|
||||
|
||||
.stash.icon {
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
padding: 0 1rem;
|
||||
fill: var(--highlight);
|
||||
|
||||
&.stashed {
|
||||
fill: var(--primary);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
fill: var(--primary);
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@media(max-width: $breakpoint4) {
|
||||
.descriptions-container {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media(max-width: $breakpoint3) {
|
||||
.profile .avatar-link {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.actor-content {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
@media(max-width: $breakpoint) {
|
||||
.profile {
|
||||
height: auto;
|
||||
max-height: none;
|
||||
flex-direction: column;
|
||||
|
||||
&.with-avatar {
|
||||
height: auto;
|
||||
max-height: none;
|
||||
}
|
||||
|
||||
&:not(.expanded) .hideable {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.bio {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
padding: 0 1rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.bio-item {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.expanded .bio-value {
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.expand {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.actor-stash {
|
||||
margin: 0 .5rem 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media(max-width: $breakpoint0) {
|
||||
.header-social {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.expanded .profile-social {
|
||||
display: block;
|
||||
margin: 1rem 0 0 0;
|
||||
}
|
||||
|
||||
.header-name {
|
||||
flex-grow: 1;
|
||||
font-size: 1.3rem;
|
||||
padding: .5rem .5rem .5rem 1rem;
|
||||
}
|
||||
|
||||
.stash.icon {
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
padding: 0 1rem 0 .25rem;
|
||||
transform: translate(0, -.1rem);
|
||||
}
|
||||
}
|
||||
*/
|
||||
</style>
|
||||
@@ -32,13 +32,13 @@
|
||||
</span>
|
||||
|
||||
<span
|
||||
v-if="actor.birthCountry"
|
||||
:title="`Born in ${actor.birthCountry.name}`"
|
||||
v-if="actor.origin.country"
|
||||
:title="`Born in ${actor.origin.country.name}`"
|
||||
class="country"
|
||||
>
|
||||
{{ actor.birthCountry.alpha2 }}
|
||||
{{ actor.origin.country.alpha2 }}
|
||||
<img
|
||||
:src="`/img/flags/${actor.birthCountry.alpha2.toLowerCase()}.svg`"
|
||||
:src="`/img/flags/${actor.origin.country.alpha2.toLowerCase()}.svg`"
|
||||
class="flag"
|
||||
>
|
||||
</span>
|
||||
@@ -47,7 +47,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { formatDate } from '#/src/format.js';
|
||||
import { formatDate } from '#/utils/format.js';
|
||||
|
||||
import Gender from './gender.vue';
|
||||
|
||||
|
||||
@@ -78,7 +78,7 @@
|
||||
:title="actor.name"
|
||||
>{{ actor.name }}</span>
|
||||
|
||||
<span class="actor-details">
|
||||
<span class="filter-details">
|
||||
<div class="actor-gender">
|
||||
<Gender
|
||||
:gender="actor.gender"
|
||||
@@ -180,12 +180,6 @@ function selectGender() {
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.actor-details {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
margin-left: .5rem;
|
||||
}
|
||||
|
||||
.actor-gender {
|
||||
width: 1rem;
|
||||
display: flex;
|
||||
|
||||
@@ -13,61 +13,62 @@
|
||||
class="filter-sort order noselect"
|
||||
@click="order = 'count'"
|
||||
>
|
||||
<Icon
|
||||
icon="sort-alpha-asc"
|
||||
/>
|
||||
<Icon icon="sort-alpha-asc" />
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-show="order === 'count'"
|
||||
class="filter-sort order noselect"
|
||||
@click="order = 'priority'"
|
||||
@click="order = 'name'"
|
||||
>
|
||||
<Icon
|
||||
icon="sort-numeric-desc"
|
||||
/>
|
||||
<Icon icon="sort-numeric-desc" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ul
|
||||
class="filter-items nolist"
|
||||
>
|
||||
<template v-for="network in networks">
|
||||
<li
|
||||
v-for="(channel) in [network, ...(network.children || [])]"
|
||||
:key="`filter-channel-${channel.id}`"
|
||||
class="filter-item"
|
||||
:class="{ channel: !channel.isIndependent && channel.type !== 'network', selected: filters.channel?.id === channel.id }"
|
||||
@click="emit('update', 'channel', channel)"
|
||||
>
|
||||
<span class="filter-name">
|
||||
<span
|
||||
class="filter-text"
|
||||
:title="channel.name"
|
||||
<li
|
||||
v-for="entity in entities"
|
||||
:key="`filter-channel-${entity.id}`"
|
||||
class="filter-item"
|
||||
:class="{ channel: !entity.isIndependent && entity.type !== 'network', selected: filters.entity?.id === entity.id }"
|
||||
@click="emit('update', 'entity', entity)"
|
||||
>
|
||||
<span class="filter-name">
|
||||
<span
|
||||
class="filter-text"
|
||||
:title="entity.name"
|
||||
>
|
||||
<img
|
||||
v-if="entity.isIndependent || entity.type === 'network'"
|
||||
:src="`/logos/${entity.slug}/favicon_dark.png`"
|
||||
class="favicon"
|
||||
>
|
||||
<img
|
||||
v-if="channel.isIndependent || channel.type === 'network'"
|
||||
:src="`/logos/${channel.slug}/favicon_dark.png`"
|
||||
class="favicon"
|
||||
>
|
||||
|
||||
<Icon
|
||||
v-else
|
||||
icon="arrow-up4"
|
||||
/>
|
||||
<Icon
|
||||
v-else
|
||||
icon="arrow-up4"
|
||||
/>
|
||||
|
||||
{{ channel.name }}
|
||||
</span>
|
||||
|
||||
<span class="channel-details">
|
||||
<span
|
||||
v-if="channel.count"
|
||||
class="filter-count"
|
||||
>{{ channel.count }}</span>
|
||||
</span>
|
||||
{{ entity.name }}
|
||||
</span>
|
||||
</li>
|
||||
</template>
|
||||
|
||||
<span class="filter-details">
|
||||
<span
|
||||
v-if="entity.count"
|
||||
class="filter-count"
|
||||
>{{ entity.count }}</span>
|
||||
|
||||
<Icon
|
||||
v-if="filters.entity?.id === entity.id"
|
||||
icon="cross2"
|
||||
class="filter-remove"
|
||||
@click.native.stop="emit('update', 'entity', null)"
|
||||
/>
|
||||
</span>
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
@@ -95,7 +96,15 @@ const order = ref('name');
|
||||
const { pageProps } = inject('pageContext');
|
||||
const { channel: pageChannel } = pageProps;
|
||||
|
||||
const networks = computed(() => {
|
||||
function sort(channelA, channelB) {
|
||||
if (order.value === 'count') {
|
||||
return channelB.count - channelA.count;
|
||||
}
|
||||
|
||||
return channelA.name.localeCompare(channelB.name);
|
||||
}
|
||||
|
||||
const entities = computed(() => {
|
||||
const filteredChannels = props.channels.filter((channel) => channel.id !== pageChannel?.id
|
||||
&& (searchRegexp.value.test(channel.name)
|
||||
|| searchRegexp.value.test(channel.slug)
|
||||
@@ -119,7 +128,14 @@ const networks = computed(() => {
|
||||
acc[channel.parent.id].children.push(channel);
|
||||
|
||||
return acc;
|
||||
}, {}));
|
||||
}, {}))
|
||||
.map((network) => ({
|
||||
...network,
|
||||
children: network.children?.sort(sort),
|
||||
count: network.count || network.children?.reduce((acc, channel) => acc + channel.count, 0),
|
||||
}))
|
||||
.sort(sort)
|
||||
.flatMap((network) => [network, ...(network.children || [])]);
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -134,11 +150,12 @@ const networks = computed(() => {
|
||||
}
|
||||
|
||||
.filter-item.channel {
|
||||
.icon {
|
||||
.filter-text .icon {
|
||||
width: 1.5rem;
|
||||
height: 1rem;
|
||||
transform: rotate(-135deg);
|
||||
fill: var(--shadow-weak-30);
|
||||
overflow: hidden; /* prevent parent jumping on load */
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
<Icon
|
||||
v-if="selectedCountry === country.alpha2"
|
||||
icon="cross2"
|
||||
class="filter-remove"
|
||||
@click.native.stop="emit('country', null)"
|
||||
/>
|
||||
</li>
|
||||
@@ -55,15 +56,6 @@ const emit = defineEmits(['country']);
|
||||
padding: .25rem .25rem .25rem .5rem;
|
||||
}
|
||||
|
||||
.icon {
|
||||
padding: .25rem .6rem;
|
||||
fill: var(--shadow);
|
||||
|
||||
&:hover {
|
||||
fill: var(--shadow-strong-10);
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: var(--shadow-weak-30);
|
||||
cursor: pointer;
|
||||
|
||||
@@ -250,6 +250,21 @@ function toggleFilters(state) {
|
||||
}
|
||||
}
|
||||
|
||||
.filter-details {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
margin-left: .5rem;
|
||||
|
||||
.filter-remove.icon {
|
||||
padding: .25rem .6rem;
|
||||
fill: var(--shadow);
|
||||
|
||||
&:hover {
|
||||
fill: var(--alert);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.filter-count {
|
||||
width: 1.5rem;
|
||||
display: flex;
|
||||
|
||||
90
components/loading/ellipsis.vue
Executable file
90
components/loading/ellipsis.vue
Executable file
@@ -0,0 +1,90 @@
|
||||
<template>
|
||||
<div class="load-container">
|
||||
<div class="load-ellipsis">
|
||||
<div />
|
||||
<div />
|
||||
<div />
|
||||
<div />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.load-container {
|
||||
display: inline-flex;
|
||||
position: relative;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.load-ellipsis {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
width: 5rem;
|
||||
height: .75rem;
|
||||
}
|
||||
|
||||
.load-ellipsis div {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: .75rem;
|
||||
height: .75rem;
|
||||
border-radius: 50%;
|
||||
background: var(--primary);
|
||||
}
|
||||
|
||||
.load-ellipsis div:nth-child(1) {
|
||||
left: .5rem;
|
||||
animation: load-ellipsis1 0.5s infinite linear;
|
||||
}
|
||||
|
||||
.load-ellipsis div:nth-child(2) {
|
||||
left: .5rem;
|
||||
animation: load-ellipsis2 0.5s infinite linear;
|
||||
}
|
||||
|
||||
.load-ellipsis div:nth-child(3) {
|
||||
left: 2rem;
|
||||
animation: load-ellipsis3 0.5s infinite linear;
|
||||
}
|
||||
|
||||
.load-ellipsis div:nth-child(4) {
|
||||
left: 3.5rem;
|
||||
animation: load-ellipsis4 0.5s infinite linear;
|
||||
}
|
||||
|
||||
@keyframes load-ellipsis1 {
|
||||
0% {
|
||||
transform: scale(0);
|
||||
}
|
||||
100% {
|
||||
transform: scale(0.5);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes load-ellipsis2 {
|
||||
0% {
|
||||
transform: translate(0, 0) scale(0.5);
|
||||
}
|
||||
100% {
|
||||
transform: translate(1.5rem, 0) scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes load-ellipsis3 {
|
||||
0% {
|
||||
transform: translate(0, 0) scale(1);
|
||||
}
|
||||
100% {
|
||||
transform: translate(1.5rem, 0) scale(0.5);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes load-ellipsis4 {
|
||||
0% {
|
||||
transform: scale(0.5);
|
||||
}
|
||||
100% {
|
||||
transform: scale(0);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,6 +1,11 @@
|
||||
<template>
|
||||
<div class="page">
|
||||
<Filters v-if="showFilters">
|
||||
<div
|
||||
class="page"
|
||||
>
|
||||
<Filters
|
||||
v-if="showFilters"
|
||||
:class="{ loading }"
|
||||
>
|
||||
<TagsFilter
|
||||
:filters="filters"
|
||||
:tags="aggTags"
|
||||
@@ -20,7 +25,10 @@
|
||||
/>
|
||||
</Filters>
|
||||
|
||||
<div class="scenes-container">
|
||||
<div
|
||||
class="scenes-container"
|
||||
:class="{ loading }"
|
||||
>
|
||||
<div
|
||||
v-if="showMeta"
|
||||
class="scenes-header"
|
||||
@@ -48,7 +56,9 @@
|
||||
>New</Link>
|
||||
</nav>
|
||||
|
||||
<ul class="scenes nolist">
|
||||
<ul
|
||||
class="scenes nolist"
|
||||
>
|
||||
<li
|
||||
v-for="scene in scenes"
|
||||
:key="scene.id"
|
||||
@@ -59,6 +69,11 @@
|
||||
|
||||
<Pagination />
|
||||
</div>
|
||||
|
||||
<Ellipsis
|
||||
class="ellipsis"
|
||||
:class="{ loading }"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -75,8 +90,9 @@ import Filters from '#/components/filters/filters.vue';
|
||||
import ActorsFilter from '#/components/filters/actors.vue';
|
||||
import TagsFilter from '#/components/filters/tags.vue';
|
||||
import ChannelsFilter from '#/components/filters/channels.vue';
|
||||
import Scene from './tile.vue';
|
||||
import Pagination from '../pagination/pagination.vue';
|
||||
import Scene from '#/components/scenes/tile.vue';
|
||||
import Pagination from '#/components/pagination/pagination.vue';
|
||||
import Ellipsis from '#/components/loading/ellipsis.vue';
|
||||
|
||||
defineProps({
|
||||
showFilters: {
|
||||
@@ -99,12 +115,13 @@ const {
|
||||
} = pageProps;
|
||||
|
||||
const scenes = ref(pageProps.scenes);
|
||||
const aggActors = ref(pageProps.aggActors);
|
||||
const aggTags = ref(pageProps.aggTags);
|
||||
const aggChannels = ref(pageProps.aggChannels);
|
||||
const aggActors = ref(pageProps.aggActors || []);
|
||||
const aggTags = ref(pageProps.aggTags || []);
|
||||
const aggChannels = ref(pageProps.aggChannels || []);
|
||||
|
||||
const currentPage = ref(Number(routeParams.page));
|
||||
const total = ref(Number(pageProps.total));
|
||||
const loading = ref(false);
|
||||
|
||||
const actorIds = urlParsed.search.actors?.split(',').map((identifier) => parseActorIdentifier(identifier)?.id).filter(Boolean) || [];
|
||||
const queryActors = actorIds.map((urlActorId) => aggActors.value.find((aggActor) => aggActor.id === urlActorId)).filter(Boolean);
|
||||
@@ -112,11 +129,11 @@ const queryActors = actorIds.map((urlActorId) => aggActors.value.find((aggActor)
|
||||
const networks = Object.fromEntries(aggChannels.value.map((channel) => (channel.type === 'network' ? channel : channel.parent)).filter(Boolean).map((parent) => [`_${parent.slug}`, parent]));
|
||||
const channels = Object.fromEntries(aggChannels.value.filter((channel) => channel.type === 'channel').map((channel) => [channel.slug, channel]));
|
||||
|
||||
const queryChannel = networks[urlParsed.search.e] || channels[urlParsed.search.e];
|
||||
const queryEntity = networks[urlParsed.search.e] || channels[urlParsed.search.e];
|
||||
|
||||
const filters = ref({
|
||||
tags: urlParsed.search.tags?.split(',').filter(Boolean) || [],
|
||||
channel: queryChannel,
|
||||
entity: queryEntity,
|
||||
actors: queryActors,
|
||||
});
|
||||
|
||||
@@ -149,14 +166,16 @@ async function search(resetPage = true) {
|
||||
|
||||
const query = {};
|
||||
|
||||
const entity = filters.value.channel || pageChannel;
|
||||
const entity = filters.value.entity || pageChannel;
|
||||
const entitySlug = entity?.type === 'network' ? `_${entity.slug}` : entity?.slug;
|
||||
|
||||
loading.value = true;
|
||||
|
||||
const res = await get('/scenes', {
|
||||
...query,
|
||||
actors: [pageActor, ...filters.value.actors].filter(Boolean).map((filterActor) => getActorIdentifier(filterActor)).join(','), // if we're on an actor page, that actor ID needs to be included
|
||||
tags: [pageTag?.slug, ...filters.value.tags].filter(Boolean).join(','),
|
||||
entity: entitySlug,
|
||||
e: entitySlug,
|
||||
scope,
|
||||
page: currentPage.value, // client uses param rather than query pagination
|
||||
});
|
||||
@@ -164,15 +183,17 @@ async function search(resetPage = true) {
|
||||
scenes.value = res.scenes;
|
||||
aggActors.value = res.aggActors;
|
||||
aggTags.value = res.aggTags;
|
||||
aggChannels.value = res.aggChannels;
|
||||
total.value = res.total;
|
||||
|
||||
loading.value = false;
|
||||
events.emit('scrollUp');
|
||||
|
||||
navigate(getPath(scope, false), {
|
||||
...query,
|
||||
actors: filters.value.actors.map((filterActor) => getActorIdentifier(filterActor)).join(',') || undefined, // don't include page actor ID in query, already a parameter
|
||||
tags: filters.value.tags.join(',') || undefined,
|
||||
e: filters.value.channel?.type === 'network' ? `_${filters.value.channel.slug}` : (filters.value.channel?.slug || undefined),
|
||||
e: filters.value.entity?.type === 'network' ? `_${filters.value.entity.slug}` : (filters.value.entity?.slug || undefined),
|
||||
}, { redirect: false });
|
||||
}
|
||||
|
||||
@@ -189,12 +210,13 @@ function updateFilter(prop, value, reload = true) {
|
||||
.page {
|
||||
display: flex;
|
||||
background: var(--background-base-10);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.scenes-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: .5rem 0 .25rem 2rem;
|
||||
padding: 1rem 0 .25rem 3rem;
|
||||
}
|
||||
|
||||
.scenes-container {
|
||||
@@ -233,4 +255,20 @@ function updateFilter(prop, value, reload = true) {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.loading:not(.ellipsis) {
|
||||
opacity: .3;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.ellipsis {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 1rem;
|
||||
left: 50%;
|
||||
|
||||
&.loading {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user