Added actor assignment to new actors module. Showing network icon on network-specific actors. Improved dark theme. Changed tag tile design. Added Digital Playground logos.

This commit is contained in:
ThePendulum 2020-03-27 04:39:13 +01:00
parent 689dbeefbd
commit fb59bf552a
24 changed files with 202 additions and 113 deletions

View File

@ -11,11 +11,7 @@
"no-console": 0,
"indent": "off",
"template-curly-spacing": "off",
"max-len": [2, {
"code": 300,
"tabWidth": 4,
"ignoreUrls": true
}],
"max-len": 0,
"vue/no-v-html": 0,
"vue/html-indent": ["error", 4],
"vue/multiline-html-element-content-newline": 0,

View File

@ -7,7 +7,9 @@
<div class="actor-header">
<h2 class="header-name">
{{ actor.name }}
<span v-if="actor.network">{{ actor.name }} ({{ actor.network.name }})</span>
<span v-else="">{{ actor.name }}</span>
<Gender
:gender="actor.gender"
class="header-gender"

View File

@ -109,8 +109,20 @@ export default {
<style lang="scss">
@import 'theme';
.gender-link.selected .gender .icon {
fill: var(--text-light);
.gender-link {
&.selected .gender .icon {
fill: var(--text-light);
filter: none;
}
&:hover:not(.selected) .gender .icon {
fill: var(--text-light);
}
&:hover:not(.selected) .transsexual .icon {
fill: var(--female);
filter: drop-shadow(1px 0 0 var(--text-light)) drop-shadow(-1px 0 0 var(--text-light)) drop-shadow(0 1px 0 var(--text-light)) drop-shadow(0 -1px 0 var(--text-light)) drop-shadow(1px 0 0 var(--male)) drop-shadow(-1px 0 0 var(--male)) drop-shadow(0 1px 0 var(--male)) drop-shadow(0 -1px 0 var(--male)) drop-shadow(0 0 1px rgba(0, 0, 0, 0.5));
}
}
</style>
@ -159,7 +171,6 @@ export default {
align-items: center;
justify-content: center;
box-sizing: border-box;
padding: .2rem 0 0 0;
margin: .25rem .5rem .25rem 0;
color: var(--shadow);
background: var(--background);
@ -167,31 +178,29 @@ export default {
text-decoration: none;
box-shadow: 0 0 3px var(--darken-weak);
.male,
.female,
.transsexual {
padding: .2rem 0 0 0;
}
.icon {
fill: var(--shadow);
}
&:hover {
color: var(--primary);
color: var(--text-light);
cursor: pointer;
.icon {
fill: var(--text-light);
}
}
&.selected {
background: var(--primary);
color: var(--text-light);
&.male {
background: var(--male);
}
&.female {
background: var(--female);
}
&.transsexual {
background: var(--text);
}
&.other .icon {
fill: var(--text-light);
}

View File

@ -72,6 +72,7 @@ export default {
flex-direction: column;
overflow: hidden;
background: var(--background-dim);
color: var(--text);
}
.content {

View File

@ -246,6 +246,7 @@ 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,
@ -287,8 +288,8 @@ export default {
.info {
padding: 1rem;
border-left: solid 1px $shadow-hint;
border-right: solid 1px $shadow-hint;
border-left: solid 1px var(--shadow-hint);
border-right: solid 1px var(--shadow-hint);
flex-grow: 1;
}
@ -304,15 +305,15 @@ export default {
.icon {
display: inline-block;
width: 1rem;
fill: $shadow-strong;
fill: var(--shadow-strong);
margin: 0 1rem 0 0;
}
}
.details {
background: $profile;
color: $text-contrast;
box-shadow: 0 0 3px $shadow-weak;
background: var(--profile);
color: var(--text-light);
box-shadow: 0 0 3px var(--shadow-weak);
cursor: default;
.column {
@ -322,7 +323,7 @@ export default {
}
.link {
color: $text-contrast;
color: var(--text-light);
}
}
@ -331,11 +332,11 @@ export default {
height: 100%;
&:not(:last-child) {
border-right: solid 1px $highlight-hint;
border-right: solid 1px var(--lighten-hint);
}
.icon {
fill: $highlight-weak;
fill: var(--lighten-weak);
margin: 0 .25rem 0 0;
}
@ -359,7 +360,6 @@ export default {
.logo {
display: inline-block;
filter: $logo-highlight;
}
.logo-site {
@ -377,7 +377,7 @@ export default {
}
.chain {
color: $highlight;
color: var(--lighten);
padding: 0 .5rem;
font-weight: bold;
font-size: .8rem;
@ -388,7 +388,7 @@ export default {
}
.description {
line-height: 1.25;
line-height: 1.5;
}
.duration {
@ -416,34 +416,36 @@ export default {
.filename {
width: 100%;
padding: .5rem;
border: solid 1px $shadow-weak;
color: var(--text);
border: solid 1px var(--shadow-weak);
background: var(--background);
}
.link {
display: inline-block;
color: $link;
color: var(--link);
text-decoration: none;
&:hover {
color: $primary;
color: var(--primary);
.icon {
fill: $primary;
fill: var(--primary);
}
}
}
.tag .link {
background: $background;
background: var(--background);
display: inline-block;
padding: .5rem;
margin: 0 .25rem .25rem 0;
box-shadow: 0 0 2px $shadow-weak;
box-shadow: 0 0 2px var(--shadow-weak);
text-decoration: none;
text-transform: capitalize;
&:hover {
color: $primary;
color: var(--primary);
}
}

View File

@ -8,8 +8,21 @@
class="link"
>
<span
v-if="actor.network"
v-tooltip.top="`${actor.name} (${actor.network.name})`"
class="handle"
>
<img
:src="`/img/logos/${actor.network.slug}/favicon.png`"
class="favicon"
>
<span class="name">{{ actor.name }}</span>
</span>
<span
v-else
v-tooltip.top="actor.name"
class="name"
class="handle name"
>{{ actor.name }}</span>
<div class="avatar-container">
@ -89,28 +102,54 @@ export default {
.actor {
width: 100%;
background: $background;
display: inline-block;
margin: 0 .5rem .5rem 0;
box-shadow: 0 0 3px $shadow-weak;
box-shadow: 0 0 3px var(--darken-weak);
background: var(--profile);
}
.link {
color: $text;
color: var(--text-light);
text-decoration: none;
&:hover {
color: $primary;
color: var(--primary);
}
}
.handle {
display: flex;
align-items: center;
justify-content: center;
padding: .5rem;
font-weight: bold;
}
.favicon {
width: 1rem;
height: 1rem;
margin: 0 .5rem 0 0;
& + .name {
padding: 0 1rem 0 0;
}
}
.name {
flex-grow: 1;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
text-align: center;
}
.avatar-container {
position: relative;
}
.avatar {
color: $shadow-weak;
background: $shadow-hint;
color: var(--darken-weak);
background: var(--darken-hint);
height: 13rem;
width: 100%;
display: flex;
@ -123,12 +162,12 @@ export default {
.avatar-fallback {
max-height: 75%;
max-width: 80%;
opacity: .1;
opacity: .5;
}
.details {
background: $shadow;
color: $text-contrast;
background: var(--darken);
color: var(--text-light);
width: 100%;
height: 1.75rem;
display: flex;
@ -156,16 +195,6 @@ export default {
}
.age-then {
color: $highlight;
}
.name {
display: block;
padding: .5rem;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
text-align: center;
font-weight: bold;
color: var(--lighten);
}
</style>

View File

@ -216,9 +216,8 @@ export default {
object-fit: cover;
background-position: center;
background-size: cover;
background-color: var(--shadow-hint);
background-color: var(--darken-hint);
color: var(--shadow);
text-shadow: 1px 1px 0 var(--highlight);
}
.row {

View File

@ -4,14 +4,14 @@
:title="tag.name"
class="tile"
>
<span class="title">{{ tag.name }}</span>
<img
v-if="tag.poster"
:src="sfw ? `/img/${tag.poster.sfw.thumbnail}` : `/img/${tag.poster.thumbnail}`"
:alt="tag.name"
class="poster"
>
<span class="title">{{ tag.name }}</span>
</router-link>
</template>
@ -44,6 +44,7 @@ export default {
align-items: left;
justify-content: flex-end;
box-sizing: border-box;
position: relative;
text-align: center;
text-decoration: none;
box-shadow: inset 0 0 3px var(--darken);
@ -51,16 +52,23 @@ export default {
.poster {
width: 100%;
height: 14rem;
height: 16rem;
object-fit: cover;
box-shadow: 0 0 3px var(--darken);
object-position: 50% 100%;
box-shadow: 0 0 1px var(--darken);
}
.title {
width: 100%;
display: flex;
font-size: 1rem;
box-sizing: border-box;
padding: .5rem 1rem;
position: absolute;
bottom: 0;
background: var(--darken);
font-size: 1rem;
font-weight: bold;
text-transform: capitalize;
text-shadow: 0 0 3px var(--darken-strong);
}
</style>

View File

@ -9,7 +9,6 @@ body {
}
body {
color: var(--text);
margin: 0;
font-family: Arial, Helvetica, sans-serif;
}

View File

@ -77,6 +77,11 @@ function initActorActions(store, _router) {
tattoos
piercings
description
network {
id
name
slug
}
avatar: actorsAvatarByActorId {
media {
thumbnail
@ -222,6 +227,11 @@ function initActorActions(store, _router) {
age
birthdate
gender
network {
id
name
slug
}
avatar: actorsAvatarByActorId {
media {
thumbnail

View File

@ -37,6 +37,11 @@ const actorFields = `
birthdate
age
gender
network {
id
name
slug
}
originCountry: countryByBirthCountryAlpha2 {
alpha2
name

View File

@ -247,7 +247,6 @@ exports.up = knex => Promise.resolve()
table.increments('id', 12);
table.string('name')
.unique()
.notNullable();
table.string('slug', 32)
@ -261,17 +260,6 @@ exports.up = knex => Promise.resolve()
.references('id')
.inTable('networks');
table.datetime('created_at')
.defaultTo(knex.fn.now());
}))
.then(() => knex.schema.createTable('actors_profiles', (table) => {
table.increments('id', 12);
table.integer('actor_id')
.references('id')
.inTable('actors')
.notNullable();
table.date('birthdate');
table.string('gender', 18);
table.text('description');
@ -307,6 +295,13 @@ exports.up = knex => Promise.resolve()
table.datetime('created_at')
.defaultTo(knex.fn.now());
}))
.then(() => knex.raw('CREATE TABLE actors_profiles AS TABLE actors WITH NO DATA;'))
.then(() => knex.schema.alterTable('actors_profiles', (table) => {
table.integer('actor_id')
.references('id')
.inTable('actors')
.notNullable();
table.datetime('scraped_at');
table.boolean('scrape_success');
@ -323,7 +318,9 @@ exports.up = knex => Promise.resolve()
{ slug: 'face', name: 'face' },
{ slug: 'scalp', name: 'scalp' },
{ slug: 'forehead', name: 'forehead' },
{ slug: 'temple', name: 'temple' },
{ slug: 'cheek', name: 'cheek' },
{ slug: 'jaw', name: 'jaw' },
{ slug: 'chin', name: 'chin' },
{ slug: 'neck', name: 'neck' },
{ slug: 'throat', name: 'throat' },
@ -337,6 +334,9 @@ exports.up = knex => Promise.resolve()
{ slug: 'upper-lip', name: 'upper lip' },
{ slug: 'lower-lip', name: 'lower lip' },
{ slug: 'inner-lip', name: 'inner lip' },
{ slug: 'inner-lower-lip', name: 'inner lower lip' },
{ slug: 'inner-upper-lip', name: 'inner upper lip' },
{ slug: 'philtrum', name: 'philtrum' },
{ slug: 'above-lip', name: 'above lip' },
{ slug: 'below-lip', name: 'below lip' },
// nose
@ -364,7 +364,7 @@ exports.up = knex => Promise.resolve()
// hands
{ slug: 'hand', name: 'hand' },
{ slug: 'fingers', name: 'fingers' },
{ slug: 'knuckles', name: 'knucles' },
{ slug: 'knuckles', name: 'knuckles' },
{ slug: 'thumb', name: 'thumb' },
{ slug: 'index-finger', name: 'index finger' },
{ slug: 'middle-finger', name: 'middle finger' },
@ -378,11 +378,14 @@ exports.up = knex => Promise.resolve()
{ slug: 'collarbone', name: 'collarbone' },
{ slug: 'chest', name: 'chest' },
{ slug: 'rib-cage', name: 'rib cage' },
{ slug: 'breastbone', name: 'breastbone' },
{ slug: 'underboob', name: 'underboob' },
{ slug: 'sideboob', name: 'sideboob' },
{ slug: 'boob', name: 'boob' },
{ slug: 'nipple', name: 'nipple' },
{ slug: 'abdomen', name: 'abdomen' },
{ slug: 'lower-abdomen', name: 'lower abdomen' },
{ slug: 'navel', name: 'navel' },
{ slug: 'pelvis', name: 'pelvis' },
// back
{ slug: 'back', name: 'back' },
{ slug: 'upper-back', name: 'upper back' },
@ -392,9 +395,9 @@ exports.up = knex => Promise.resolve()
// bottom
{ slug: 'butt', name: 'butt' },
{ slug: 'hip', name: 'hip' },
{ slug: 'anus', name: 'anus' },
// genitals
{ slug: 'pubic-mound', name: 'pubic mound' },
{ slug: 'anus', name: 'anus' },
{ slug: 'vagina', name: 'vagina' },
{ slug: 'outer-labia', name: 'outer labia' },
{ slug: 'inner-labia', name: 'inner labia' },
@ -748,8 +751,8 @@ exports.up = knex => Promise.resolve()
COMMENT ON VIEW movie_actors IS E'@foreignKey (movie_id) references releases (id)\n@foreignKey (actor_id) references actors (id)';
COMMENT ON VIEW movie_tags IS E'@foreignKey (movie_id) references releases (id)\n@foreignKey (tag_id) references tags (id)';
COMMENT ON COLUMN actors_profiles.height IS E'@omit read,update,create,delete,all,many';
COMMENT ON COLUMN actors_profiles.weight IS E'@omit read,update,create,delete,all,many';
COMMENT ON COLUMN actors.height IS E'@omit read,update,create,delete,all,many';
COMMENT ON COLUMN actors.weight IS E'@omit read,update,create,delete,all,many';
`));
exports.down = knex => knex.raw(`

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 258 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

View File

@ -1,57 +1,74 @@
'use strict';
const logger = require('./logger')(__filename);
const knex = require('./knex');
const slugify = require('./utils/slugify');
const capitalize = require('./utils/capitalize');
function toBaseActors(actorsOrNames, release) {
return actorsOrNames.map((actorOrName) => {
const name = capitalize(actorOrName.name || actorOrName);
const slug = slugify(name);
const baseActor = {
name,
slug,
hasSingleName: name.split(/\s+/).length === 1,
network: release.site.network,
slugWithNetworkSlug: `${slug}-${release.site.network.slug}`,
};
if (actorOrName.name) {
return {
...actorOrName,
name: capitalize(actorOrName.name),
slug: slugify(actorOrName.name),
networkId: release.site.network.id,
...baseActor,
};
}
return {
name: capitalize(actorOrName),
slug: slugify(actorOrName),
networkId: release.site.network.id,
};
return baseActor;
});
}
function curateActorEntry(baseActor) {
const actorEntry = {
if (baseActor.hasSingleName) {
logger.warn(`Assigning single name actor '${baseActor.name}' to network '${baseActor.network.name}'`);
// attach network ID to allow separating actors with the same name
return {
name: baseActor.name,
slug: baseActor.slugWithNetworkSlug,
network_id: baseActor.network.id,
};
}
return {
name: baseActor.name,
slug: baseActor.slug,
};
if (baseActor.name.split(/\s+/).length === 1) {
// attach network ID for single names, to reduce mismatches
actorEntry.network_id = baseActor.networkId;
}
return actorEntry;
}
function curateActorEntries(baseActors) {
return baseActors.map(baseActor => curateActorEntry(baseActor));
}
async function getActors(baseActors) {
async function getOrCreateActors(baseActors) {
const existingActors = await knex('actors')
.select('id', 'name', 'slug', 'network_id')
.whereIn('slug', baseActors.map(baseActor => baseActor.slug))
.orWhereIn('name', baseActors.map(baseActor => baseActor.slug));
.whereNull('network_id')
.orWhereIn(['slug', 'network_id'], baseActors.map(baseActor => [baseActor.slugWithNetworkSlug, baseActor.network.id]));
if (existingActors.length === 0) {
// TODO: TESTING ONLY
await knex('actors').insert(curateActorEntries(baseActors.slice(0, 3)));
const existingActorSlugs = new Set(existingActors.map(actor => actor.slug));
const uniqueBaseActors = baseActors.filter(baseActor => !existingActorSlugs.has(baseActor.slug) && !existingActorSlugs.has(baseActor.slugWithNetworkSlug));
const curatedActorEntries = curateActorEntries(uniqueBaseActors);
const newActors = await knex('actors').insert(curatedActorEntries, ['id', 'name', 'slug', 'network_id']);
if (Array.isArray(newActors)) {
return newActors.concat(existingActors);
}
console.log(existingActors);
return existingActors;
}
async function associateActors(releases) {
@ -67,9 +84,18 @@ async function associateActors(releases) {
const baseActorsBySlug = baseActors.reduce((acc, baseActor) => ({ ...acc, [baseActor.slug]: baseActor }), {});
const uniqueBaseActors = Object.values(baseActorsBySlug);
const actors = await getActors(uniqueBaseActors);
const actors = await getOrCreateActors(uniqueBaseActors);
const actorIdsBySlug = actors.reduce((acc, actor) => ({ ...acc, [actor.slug]: actor.id }), {});
console.log(actors);
const releaseActorAssociations = Object.entries(baseActorsByReleaseId)
.map(([releaseId, releaseActors]) => releaseActors
.map(releaseActor => ({
release_id: releaseId,
actor_id: actorIdsBySlug[releaseActor.slug] || actorIdsBySlug[releaseActor.slugWithNetworkSlug],
})))
.flat();
await knex.raw(`${knex('releases_actors').insert(releaseActorAssociations).toString()} ON CONFLICT DO NOTHING;`);
}
module.exports = {

View File

@ -11,14 +11,14 @@ const schemaExtender = makeExtendSchemaPlugin(_build => ({
IMPERIAL
}
extend type ActorProfile {
extend type Actor {
age: Int @requires(columns: ["birthdate"])
height(units:Units): String @requires(columns: ["height"])
weight(units:Units): String @requires(columns: ["weight"])
}
`,
resolvers: {
ActorProfile: {
Actor: {
age(parent, _args, _context, _info) {
if (!parent.birthdate) return null;