Improved actor editing units.

This commit is contained in:
DebaucheryLibrarian 2024-10-30 02:25:19 +01:00
parent 2293e10af7
commit 855a698eae
3 changed files with 262 additions and 127 deletions

View File

@ -66,7 +66,16 @@
</div> </div>
</div> </div>
<span class="name">{{ actor.name }}</span> <span class="label">
<span class="name ellipsis">{{ actor.name }}</span>
<img
v-if="actor.entity"
v-tooltip="actor.entity.name"
:src="`/logos/${actor.entity.slug}/favicon_dark.png`"
class="favicon"
>
</span>
</div> </div>
</template> </template>
@ -119,9 +128,11 @@ const favorited = ref(props.actor.stashes.some((actorStash) => actorStash.id ===
} }
} }
.name { .label {
display: flex;
justify-content: space-between;
align-items: center;
flex-shrink: 0; flex-shrink: 0;
padding: .35rem .5rem;
font-weight: bold; font-weight: bold;
font-size: .9rem; font-size: .9rem;
white-space: nowrap; white-space: nowrap;
@ -130,6 +141,15 @@ const favorited = ref(props.actor.stashes.some((actorStash) => actorStash.id ===
user-select: all; user-select: all;
} }
.name {
padding: .35rem .25rem .35rem .5rem;
}
.favicon {
height: 1rem;
padding: .35rem .5rem .35rem .25rem;
}
.avatar-container { .avatar-container {
position: relative; position: relative;
flex-grow: 1; flex-grow: 1;

View File

@ -68,7 +68,7 @@
<div class="item-header"> <div class="item-header">
<div class="key">{{ item.label || item.key }}</div> <div class="key">{{ item.label || item.key }}</div>
<div class="item-actions"> <div class="item-actions noselect">
<Icon <Icon
v-if="!item.forced" v-if="!item.forced"
icon="pencil5" icon="pencil5"
@ -153,32 +153,66 @@
class="input" class="input"
:disabled="!editing.has(item.key)" :disabled="!editing.has(item.key)"
> >
<option value="imperial">Imperial</option>
<option value="metric">Metric</option> <option value="metric">Metric</option>
<option value="imperial">Imperial</option>
</select> </select>
</div> </div>
<span class="figure-bust"> <span class="figure-height">
<div class="value-section"> <div class="value-section">
<span class="value-label">Bust</span> <span class="value-label">Height</span>
<select <span v-if="sizeUnits === 'metric'">
v-model="edits[item.key].bust" <input
class="select input" v-model="edits[item.key].metricHeight"
placeholder="Bust" type="number"
:disabled="!editing.has(item.key)" class="input"
> :disabled="!editing.has(item.key)"
<option > cm
:key="`${item.key}-bust-unknown`" </span>
:value="null"
/>
<option <span v-if="sizeUnits === 'imperial'">
v-for="bust in bustSizes[figureUnits]" <input
:key="`${item.key}-bust-${bust}`" v-model="edits[item.key].imperialHeight[0]"
:value="Array.isArray(bust) ? bust[0] : bust" type="number"
>{{ Array.isArray(bust) ? bust.join('/') : bust }}</option> class="input"
</select> :disabled="!editing.has(item.key)"
> ft
<input
v-model="edits[item.key].imperialHeight[1]"
type="number"
class="input"
:disabled="!editing.has(item.key)"
> in
</span>
</div>
</span>
<span class="figure-weight">
<div class="value-section">
<span class="value-label">Weight</span>
<span v-if="sizeUnits === 'metric'">
<input
v-model="edits[item.key].metricWeight"
type="number"
class="input"
:disabled="!editing.has(item.key)"
> kg
</span>
<span v-if="sizeUnits === 'imperial'">
<input
v-model="edits[item.key].imperialWeight"
type="number"
class="input"
:disabled="!editing.has(item.key)"
>
<template v-if="sizeUnits === 'imperial'">&nbsp;lbs</template>
<template v-else>&nbsp;kg</template>
</span>
</div> </div>
</span> </span>
</div> </div>
@ -210,20 +244,10 @@
</div> </div>
<div class="value-section"> <div class="value-section">
<span class="value-label">State</span> <span class="value-label">Place</span>
<input <input
v-model="edits[item.key].state" v-model="edits[item.key].place"
class="string input"
:disabled="!editing.has(item.key)"
>
</div>
<div class="value-section">
<span class="value-label">City</span>
<input
v-model="edits[item.key].city"
class="string input" class="string input"
:disabled="!editing.has(item.key)" :disabled="!editing.has(item.key)"
> >
@ -560,27 +584,53 @@
<div class="value-section"> <div class="value-section">
<span class="value-label">Penis length</span> <span class="value-label">Penis length</span>
<input <span v-if="penisUnits === 'metric'">
v-model="edits[item.key].penisLength" <input
type="number" v-model="edits[item.key].metricLength"
class="volume input" type="number"
min="1" class="volume input"
max="20" min="1"
:disabled="!editing.has(item.key)" max="30"
> :disabled="!editing.has(item.key)"
> cm
</span>
<span v-if="penisUnits === 'imperial'">
<input
v-model="edits[item.key].imperialLength"
type="number"
class="volume input"
min="1"
max="30"
:disabled="!editing.has(item.key)"
> inch
</span>
</div> </div>
<div class="value-section"> <div class="value-section">
<span class="value-label">Penis girth</span> <span class="value-label">Penis girth</span>
<input <span v-if="penisUnits === 'metric'">
v-model="edits[item.key].penisGirth" <input
type="number" v-model="edits[item.key].metricGirth"
class="volume input" type="number"
min="1" class="volume input"
max="20" min="1"
:disabled="!editing.has(item.key)" max="30"
> :disabled="!editing.has(item.key)"
> cm
</span>
<span v-if="penisUnits === 'imperial'">
<input
v-model="edits[item.key].imperialGirth"
type="number"
class="volume input"
min="1"
max="30"
:disabled="!editing.has(item.key)"
> inch
</span>
</div> </div>
<div class="value-section"> <div class="value-section">
@ -690,7 +740,7 @@ const user = pageContext.user;
const countries = pageContext.pageProps.countries; const countries = pageContext.pageProps.countries;
const actor = ref(pageContext.pageProps.actor); const actor = ref(pageContext.pageProps.actor);
console.log(actor.value); // console.log(actor.value);
const topCountries = [ const topCountries = [
'AU', 'AU',
@ -705,26 +755,6 @@ const topCountries = [
const sortedCountries = countries.toSorted((countryA, countryB) => topCountries.indexOf(countryB.alpha2) - topCountries.indexOf(countryA.alpha2)); const sortedCountries = countries.toSorted((countryA, countryB) => topCountries.indexOf(countryB.alpha2) - topCountries.indexOf(countryA.alpha2));
/*
const cupSizes = [
'A', 'AA',
'B',
'C',
'D', 'DD', 'DDD',
'E',
'F', 'FF',
'G', 'GG',
'H', 'HH',
'J', 'JJ',
'K', 'KK',
'L',
'M',
'N',
'O',
'P',
];
*/
const cupSizes = { const cupSizes = {
us: ['AA', 'A', 'B', 'C', 'D', ['DD', 'E'], ['DDD', 'F'], 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P'], // United States us: ['AA', 'A', 'B', 'C', 'D', ['DD', 'E'], ['DDD', 'F'], 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P'], // United States
uk: ['AA', 'A', 'B', 'C', 'D', 'DD', 'E', 'F', 'FF', 'G', 'GG', 'H', 'HH', 'J', 'JJ', 'K', 'KK'], // United Kingdom uk: ['AA', 'A', 'B', 'C', 'D', 'DD', 'E', 'F', 'FF', 'G', 'GG', 'H', 'HH', 'J', 'JJ', 'K', 'KK'], // United Kingdom
@ -778,28 +808,42 @@ const fields = computed(() => [
: null, : null,
inline: true, inline: true,
}, },
{
key: 'ethnicity',
type: 'string',
value: actor.value.ethnicity,
suggestions: [
'Asian',
'Black',
'Indian',
'Latina',
'White',
],
},
{ {
key: 'origin', key: 'origin',
type: 'place', type: 'place',
value: { value: {
...actor.value.origin,
country: actor.value.origin?.country?.alpha2 || null, country: actor.value.origin?.country?.alpha2 || null,
place: [actor.value.origin?.city, actor.value.origin?.state].filter(Boolean).join(', '),
}, },
}, },
{ {
key: 'residence', key: 'residence',
type: 'place', type: 'place',
value: { value: {
...actor.value.residence,
country: actor.value.residence?.country?.alpha2 || null, country: actor.value.residence?.country?.alpha2 || null,
place: [actor.value.residence?.city, actor.value.residence?.state].filter(Boolean).join(', '),
}, },
}, },
{ {
key: 'size', key: 'size',
type: 'size', type: 'size',
value: { value: {
height: actor.value.height?.metric, metricHeight: actor.value.height?.metric,
weight: actor.value.weight?.metric, metricWeight: actor.value.weight?.metric,
imperialHeight: actor.value.height?.imperial || [],
imperialWeight: actor.value.weight?.imperial,
}, },
}, },
{ {
@ -900,8 +944,10 @@ const fields = computed(() => [
key: 'penis', key: 'penis',
type: 'penis', type: 'penis',
value: { value: {
penisLength: actor.value.penisLength?.imperial, metricLength: actor.value.penisLength?.metric,
penisGirth: actor.value.penisGirth?.imperial, metricGirth: actor.value.penisGirth?.metric,
imperialLength: actor.value.penisLength?.imperial,
imperialGirth: actor.value.penisGirth?.imperial,
isCircumcised: actor.value.isCircumcised, isCircumcised: actor.value.isCircumcised,
}, },
}, },
@ -943,27 +989,49 @@ function setAvatar(avatarId) {
const keyMap = { const keyMap = {
origin: { origin: {
country: 'originCountry', country: 'originCountry',
state: 'originState', place: 'originPlace',
city: 'originCity',
}, },
residence: { residence: {
country: 'residenceCountry', country: 'residenceCountry',
state: 'residenceState', place: 'residencePlace',
city: 'residenceCity',
}, },
}; };
const groupMap = {
penisLength: 'penis',
penisGirth: 'penis',
height: 'size',
weight: 'size',
};
async function submit() { async function submit() {
try { try {
await post('/revisions/actors', { await post('/revisions/actors', {
actorId: actor.value.id, actorId: actor.value.id,
edits: Object.fromEntries(Array.from(editing.value).flatMap((key) => { edits: {
if (edits.value[key] && typeof edits.value[key] === 'object') { ...Object.fromEntries(Array.from(editing.value).flatMap((key) => {
return Object.entries(edits.value[key]).map(([valueKey, value]) => [keyMap[key]?.[valueKey] || valueKey, value]); if (edits.value[key] && typeof edits.value[key] === 'object') {
} return Object.entries(edits.value[key]).map(([valueKey, value]) => [keyMap[key]?.[valueKey] || valueKey, value]);
}
return [[key, edits.value[key]]]; return [[key, edits.value[key]]];
})), })),
...Object.fromEntries(Object.entries({
height: sizeUnits.value === 'imperial' ? edits.value.size.imperialHeight : edits.value.size.metricHeight,
weight: sizeUnits.value === 'imperial' ? edits.value.size.imperialWeight : edits.value.size.metricWeight,
penisLength: penisUnits.value === 'imperial' ? edits.value.penis.imperialLength : edits.value.penis.metricLength,
penisGirth: penisUnits.value === 'imperial' ? edits.value.penis.imperialGirth : edits.value.penis.metricGirth,
}).filter(([key]) => editing.value.has(groupMap[key] || key))),
metricHeight: undefined,
metricWeight: undefined,
imperialHeight: undefined,
imperialWeight: undefined,
metricLength: undefined,
metricGirth: undefined,
imperialLength: undefined,
imperialGirth: undefined,
},
sizeUnits: sizeUnits.value,
figureUnits: figureUnits.value, figureUnits: figureUnits.value,
penisUnits: penisUnits.value, penisUnits: penisUnits.value,
comment: comment.value, comment: comment.value,

View File

@ -59,6 +59,12 @@ export function curateActor(actor, context = {}) {
name: actor.name, name: actor.name,
gender: actor.gender, gender: actor.gender,
age: actor.age, age: actor.age,
ethnicity: actor.ethnicity,
entity: actor.entity && {
id: actor.entity.id,
slug: actor.entity.slug,
name: actor.entity.name,
},
...Object.fromEntries(Object.entries(keyMap).map(([key, entryKey]) => [key, actor[entryKey]])), ...Object.fromEntries(Object.entries(keyMap).map(([key, entryKey]) => [key, actor[entryKey]])),
ageFromBirth: actor.date_of_birth && differenceInYears(Date.now(), actor.date_of_birth), ageFromBirth: actor.date_of_birth && differenceInYears(Date.now(), actor.date_of_birth),
ageThen: context.sceneDate && actor.date_of_birth && differenceInYears(context.sceneDate, actor.date_of_birth), ageThen: context.sceneDate && actor.date_of_birth && differenceInYears(context.sceneDate, actor.date_of_birth),
@ -166,11 +172,13 @@ export async function fetchActorsById(actorIds, options = {}, reqUser) {
knex.raw('COALESCE(birth_countries.alias, birth_countries.name) as birth_country_name'), knex.raw('COALESCE(birth_countries.alias, birth_countries.name) as birth_country_name'),
'residence_countries.alpha2 as residence_country_alpha2', 'residence_countries.alpha2 as residence_country_alpha2',
knex.raw('COALESCE(residence_countries.alias, residence_countries.name) as residence_country_name'), knex.raw('COALESCE(residence_countries.alias, residence_countries.name) as residence_country_name'),
knex.raw('row_to_json(entities) as entity'),
) )
.leftJoin('actors_meta', 'actors_meta.actor_id', 'actors.id') .leftJoin('actors_meta', 'actors_meta.actor_id', 'actors.id')
.leftJoin('countries as birth_countries', 'birth_countries.alpha2', 'actors.birth_country_alpha2') .leftJoin('countries as birth_countries', 'birth_countries.alpha2', 'actors.birth_country_alpha2')
.leftJoin('countries as residence_countries', 'residence_countries.alpha2', 'actors.residence_country_alpha2') .leftJoin('countries as residence_countries', 'residence_countries.alpha2', 'actors.residence_country_alpha2')
.leftJoin('media as avatars', 'avatars.id', 'actors.avatar_media_id') .leftJoin('media as avatars', 'avatars.id', 'actors.avatar_media_id')
.leftJoin('entities', 'entities.id', 'actors.entity_id')
.whereIn('actors.id', actorIds) .whereIn('actors.id', actorIds)
.modify((builder) => { .modify((builder) => {
if (options.order) { if (options.order) {
@ -199,8 +207,8 @@ export async function fetchActorsById(actorIds, options = {}, reqUser) {
) )
.whereIn('actor_id', actorIds) .whereIn('actor_id', actorIds)
.leftJoin('media', 'media.id', 'actors_avatars.media_id') .leftJoin('media', 'media.id', 'actors_avatars.media_id')
.groupBy('media.id', 'actors_avatars.actor_id', 'actors_avatars.created_at') .groupBy('media.id', 'actors_avatars.actor_id')
.orderBy('actors_avatars.created_at', 'desc'), .orderBy(knex.raw('max(actors_avatars.created_at)'), 'desc'),
reqUser reqUser
? knex('stashes_actors') ? knex('stashes_actors')
.leftJoin('stashes', 'stashes.id', 'stashes_actors.stash_id') .leftJoin('stashes', 'stashes.id', 'stashes_actors.stash_id')
@ -549,34 +557,6 @@ async function fetchMainProfile(actorId, wasCreated = false) {
return fetchMainProfile(actorId, true); return fetchMainProfile(actorId, true);
} }
/*
async function applyMainProfile(actorId) {
const [actorEntry, mainProfile] = await Promise.all([
knex('actors')
.where('id', actorId)
.first(),
fetchMainProfile(actorId),
]);
if (!actorEntry) {
throw new HttpError('No actor profile found to apply main profile to', 404);
}
const preservedKeys = ['id'];
// we start iterating from the actor entry so we don't include keys that are not yet supported by the actors table
const mergedProfile = Object.fromEntries(Object.entries(actorEntry)
.filter(([key]) => Object.hasOwn(mainProfile, key))
.map(([key, value]) => [key, mainProfile[key] === null || preservedKeys.includes(key)
? value
: mainProfile[key]]));
await knex('actors')
.where('id', actorId)
.update(mergedProfile);
}
*/
async function applyActorRevision(revisionIds, reqUser) { async function applyActorRevision(revisionIds, reqUser) {
const revisions = await knex('actors_revisions') const revisions = await knex('actors_revisions')
.whereIn('id', revisionIds) .whereIn('id', revisionIds)
@ -600,6 +580,7 @@ async function applyActorRevision(revisionIds, reqUser) {
'residenceCountry', 'residenceCountry',
'residenceState', 'residenceState',
'residenceCity', 'residenceCity',
'ethnicity',
'height', 'height',
'weight', 'weight',
'bust', 'bust',
@ -755,6 +736,27 @@ function convertFigure(domain = 'cup', rawValue, units) {
return usValue; return usValue;
} }
function convertHeight(height, units) {
if (units === 'metric' || !Array.isArray(height)) {
return Number(height) || null;
}
if (height.length !== 2) {
return null;
}
// 12 inches in a foot
return Math.round(((height[0] * 12) + height[1]) * 2.54);
}
function convertWeight(weight, units) {
if (units === 'imperial') {
return Math.round(unit(weight, 'lbs').toNumeric('kg'));
}
return Number(weight) || null;
}
export async function createActorRevision(actorId, { export async function createActorRevision(actorId, {
edits, edits,
comment, comment,
@ -819,15 +821,9 @@ export async function createActorRevision(actorId, {
return null; return null;
} }
if (Array.isArray(value)) { if (['originPlace', 'residencePlay'].includes(key)) {
const valueSet = new Set(value); console.log(key, value);
const baseSet = new Set(baseActor[key]); throw new Error('must be converted first!');
if (valueSet.size === baseSet.size && baseActor[key].every((id) => valueSet.has(id))) {
return null;
}
return { key, value: Array.from(valueSet) };
} }
if (['cup', 'bust', 'waist', 'hip'].includes(key)) { if (['cup', 'bust', 'waist', 'hip'].includes(key)) {
@ -844,9 +840,49 @@ export async function createActorRevision(actorId, {
}; };
} }
if (['height'].includes(key)) {
const convertedValue = convertHeight(value, options.sizeUnits);
if (baseActor[key] === convertedValue) {
return null;
}
const conversionComment = !value || convertedValue === value
? null
: `${key} converted from ${value[0]} in ${value[1]} ft to ${convertedValue} cm`;
return {
key,
value: convertedValue,
comment: conversionComment,
};
}
if (['weight'].includes(key)) {
const convertedValue = convertWeight(value, options.sizeUnits);
if (baseActor[key] === convertedValue) {
return null;
}
const conversionComment = !value || convertedValue === value
? null
: `${key} converted from ${value} lbs to ${convertedValue} kg`;
return {
key,
value: convertedValue,
comment: conversionComment,
};
}
if (['penisLength', 'penisGirth'].includes(key) && options.penisUnits === 'imperial') { if (['penisLength', 'penisGirth'].includes(key) && options.penisUnits === 'imperial') {
const convertedValue = Math.round(convert(value, 'inches').to('cm')); const convertedValue = Math.round(convert(value, 'inches').to('cm'));
if (baseActor[key] === convertedValue) {
return null;
}
return { return {
key, key,
value: convertedValue, value: convertedValue,
@ -854,6 +890,17 @@ export async function createActorRevision(actorId, {
}; };
} }
if (Array.isArray(value)) {
const valueSet = new Set(value);
const baseSet = new Set(baseActor[key]);
if (valueSet.size === baseSet.size && baseActor[key].every((id) => valueSet.has(id))) {
return null;
}
return { key, value: Array.from(valueSet) };
}
return { key, value }; return { key, value };
}).filter(Boolean); }).filter(Boolean);