<template> <div class="character-preview"> <div v-if="match && character" class="row"> <div class="col-2"> <img :src="avatarUrl(character.character.name)" class="character-avatar"> </div> <div class="col-10"> <h1 class="user-view"> <span class="character-name" :class="(statusClasses || {}).userClass">{{ character.character.name }}</span> <span v-if="((statusClasses) && (statusClasses.matchScore === 'unicorn'))" :class="(statusClasses || {}).matchClass">Unicorn</span> </h1> <h3>{{ getOnlineStatus() }}</h3> <div class="summary"> <span class="uc"> <span v-if="age" :class="byScore(TagId.Age)">{{age}}-years-old </span> <span v-if="sexualOrientation" :class="byScore(TagId.Orientation)">{{sexualOrientation}} </span> <span v-if="gender" :class="byScore(TagId.Gender)">{{gender}} </span> <span v-if="species" :class="byScore(TagId.Species)">{{species}} </span> </span> <span v-if="furryPref" :class="byScore(TagId.FurryPreference)"><br /><span class="uc">{{furryPref}}</span></span> <span v-if="subDomRole" :class="byScore(TagId.SubDomRole)"><br /><span class="uc">{{subDomRole}}</span></span> </div> <match-tags v-if="match" :match="match"></match-tags> <!-- <div v-if="customs">--> <!-- <span v-for="c in customs" :class="Score.getClasses(c.score)">{{c.name}}</span>--> <!-- </div>--> <div class="status-message" v-if="statusMessage"> <h4>Status <span v-if="latestAd && (statusMessage === latestAd.message)">& Latest Ad</span></h4> <bbcode :text="statusMessage"></bbcode> </div> <div class="latest-ad-message" v-if="latestAd && (latestAd.message !== statusMessage)"> <h4>Latest Ad <span class="message-time">{{formatTime(latestAd.datePosted)}}</span></h4> <bbcode :text="latestAd.message"></bbcode> </div> </div> </div> <div v-else> Loading... </div> </div> </template> <script lang="ts"> import { Component, Hook, Prop } from '@f-list/vue-ts'; import Vue from 'vue'; import core from '../core'; import { methods } from '../../site/character_page/data_store'; import {Character as ComplexCharacter} from '../../site/character_page/interfaces'; import { Matcher, MatchReport, Score } from '../../learn/matcher'; import { Character as CharacterStatus } from '../../fchat'; import { getStatusClasses, StatusClasses } from '../UserView.vue'; import * as _ from 'lodash'; import { AdCachedPosting } from '../../learn/ad-cache'; import {formatTime} from '../common'; import * as Utils from '../../site/utils'; import MatchTags from './MatchTags.vue'; import { furryPreferenceMapping, Gender, kinkMapping, Orientation, Species, SubDomRole, TagId } from '../../learn/matcher-types'; import { BBCodeView } from '../../bbcode/view'; import { EventBus } from './event-bus'; import { Character, CustomKink } from '../../interfaces'; interface CustomKinkWithScore extends CustomKink { score: number; } @Component({ components: { 'match-tags': MatchTags, bbcode: BBCodeView(core.bbCodeParser) } }) export default class CharacterPreview extends Vue { @Prop readonly id?: number; characterName?: string; character?: ComplexCharacter; match?: MatchReport; ownCharacter?: ComplexCharacter; onlineCharacter?: CharacterStatus; statusClasses?: StatusClasses; latestAd?: AdCachedPosting; statusMessage?: string; age?: string; sexualOrientation?: string; species?: string; gender?: string; furryPref?: string; subDomRole?: string; formatTime = formatTime; readonly avatarUrl = Utils.avatarURL; TagId = TagId; Score = Score; scoreWatcher: ((event: any) => void) | null = null; customs?: CustomKinkWithScore[]; @Hook('mounted') mounted(): void { // tslint:disable-next-line no-unsafe-any no-any this.scoreWatcher = async(event: {character: Character, score: number}): Promise<void> => { // console.log('scoreWatcher', event); if ( (event.character) && (this.characterName) && (event.character.name === this.characterName) ) { await this.load(this.characterName, true); } }; EventBus.$on( 'character-score', this.scoreWatcher ); } @Hook('beforeDestroy') beforeDestroy(): void { if (this.scoreWatcher) { EventBus.$off( 'character-score', this.scoreWatcher ); this.scoreWatcher = null; } } async load(characterName: string, force: boolean = false): Promise<void> { if ( (this.characterName === characterName) && (!force) && (this.match) && (this.character) && (this.ownCharacter) && (this.ownCharacter.character.name === core.characters.ownProfile.character.name) ) { this.updateOnlineStatus(); this.updateAdStatus(); return; } this.characterName = characterName; this.match = undefined; this.character = undefined; this.customs = undefined; this.ownCharacter = core.characters.ownProfile; this.updateOnlineStatus(); this.updateAdStatus(); this.character = await this.getCharacterData(characterName); this.match = Matcher.identifyBestMatchReport(this.ownCharacter.character, this.character.character); this.updateCustoms(); this.updateDetails(); } updateOnlineStatus(): void { this.onlineCharacter = core.characters.get(this.characterName!); if (!this.onlineCharacter) { this.statusClasses = undefined; return; } this.statusMessage = this.onlineCharacter.statusText; this.statusClasses = getStatusClasses(this.onlineCharacter, undefined, true, false, true); } updateAdStatus(): void { const cache = core.cache.adCache.get(this.characterName!); if ( (!cache) || (cache.posts.length === 0) || (Date.now() - cache.posts[cache.posts.length - 1].datePosted.getTime() > (45 * 60 * 1000)) ) { this.latestAd = undefined; return; } this.latestAd = cache.posts[cache.posts.length - 1]; } updateCustoms(): void { this.customs = _.orderBy( _.map( _.reject(this.character!.character.customs || [], (c) => _.isUndefined(c)) as CustomKink[], (c: CustomKink) => _.assign( {}, c, { score: kinkMapping[c.choice], name: c.name.trim().replace(/^\W+/, '').replace(/\W+$/, '') } ) ), ['score', 'name'], ['desc', 'asc'] ); } updateDetails(): void { if (!this.match) { this.age = undefined; this.species = undefined; this.gender = undefined; this.furryPref = undefined; this.subDomRole = undefined; this.sexualOrientation = undefined; return; } const a = this.match.them.yourAnalysis; const c = this.match.them.you; const rawSpecies = Matcher.getTagValue(TagId.Species, c); const rawAge = Matcher.getTagValue(TagId.Age, c); // if ((a.species) && (!Species[a.species])) { // console.log('SPECIES', a.species, rawSpecies); // } if ((a.orientation) && (!Orientation[a.orientation])) { console.error('Missing Orientation', a.orientation, c.name); } this.age = a.age ? this.readable(`${a.age}`) : (rawAge && /[0-9]/.test(rawAge.string || '') && rawAge.string) || undefined; this.species = a.species ? this.readable(Species[a.species]) : (rawSpecies && rawSpecies.string) || undefined; this.gender = a.gender ? this.readable(Gender[a.gender]) : undefined; this.furryPref = a.furryPreference ? this.readable(furryPreferenceMapping[a.furryPreference]) : undefined; this.subDomRole = a.subDomRole ? this.readable(SubDomRole[a.subDomRole]) : undefined; this.sexualOrientation = a.orientation ? this.readable(Orientation[a.orientation]) : undefined; } readable(s: string): string { return s.replace(/([A-Z])/g, ' $1').trim().toLowerCase() .replace(/(always|usually) (submissive|dominant)/, '$2') .replace(/bi (fe)?male preference/, 'bisexual'); } byScore(_tagId: any): string { return ''; // too much // if (!this.match) { // return ''; // } // // const score = this.match.merged[tagId]; // // if (!score) { // return ''; // } // // return score.getRecommendedClass(); } getOnlineStatus(): string { if (!this.onlineCharacter) { return 'Offline'; } const s = this.onlineCharacter.status as string; return `${s.substr(0, 1).toUpperCase()}${s.substr(1)}`; } async getCharacterData(characterName: string): Promise<ComplexCharacter> { const cache = await core.cache.profileCache.get(characterName); if (cache) { return cache.character; } return methods.characterData(characterName, this.id, false); } } </script> <style lang="scss"> .character-preview { padding: 10px; padding-right: 15px; background-color: var(--input-bg); max-height: 100%; overflow: hidden; opacity: 0.95; border-radius: 0 5px 5px 5px; border: 1px solid var(--secondary); .unicorn { margin-left: 8px; } .summary { font-size: 125%; .uc { display: inline-block; &::first-letter { text-transform: capitalize; } } .match { background-color: var(--scoreMatchBg); border: solid 1px var(--scoreMatchFg); } .weak-match { background-color: var(--scoreWeakMatchBg); border: 1px solid var(--scoreWeakMatchFg); } .weak-mismatch { background-color: var(--scoreWeakMismatchBg); border: 1px solid var(--scoreWeakMismatchFg); } .mismatch { background-color: var(--scoreMismatchBg); border: 1px solid var(--scoreMismatchFg); } } .matched-tags { margin-top: 1rem; } h1 { line-height: 100%; margin-bottom: 0; font-size: 2em; } h3 { font-size: 1.1rem; color: var(--dark); } h4 { font-size: 1.25rem; margin-bottom: 0; .message-time { font-size: 80%; font-weight: normal; color: var(--messageTimeFgColor); margin-left: 2px; } } .status-message, .latest-ad-message { display: block; background-color: rgba(0,0,0,0.2); padding: 10px; border-radius: 5px; margin-top: 1.3rem; } .character-avatar { width: 100%; height: auto; } } </style>