Character previews
This commit is contained in:
parent
ef123fdddc
commit
61b4976763
|
@ -1,7 +1,8 @@
|
|||
# Changelog
|
||||
|
||||
## Canary
|
||||
* More conifugrable settings for F-Chat Rising
|
||||
* More configurable settings for F-Chat Rising
|
||||
* Hover mouse over a character name to see a character preview
|
||||
|
||||
|
||||
## 1.3.0
|
||||
|
|
|
@ -55,7 +55,6 @@
|
|||
EventBus.$emit('imagepreview-show', {url: this.url});
|
||||
}
|
||||
|
||||
|
||||
toggleStickyness(): void {
|
||||
EventBus.$emit('imagepreview-toggle-stickyness', {url: this.url});
|
||||
}
|
||||
|
|
|
@ -10,9 +10,7 @@
|
|||
<bbcode id="userMenuStatus" :text="character.statusText" v-show="character.statusText" class="list-group-item"
|
||||
style="max-height:200px;overflow:auto;clear:both"></bbcode>
|
||||
|
||||
<div v-if="match" class="list-group-item menu-character-score">
|
||||
<span v-for="(score, key) in match" :class="score.getRecommendedClass()"><i :class="score.getRecommendedIcon()"></i> {{getTagDesc(key)}}</span>
|
||||
</div>
|
||||
<match-tags v-if="match" :match="match" class="list-group-item"></match-tags>
|
||||
|
||||
<a tabindex="-1" :href="profileLink" target="_blank" v-if="showProfileFirst" class="list-group-item list-group-item-action">
|
||||
<span class="fa fa-fw fa-user"></span>{{l('user.profile')}}</a>
|
||||
|
@ -59,12 +57,12 @@ import core from './core';
|
|||
import { Channel, Character } from './interfaces';
|
||||
import l from './localize';
|
||||
import ReportDialog from './ReportDialog.vue';
|
||||
import { Matcher, MatchResultScores } from '../learn/matcher';
|
||||
import { TagId } from '../learn/matcher-types';
|
||||
import { Matcher, MatchReport } from '../learn/matcher';
|
||||
import _ from 'lodash';
|
||||
import MatchTags from './preview/MatchTags.vue';
|
||||
|
||||
@Component({
|
||||
components: {bbcode: BBCodeView(core.bbCodeParser), modal: Modal, 'ad-view': CharacterAdView}
|
||||
components: {'match-tags': MatchTags, bbcode: BBCodeView(core.bbCodeParser), modal: Modal, 'ad-view': CharacterAdView}
|
||||
})
|
||||
export default class UserMenu extends Vue {
|
||||
@Prop({required: true})
|
||||
|
@ -80,7 +78,7 @@ import _ from 'lodash';
|
|||
memo = '';
|
||||
memoId = 0;
|
||||
memoLoading = false;
|
||||
match: MatchResultScores | null = null;
|
||||
match: MatchReport | null = null;
|
||||
|
||||
openConversation(jump: boolean): void {
|
||||
const conversation = core.conversations.getPrivate(this.character!);
|
||||
|
@ -226,9 +224,6 @@ import _ from 'lodash';
|
|||
this.showContextMenu = false;
|
||||
}
|
||||
|
||||
getTagDesc(key: any): any {
|
||||
return TagId[key].toString().replace(/([A-Z])/g, ' $1').trim();
|
||||
}
|
||||
|
||||
private async openMenu(touch: MouseEvent | Touch, character: Character, channel: Channel | undefined): Promise<void> {
|
||||
this.channel = channel;
|
||||
|
@ -246,7 +241,7 @@ import _ from 'lodash';
|
|||
const match = Matcher.identifyBestMatchReport(myProfile.character, theirProfile.character.character);
|
||||
|
||||
if (_.keys(match.merged).length > 0) {
|
||||
this.match = match.merged;
|
||||
this.match = match;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -272,44 +267,6 @@ import _ from 'lodash';
|
|||
border-top-width: 0;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
#userMenu {
|
||||
.menu-character-score {
|
||||
span {
|
||||
padding-left: 3px;
|
||||
padding-right: 3px;
|
||||
margin-bottom: 3px;
|
||||
margin-right: 3px;
|
||||
display: inline-block;
|
||||
border: 1px solid;
|
||||
border-radius: 3px;
|
||||
|
||||
i {
|
||||
color: white;
|
||||
}
|
||||
|
||||
&.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<!-- Linebreaks inside this template will break BBCode views -->
|
||||
<template><span :class="userClass" v-bind:bbcodeTag.prop="'user'" v-bind:character.prop="character" v-bind:channel.prop="channel"><span v-if="!!statusClass" :class="statusClass"></span><span v-if="!!rankIcon" :class="rankIcon"></span>{{character.name}}<span v-if="!!matchClass" :class="matchClass">{{getMatchScoreTitle(matchScore)}}</span></span></template>
|
||||
<template><span :class="userClass" v-bind:bbcodeTag.prop="'user'" v-bind:character.prop="character" v-bind:channel.prop="channel" @mouseover.prevent="show()" @mouseenter.prevent="show()" @mouseleave.prevent="dismiss()" @mouseout.prevent="dismiss()" @click.middle.prevent="toggleStickyness()" @click.right.passive="dismiss(true)" @click.left.passive="dismiss(true)"><span v-if="!!statusClass" :class="statusClass"></span><span v-if="!!rankIcon" :class="rankIcon"></span>{{character.name}}<span v-if="!!matchClass" :class="matchClass">{{getMatchScoreTitle(matchScore)}}</span></span></template>
|
||||
|
||||
|
||||
<script lang="ts">
|
||||
|
@ -33,6 +33,70 @@ export function getStatusIcon(status: Character.Status): string {
|
|||
}
|
||||
|
||||
|
||||
export interface StatusClasses {
|
||||
rankIcon: string | null;
|
||||
statusClass: string | null;
|
||||
matchClass: string | null;
|
||||
matchScore: number | null;
|
||||
userClass: string;
|
||||
isBookmark: boolean;
|
||||
}
|
||||
|
||||
export function getStatusClasses(
|
||||
character: Character,
|
||||
channel: Channel | undefined,
|
||||
showStatus: boolean,
|
||||
showBookmark: boolean,
|
||||
showMatch: boolean
|
||||
): StatusClasses {
|
||||
let rankIcon: string | null = null;
|
||||
let statusClass = null;
|
||||
let matchClass = null;
|
||||
let matchScore = null;
|
||||
|
||||
if(character.isChatOp) {
|
||||
rankIcon = 'far fa-gem';
|
||||
} else if(channel !== undefined) {
|
||||
rankIcon = (channel.owner === character.name)
|
||||
? 'fa fa-key'
|
||||
: channel.opList.indexOf(character.name) !== -1
|
||||
? (channel.id.substr(0, 4) === 'adh-' ? 'fa fa-shield-alt' : 'fa fa-star')
|
||||
: null;
|
||||
}
|
||||
|
||||
if ((showStatus) || (character.status === 'crown'))
|
||||
statusClass = `fa-fw ${getStatusIcon(character.status)}`;
|
||||
|
||||
if ((core.state.settings.risingAdScore) && (showMatch)) {
|
||||
const cache = core.cache.profileCache.getSync(character.name);
|
||||
|
||||
if (cache) {
|
||||
matchClass = `match-found ${Score.getClasses(cache.matchScore)}`;
|
||||
matchScore = cache.matchScore;
|
||||
} else {
|
||||
/* tslint:disable-next-line no-floating-promises */
|
||||
core.cache.addProfile(character.name);
|
||||
}
|
||||
}
|
||||
|
||||
const gender = character.gender !== undefined ? character.gender.toLowerCase() : 'none';
|
||||
|
||||
const isBookmark = (showBookmark) && (core.connection.isOpen) && (core.state.settings.colorBookmarks) &&
|
||||
((character.isFriend) || (character.isBookmarked));
|
||||
|
||||
const userClass = `user-view gender-${gender}${isBookmark ? ' user-bookmark' : ''}`;
|
||||
|
||||
return {
|
||||
rankIcon,
|
||||
statusClass,
|
||||
matchClass,
|
||||
matchScore,
|
||||
userClass,
|
||||
isBookmark
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@Component({
|
||||
components: {
|
||||
|
||||
|
@ -54,6 +118,9 @@ export default class UserView extends Vue {
|
|||
@Prop()
|
||||
readonly match?: boolean = false;
|
||||
|
||||
@Prop({default: true})
|
||||
readonly preview: boolean = true;
|
||||
|
||||
userClass = '';
|
||||
|
||||
rankIcon: string | null = null;
|
||||
|
@ -100,8 +167,14 @@ export default class UserView extends Vue {
|
|||
onBeforeDestroy(): void {
|
||||
if (this.scoreWatcher)
|
||||
EventBus.$off('character-score', this.scoreWatcher);
|
||||
|
||||
this.dismiss();
|
||||
}
|
||||
|
||||
@Hook('deactivated')
|
||||
deactivate(): void {
|
||||
this.dismiss();
|
||||
}
|
||||
|
||||
@Hook('beforeUpdate')
|
||||
onBeforeUpdate(): void {
|
||||
|
@ -114,55 +187,13 @@ export default class UserView extends Vue {
|
|||
}
|
||||
|
||||
update(): void {
|
||||
this.rankIcon = null;
|
||||
this.statusClass = null;
|
||||
this.matchClass = null;
|
||||
this.matchScore = null;
|
||||
const res = getStatusClasses(this.character, this.channel, !!this.showStatus, !!this.bookmark, !!this.match);
|
||||
|
||||
// if (this.match) console.log('Update', this.character.name);
|
||||
|
||||
if(this.character.isChatOp) {
|
||||
this.rankIcon = 'far fa-gem';
|
||||
} else if(this.channel !== undefined) {
|
||||
this.rankIcon = (this.channel.owner === this.character.name)
|
||||
? 'fa fa-key'
|
||||
: this.channel.opList.indexOf(this.character.name) !== -1
|
||||
? (this.channel.id.substr(0, 4) === 'adh-' ? 'fa fa-shield-alt' : 'fa fa-star')
|
||||
: null;
|
||||
}
|
||||
|
||||
if ((this.showStatus) || (this.character.status === 'crown'))
|
||||
this.statusClass = `fa-fw ${getStatusIcon(this.character.status)}`;
|
||||
|
||||
// if (this.match) console.log('Update prematch', this.character.name);
|
||||
|
||||
if ((core.state.settings.risingAdScore) && (this.match)) {
|
||||
const cache = core.cache.profileCache.getSync(this.character.name);
|
||||
|
||||
if (cache) {
|
||||
this.matchClass = `match-found ${Score.getClasses(cache.matchScore)}`;
|
||||
this.matchScore = cache.matchScore;
|
||||
|
||||
// console.log('Found match data', this.character.name, cache.matchScore);
|
||||
} else {
|
||||
// console.log('Need match data', this.character.name);
|
||||
|
||||
/* tslint:disable-next-line no-floating-promises */
|
||||
core.cache.addProfile(this.character.name);
|
||||
}
|
||||
}
|
||||
|
||||
// if (this.match) console.log('Update post match', this.character.name);
|
||||
|
||||
const gender = this.character.gender !== undefined ? this.character.gender.toLowerCase() : 'none';
|
||||
|
||||
const isBookmark = (this.bookmark) && (core.connection.isOpen) && (core.state.settings.colorBookmarks) &&
|
||||
((this.character.isFriend) || (this.character.isBookmarked));
|
||||
|
||||
|
||||
this.userClass = `user-view gender-${gender}${isBookmark ? ' user-bookmark' : ''}`;
|
||||
|
||||
// if (this.match) console.log('Update done');
|
||||
this.rankIcon = res.rankIcon;
|
||||
this.statusClass = res.statusClass;
|
||||
this.matchClass = res.matchClass;
|
||||
this.matchScore = res.matchScore;
|
||||
this.userClass = res.userClass;
|
||||
}
|
||||
|
||||
|
||||
|
@ -183,47 +214,37 @@ export default class UserView extends Vue {
|
|||
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
//tslint:disable-next-line:variable-name
|
||||
/* const UserView = Vue.extend({
|
||||
functional: true,
|
||||
render(this: void | Vue, createElement: CreateElement, context?: RenderContext): VNode {
|
||||
const props = <{character: Character, channel?: Channel, showStatus?: true, bookmark?: false, match?: false}>(
|
||||
context !== undefined ? context.props : (<Vue>this).$options.propsData);
|
||||
|
||||
const character = props.character;
|
||||
getCharacterUrl(): string {
|
||||
return `flist-character://${this.character.name}`;
|
||||
}
|
||||
|
||||
let matchClasses: string | undefined;
|
||||
|
||||
if (props.match) {
|
||||
const cache = core.cache.profileCache.getSync(character.name);
|
||||
|
||||
if (cache) {
|
||||
matchClasses = Score.getClasses(cache.matchScore);
|
||||
}
|
||||
dismiss(force: boolean = false): void {
|
||||
if (!this.preview) {
|
||||
return;
|
||||
}
|
||||
|
||||
let rankIcon;
|
||||
if(character.isChatOp) rankIcon = 'far fa-gem';
|
||||
else if(props.channel !== undefined)
|
||||
rankIcon = props.channel.owner === character.name ? 'fa fa-key' : props.channel.opList.indexOf(character.name) !== -1 ?
|
||||
(props.channel.id.substr(0, 4) === 'adh-' ? 'fa fa-shield-alt' : 'fa fa-star') : '';
|
||||
else rankIcon = '';
|
||||
const children: (VNode | string)[] = [character.name];
|
||||
if(rankIcon !== '') children.unshift(createElement('span', {staticClass: rankIcon}));
|
||||
if(props.showStatus !== undefined || character.status === 'crown')
|
||||
children.unshift(createElement('span', {staticClass: `fa-fw ${getStatusIcon(character.status)}`}));
|
||||
const gender = character.gender !== undefined ? character.gender.toLowerCase() : 'none';
|
||||
const isBookmark = props.bookmark !== false && core.connection.isOpen && core.state.settings.colorBookmarks &&
|
||||
(character.isFriend || character.isBookmarked);
|
||||
return createElement('span', {
|
||||
attrs: {class: `user-view gender-${gender}${isBookmark ? ' user-bookmark' : ''} ${matchClasses}`},
|
||||
domProps: {character, channel: props.channel, bbcodeTag: 'user'}
|
||||
}, children);
|
||||
EventBus.$emit('imagepreview-dismiss', {url: this.getCharacterUrl(), force});
|
||||
}
|
||||
});
|
||||
|
||||
export default UserView;
|
||||
*/
|
||||
|
||||
show(): void {
|
||||
if (!this.preview) {
|
||||
return;
|
||||
}
|
||||
|
||||
EventBus.$emit('imagepreview-show', {url: this.getCharacterUrl()});
|
||||
}
|
||||
|
||||
|
||||
toggleStickyness(): void {
|
||||
if (!this.preview) {
|
||||
return;
|
||||
}
|
||||
|
||||
EventBus.$emit('imagepreview-toggle-stickyness', {url: this.getCharacterUrl()});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -0,0 +1,290 @@
|
|||
<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-8">
|
||||
<h1><span class="character-name" :class="(statusClasses || {}).userClass">{{ character.character.name }}</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="latestAd">
|
||||
<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, 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 } 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,
|
||||
Orientation,
|
||||
Species,
|
||||
SubDomRole,
|
||||
TagId
|
||||
} from '../../learn/matcher-types';
|
||||
import { BBCodeView } from '../../bbcode/view';
|
||||
|
||||
|
||||
@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;
|
||||
|
||||
age?: string;
|
||||
sexualOrientation?: string;
|
||||
species?: string;
|
||||
gender?: string;
|
||||
furryPref?: string;
|
||||
subDomRole?: string;
|
||||
|
||||
formatTime = formatTime;
|
||||
readonly avatarUrl = Utils.avatarURL;
|
||||
TagId = TagId;
|
||||
|
||||
async load(characterName: string): Promise<void> {
|
||||
if (
|
||||
(this.characterName === characterName)
|
||||
&& (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.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.updateDetails();
|
||||
}
|
||||
|
||||
|
||||
updateOnlineStatus(): void {
|
||||
this.onlineCharacter = core.characters.get(this.characterName!);
|
||||
|
||||
if (!this.onlineCharacter) {
|
||||
this.statusClasses = undefined;
|
||||
return;
|
||||
}
|
||||
|
||||
this.statusClasses = getStatusClasses(this.onlineCharacter, undefined, true, true, false);
|
||||
}
|
||||
|
||||
|
||||
updateAdStatus(): void {
|
||||
const cache = core.cache.adCache.get(this.characterName!);
|
||||
|
||||
if ((!cache) || (cache.posts.length === 0)) {
|
||||
this.latestAd = undefined;
|
||||
return;
|
||||
}
|
||||
|
||||
this.latestAd = cache.posts[cache.posts.length - 1];
|
||||
}
|
||||
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
this.age = a.age ? this.readable(`${a.age}`) : (rawAge && 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;
|
||||
background-color: var(--input-bg);
|
||||
|
||||
.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: 1rem;
|
||||
margin-top: 1rem;
|
||||
|
||||
.message-time {
|
||||
font-size: 80%;
|
||||
font-weight: normal;
|
||||
color: var(--messageTimeFgColor);
|
||||
margin-left: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.character-avatar {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -21,15 +21,20 @@
|
|||
id="image-preview-ext"
|
||||
ref="imagePreviewExt"
|
||||
class="image-preview-external"
|
||||
:style="externalPreviewStyle">
|
||||
:style="previewStyles.ExternalImagePreviewHelper">
|
||||
</webview>
|
||||
|
||||
<div
|
||||
class="image-preview-local"
|
||||
:style="localPreviewStyle"
|
||||
:style="previewStyles.LocalImagePreviewHelper"
|
||||
>
|
||||
</div>
|
||||
|
||||
<character-preview
|
||||
:style="previewStyles.CharacterPreviewHelper"
|
||||
ref="characterPreview"
|
||||
></character-preview>
|
||||
|
||||
<i id="preview-spinner" class="fas fa-circle-notch fa-spin" v-show="shouldShowSpinner"></i>
|
||||
<i id="preview-error" class="fas fa-times" v-show="shouldShowError"></i>
|
||||
</div>
|
||||
|
@ -44,12 +49,17 @@
|
|||
import {domain} from '../../bbcode/core';
|
||||
import {ImageDomMutator} from './image-dom-mutator';
|
||||
|
||||
import { ExternalImagePreviewHelper, LocalImagePreviewHelper } from './helper';
|
||||
import {
|
||||
ExternalImagePreviewHelper,
|
||||
LocalImagePreviewHelper,
|
||||
PreviewManager,
|
||||
CharacterPreviewHelper, RenderStyle
|
||||
} from './helper';
|
||||
|
||||
import {Point, WebviewTag, remote} from 'electron';
|
||||
import Timer = NodeJS.Timer;
|
||||
import IpcMessageEvent = Electron.IpcMessageEvent;
|
||||
|
||||
import CharacterPreview from './CharacterPreview.vue';
|
||||
|
||||
const screen = remote.screen;
|
||||
|
||||
|
@ -63,15 +73,30 @@
|
|||
httpStatusText: string;
|
||||
}
|
||||
|
||||
|
||||
@Component
|
||||
@Component({
|
||||
components: {
|
||||
'character-preview': CharacterPreview
|
||||
}
|
||||
})
|
||||
export default class ImagePreview extends Vue {
|
||||
private readonly MinTimePreviewVisible = 100;
|
||||
|
||||
visible = false;
|
||||
|
||||
externalPreviewHelper = new ExternalImagePreviewHelper(this);
|
||||
localPreviewHelper = new LocalImagePreviewHelper(this);
|
||||
previewManager = new PreviewManager(
|
||||
this,
|
||||
[
|
||||
new ExternalImagePreviewHelper(this),
|
||||
new LocalImagePreviewHelper(this),
|
||||
new CharacterPreviewHelper(this)
|
||||
// new ChannelPreviewHelper(this)
|
||||
]
|
||||
);
|
||||
|
||||
// externalPreviewHelper = new ExternalImagePreviewHelper(this);
|
||||
// localPreviewHelper = new LocalImagePreviewHelper(this);
|
||||
// externalPreviewStyle: Record<string, any> = {};
|
||||
// localPreviewStyle: Record<string, any> = {};
|
||||
|
||||
url: string | null = null;
|
||||
domain: string | undefined;
|
||||
|
@ -82,15 +107,11 @@
|
|||
|
||||
jsMutator = new ImageDomMutator(this.debug);
|
||||
|
||||
externalPreviewStyle: Record<string, any> = {};
|
||||
localPreviewStyle: Record<string, any> = {};
|
||||
|
||||
state = 'hidden';
|
||||
|
||||
shouldShowSpinner = false;
|
||||
shouldShowError = true;
|
||||
|
||||
|
||||
private interval: Timer | null = null;
|
||||
|
||||
private exitInterval: Timer | null = null;
|
||||
|
@ -100,6 +121,9 @@
|
|||
private shouldDismiss = false;
|
||||
private visibleSince = 0;
|
||||
|
||||
previewStyles: Record<string, RenderStyle> = {};
|
||||
|
||||
|
||||
@Hook('mounted')
|
||||
onMounted(): void {
|
||||
console.warn('Mounted ImagePreview');
|
||||
|
@ -299,43 +323,33 @@
|
|||
|
||||
|
||||
reRenderStyles(): void {
|
||||
// tslint:disable-next-line:no-unsafe-any
|
||||
this.externalPreviewStyle = this.externalPreviewHelper.renderStyle();
|
||||
// tslint:disable-next-line:no-unsafe-any
|
||||
this.localPreviewStyle = this.localPreviewHelper.renderStyle();
|
||||
|
||||
this.debugLog(
|
||||
'ImagePreview: reRenderStyles', 'external',
|
||||
JSON.parse(JSON.stringify(this.externalPreviewStyle)),
|
||||
'local', JSON.parse(JSON.stringify(this.localPreviewStyle))
|
||||
);
|
||||
this.previewStyles = this.previewManager.renderStyles();
|
||||
}
|
||||
|
||||
|
||||
updatePreviewSize(width: number, height: number): void {
|
||||
if (!this.externalPreviewHelper.isVisible()) {
|
||||
return;
|
||||
const helper = this.previewManager.getVisiblePreview();
|
||||
|
||||
if ((!helper) || (!helper.reactsToSizeUpdates())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ((width) && (height)) {
|
||||
this.debugLog('ImagePreview: updatePreviewSize', width, height, width / height);
|
||||
|
||||
this.externalPreviewHelper.setRatio(width / height);
|
||||
helper.setRatio(width / height);
|
||||
this.reRenderStyles();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
hide(): void {
|
||||
this.debugLog('ImagePreview: hide', this.externalPreviewHelper.isVisible(), this.localPreviewHelper.isVisible());
|
||||
|
||||
this.cancelExitTimer();
|
||||
|
||||
this.url = null;
|
||||
this.visible = false;
|
||||
|
||||
this.localPreviewHelper.hide();
|
||||
this.externalPreviewHelper.hide();
|
||||
this.previewManager.hide();
|
||||
|
||||
this.exitUrl = null;
|
||||
this.exitInterval = null;
|
||||
|
@ -378,7 +392,7 @@
|
|||
if ((!this.hasMouseMovedSince()) && (!force))
|
||||
return;
|
||||
|
||||
this.debugLog('ImagePreview: dismiss.exec', this.externalPreviewHelper.isVisible(), this.localPreviewHelper.isVisible(), url);
|
||||
this.debugLog('ImagePreview: dismiss.exec', this.previewManager.getVisibilityStatus(), url);
|
||||
|
||||
// This timeout makes the preview window disappear with a slight delay, which helps UX
|
||||
// when dealing with situations such as quickly scrolling text that moves the cursor away
|
||||
|
@ -393,7 +407,7 @@
|
|||
show(initialUrl: string): void {
|
||||
const url = this.jsMutator.mutateUrl(initialUrl);
|
||||
|
||||
this.debugLog('ImagePreview: show', this.externalPreviewHelper.isVisible(), this.localPreviewHelper.isVisible(),
|
||||
this.debugLog('ImagePreview: show', this.previewManager.getVisibilityStatus(),
|
||||
this.visible, this.hasMouseMovedSince(), !!this.interval, this.sticky, url);
|
||||
|
||||
// console.log('SHOW');
|
||||
|
@ -430,15 +444,7 @@
|
|||
() => {
|
||||
this.debugLog('ImagePreview: show.timeout', this.url);
|
||||
|
||||
const isLocal = this.localPreviewHelper.match(this.domain as string);
|
||||
|
||||
isLocal
|
||||
? this.localPreviewHelper.show(this.url as string)
|
||||
: this.localPreviewHelper.hide();
|
||||
|
||||
this.externalPreviewHelper.match(this.domain as string)
|
||||
? this.externalPreviewHelper.show(this.url as string)
|
||||
: this.externalPreviewHelper.hide();
|
||||
const helper = this.previewManager.show(this.url || undefined, this.domain);
|
||||
|
||||
this.interval = null;
|
||||
this.visible = true;
|
||||
|
@ -449,7 +455,11 @@
|
|||
|
||||
this.reRenderStyles();
|
||||
|
||||
this.setState(isLocal ? 'loaded' : 'loading');
|
||||
if (helper) {
|
||||
this.setState(helper.shouldTrackLoading() ? 'loading' : 'loaded');
|
||||
} else {
|
||||
this.setState('loaded');
|
||||
}
|
||||
},
|
||||
due
|
||||
) as Timer;
|
||||
|
@ -504,8 +514,7 @@
|
|||
this.debug = !this.debug;
|
||||
|
||||
this.jsMutator.setDebug(this.debug);
|
||||
this.localPreviewHelper.setDebug(this.debug);
|
||||
this.externalPreviewHelper.setDebug(this.debug);
|
||||
this.previewManager.setDebug(this.debug);
|
||||
|
||||
if (this.debug) {
|
||||
const webview = this.getWebview();
|
||||
|
@ -550,26 +559,44 @@
|
|||
this.hide();
|
||||
}
|
||||
|
||||
|
||||
toggleJsMode(): void {
|
||||
this.runJs = !this.runJs;
|
||||
}
|
||||
|
||||
reloadUrl(): void {
|
||||
if (this.externalPreviewHelper.isVisible()) {
|
||||
const webview = this.getWebview();
|
||||
|
||||
webview.reload();
|
||||
reloadUrl(): void {
|
||||
const helper = this.previewManager.getVisiblePreview();
|
||||
|
||||
if ((!helper) || (!helper.usesWebView())) {
|
||||
return;
|
||||
}
|
||||
|
||||
// helper.reload();
|
||||
this.getWebview().reload();
|
||||
}
|
||||
|
||||
|
||||
getWebview(): WebviewTag {
|
||||
return this.$refs.imagePreviewExt as WebviewTag;
|
||||
}
|
||||
|
||||
|
||||
getCharacterPreview(): CharacterPreview {
|
||||
return this.$refs.characterPreview as CharacterPreview;
|
||||
}
|
||||
|
||||
|
||||
reset(): void {
|
||||
this.externalPreviewHelper = new ExternalImagePreviewHelper(this);
|
||||
this.localPreviewHelper = new LocalImagePreviewHelper(this);
|
||||
this.previewManager = new PreviewManager(
|
||||
this,
|
||||
[
|
||||
new ExternalImagePreviewHelper(this),
|
||||
new LocalImagePreviewHelper(this),
|
||||
new CharacterPreviewHelper(this)
|
||||
// new ChannelPreviewHelper(this)
|
||||
]
|
||||
);
|
||||
|
||||
this.url = null;
|
||||
this.domain = undefined;
|
||||
|
@ -612,8 +639,15 @@
|
|||
: false;
|
||||
}
|
||||
|
||||
|
||||
testError(): boolean {
|
||||
return ((this.state === 'error') && (this.externalPreviewHelper.isVisible()));
|
||||
const helper = this.previewManager.getVisiblePreview();
|
||||
|
||||
if ((!helper) || (!helper.usesWebView())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (this.state === 'error');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@ -693,6 +727,7 @@
|
|||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
padding: 0.5rem;
|
||||
box-shadow: 2px 2px 3px rgba(0, 0, 0, 0.2);
|
||||
z-index: 1000;
|
||||
|
||||
a i.fa {
|
||||
font-size: 1.25rem;
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
<template>
|
||||
<div class="matched-tags">
|
||||
<span v-for="(score, key) in merged" :class="score.getRecommendedClass()"><i :class="score.getRecommendedIcon()"></i> {{getTagDesc(key)}}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Hook, Prop } from '@f-list/vue-ts';
|
||||
import Vue from 'vue';
|
||||
import { MatchReport, MatchResultScores } from '../../learn/matcher';
|
||||
import { TagId } from '../../learn/matcher-types';
|
||||
|
||||
@Component({
|
||||
components: {
|
||||
|
||||
}
|
||||
})
|
||||
export default class MatchTags extends Vue {
|
||||
@Prop({required: true})
|
||||
readonly match!: MatchReport;
|
||||
|
||||
merged!: MatchResultScores;
|
||||
|
||||
|
||||
@Hook('mounted')
|
||||
onMounted(): void {
|
||||
this.merged = this.match.merged;
|
||||
}
|
||||
|
||||
|
||||
// @Watch('match', { deep: true })
|
||||
// onMatchUpdate(match: MatchReport): void {
|
||||
// // console.log('ON UPDATED ETA', match);
|
||||
// this.merged = match.merged;
|
||||
// }
|
||||
|
||||
|
||||
getTagDesc(key: any): any {
|
||||
return TagId[key].toString().replace(/([A-Z])/g, ' $1').trim();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.matched-tags {
|
||||
span {
|
||||
padding-left: 3px;
|
||||
padding-right: 3px;
|
||||
margin-bottom: 3px;
|
||||
margin-right: 3px;
|
||||
display: inline-block;
|
||||
border: 1px solid;
|
||||
border-radius: 3px;
|
||||
|
||||
i {
|
||||
color: white;
|
||||
}
|
||||
|
||||
&.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,68 @@
|
|||
import { ImagePreviewHelper } from './helper';
|
||||
|
||||
export class CharacterPreviewHelper extends ImagePreviewHelper {
|
||||
static readonly FLIST_CHARACTER_PROTOCOL_TESTER = /^flist-character:\/\/(.+)/;
|
||||
|
||||
hide(): void {
|
||||
this.visible = false;
|
||||
this.url = undefined;
|
||||
}
|
||||
|
||||
|
||||
show(url: string | undefined): void {
|
||||
this.visible = true;
|
||||
this.url = url;
|
||||
|
||||
if (!url) {
|
||||
return;
|
||||
}
|
||||
|
||||
const match = url.match(CharacterPreviewHelper.FLIST_CHARACTER_PROTOCOL_TESTER);
|
||||
|
||||
if (!match) {
|
||||
return;
|
||||
}
|
||||
|
||||
const characterName = match[1];
|
||||
|
||||
// tslint:disable-next-line no-floating-promises
|
||||
this.parent.getCharacterPreview().load(characterName);
|
||||
}
|
||||
|
||||
|
||||
setRatio(_ratio: number): void {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
|
||||
reactsToSizeUpdates(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
shouldTrackLoading(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
usesWebView(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
match(_domainName: string | undefined, url: string | undefined): boolean {
|
||||
if (!url) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return CharacterPreviewHelper.FLIST_CHARACTER_PROTOCOL_TESTER.test(url);
|
||||
}
|
||||
|
||||
|
||||
renderStyle(): Record<string, any> {
|
||||
return this.isVisible()
|
||||
? { display: 'block' }
|
||||
: { display: 'none' };
|
||||
}
|
||||
}
|
||||
|
|
@ -3,7 +3,7 @@ import { ImagePreviewHelper } from './helper';
|
|||
import * as _ from 'lodash';
|
||||
|
||||
export class ExternalImagePreviewHelper extends ImagePreviewHelper {
|
||||
protected lastExternalUrl: string | null = null;
|
||||
protected lastExternalUrl: string | undefined = undefined;
|
||||
|
||||
protected allowCachedUrl = true;
|
||||
|
||||
|
@ -47,6 +47,21 @@ export class ExternalImagePreviewHelper extends ImagePreviewHelper {
|
|||
}
|
||||
|
||||
|
||||
reactsToSizeUpdates(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
shouldTrackLoading(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
usesWebView(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
setDebug(debug: boolean): void {
|
||||
this.debug = debug;
|
||||
|
||||
|
@ -54,7 +69,7 @@ export class ExternalImagePreviewHelper extends ImagePreviewHelper {
|
|||
}
|
||||
|
||||
|
||||
show(url: string): void {
|
||||
show(url: string | undefined): void {
|
||||
const webview = this.parent.getWebview();
|
||||
|
||||
if (!this.parent) {
|
||||
|
@ -65,6 +80,10 @@ export class ExternalImagePreviewHelper extends ImagePreviewHelper {
|
|||
throw new Error('Empty webview!');
|
||||
}
|
||||
|
||||
if (!url) {
|
||||
throw new Error('Empty URL!');
|
||||
}
|
||||
|
||||
// const oldUrl = this.url;
|
||||
// const oldLastExternalUrl = this.lastExternalUrl;
|
||||
|
||||
|
@ -113,8 +132,13 @@ export class ExternalImagePreviewHelper extends ImagePreviewHelper {
|
|||
}
|
||||
|
||||
|
||||
match(domainName: string): boolean {
|
||||
return !((domainName === 'f-list.net') || (domainName === 'static.f-list.net'));
|
||||
match(domainName: string | undefined, url: string | undefined): boolean {
|
||||
if ((!domainName) || (!url)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (ImagePreviewHelper.HTTP_TESTER.test(url))
|
||||
&& (!((domainName === 'f-list.net') || (domainName === 'static.f-list.net')));
|
||||
}
|
||||
|
||||
|
||||
|
@ -152,6 +176,7 @@ export class ExternalImagePreviewHelper extends ImagePreviewHelper {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
renderStyle(): Record<string, any> {
|
||||
return this.isVisible()
|
||||
? _.merge({ display: 'flex' }, this.determineScalingRatio())
|
||||
|
|
|
@ -1,16 +1,23 @@
|
|||
import ImagePreview from '../ImagePreview.vue';
|
||||
|
||||
export abstract class ImagePreviewHelper {
|
||||
static readonly HTTP_TESTER = /^https?:\/\//;
|
||||
|
||||
protected visible = false;
|
||||
protected url: string | null = 'about:blank';
|
||||
protected url: string | undefined = 'about:blank';
|
||||
protected parent: ImagePreview;
|
||||
protected debug: boolean;
|
||||
|
||||
abstract show(url: string): void;
|
||||
abstract show(url: string | undefined): void;
|
||||
abstract hide(): void;
|
||||
abstract match(domainName: string): boolean;
|
||||
abstract match(domainName: string | undefined, url: string | undefined): boolean;
|
||||
abstract renderStyle(): Record<string, any>;
|
||||
|
||||
abstract reactsToSizeUpdates(): boolean;
|
||||
abstract setRatio(ratio: number): void;
|
||||
abstract shouldTrackLoading(): boolean;
|
||||
abstract usesWebView(): boolean;
|
||||
|
||||
constructor(parent: ImagePreview) {
|
||||
if (!parent) {
|
||||
throw new Error('Empty parent!');
|
||||
|
@ -24,7 +31,7 @@ export abstract class ImagePreviewHelper {
|
|||
return this.visible;
|
||||
}
|
||||
|
||||
getUrl(): string | null {
|
||||
getUrl(): string | undefined {
|
||||
return this.url;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
export * from './helper';
|
||||
export * from './character';
|
||||
export * from './external';
|
||||
export * from './helper';
|
||||
export * from './local';
|
||||
export * from './manager';
|
||||
|
||||
|
|
|
@ -1,20 +1,46 @@
|
|||
import { ImagePreviewHelper } from './helper';
|
||||
|
||||
|
||||
export class LocalImagePreviewHelper extends ImagePreviewHelper {
|
||||
hide(): void {
|
||||
this.visible = false;
|
||||
this.url = null;
|
||||
this.url = undefined;
|
||||
}
|
||||
|
||||
|
||||
show(url: string): void {
|
||||
show(url: string | undefined): void {
|
||||
this.visible = true;
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
|
||||
match(domainName: string): boolean {
|
||||
return ((domainName === 'f-list.net') || (domainName === 'static.f-list.net'));
|
||||
setRatio(_ratio: number): void {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
|
||||
reactsToSizeUpdates(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
shouldTrackLoading(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
usesWebView(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
match(domainName: string | undefined, url: string | undefined): boolean {
|
||||
if ((!domainName) || (!url)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (ImagePreviewHelper.HTTP_TESTER.test(url))
|
||||
&& ((domainName === 'f-list.net') || (domainName === 'static.f-list.net'));
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,107 @@
|
|||
import _ from 'lodash';
|
||||
import { ImagePreviewHelper } from './helper';
|
||||
import ImagePreview from '../ImagePreview.vue';
|
||||
|
||||
export type RenderStyle = Record<string, any>;
|
||||
|
||||
export interface PreviewManagerHelper {
|
||||
helper: ImagePreviewHelper;
|
||||
renderStyle: RenderStyle;
|
||||
}
|
||||
|
||||
|
||||
export class PreviewManager {
|
||||
private parent: ImagePreview;
|
||||
|
||||
private helpers: PreviewManagerHelper[];
|
||||
|
||||
private debugMode = false;
|
||||
|
||||
constructor(parent: ImagePreview, helperInstances: ImagePreviewHelper[]) {
|
||||
this.parent = parent;
|
||||
this.helpers = _.map(helperInstances, (helper) => ({ helper, renderStyle: {}}));
|
||||
}
|
||||
|
||||
match(domain: string | undefined, url: string | undefined): PreviewManagerHelper | undefined {
|
||||
return _.find(this.helpers, (h) => h.helper.match(domain, url));
|
||||
}
|
||||
|
||||
matchIndex(domain: string | undefined, url: string | undefined): number {
|
||||
return _.findIndex(this.helpers, (h) => h.helper.match(domain, url));
|
||||
}
|
||||
|
||||
renderStyles(): Record<string, RenderStyle> {
|
||||
_.each(
|
||||
this.helpers,
|
||||
(h) => {
|
||||
h.renderStyle = h.helper.renderStyle();
|
||||
|
||||
this.debugLog('ImagePreview: pm.renderStyles()', h.helper.constructor.name, JSON.parse(JSON.stringify(h.renderStyle)));
|
||||
}
|
||||
);
|
||||
|
||||
return _.fromPairs(
|
||||
_.map(
|
||||
this.helpers, (h) => ([h.helper.constructor.name, h.renderStyle])
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
getVisiblePreview(): ImagePreviewHelper | undefined {
|
||||
const found = _.find(this.helpers, (h) => h.helper.isVisible());
|
||||
|
||||
return found ? found.helper : undefined;
|
||||
}
|
||||
|
||||
|
||||
show(url: string | undefined, domain: string | undefined): ImagePreviewHelper | undefined {
|
||||
const matchedHelper = this.match(domain, url);
|
||||
|
||||
_.each(
|
||||
_.filter(this.helpers, (h) => (h !== matchedHelper)),
|
||||
(h) => h.helper.hide()
|
||||
);
|
||||
|
||||
if (!matchedHelper) {
|
||||
this.debugLog('ImagePreview: pm.show()', 'Unmatched helper', url, domain);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
matchedHelper.helper.show(url);
|
||||
return matchedHelper.helper;
|
||||
}
|
||||
|
||||
|
||||
hide(): void {
|
||||
_.each(
|
||||
this.helpers,
|
||||
(h) => {
|
||||
this.debugLog('ImagePreview: pm.hide()', h.helper.constructor.name, h.helper.isVisible());
|
||||
h.helper.hide();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
getVisibilityStatus(): Record<string, boolean> {
|
||||
return _.fromPairs(
|
||||
_.map(
|
||||
this.helpers, (h) => [h.helper.constructor.name, h.helper.isVisible()]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
setDebug(debugMode: boolean): void {
|
||||
_.each(this.helpers, (h) => h.helper.setDebug(debugMode));
|
||||
|
||||
this.debugMode = debugMode;
|
||||
}
|
||||
|
||||
|
||||
debugLog(...messages: any[]): void {
|
||||
if (this.debugMode) {
|
||||
this.parent.debugLog(...messages);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -92,6 +92,15 @@ export enum FurryPreference {
|
|||
FurriesPreferredHumansOk = 149
|
||||
}
|
||||
|
||||
export const furryPreferenceMapping = {
|
||||
[FurryPreference.FurriesOnly]: 'furries only',
|
||||
[FurryPreference.FursAndHumans]: 'loves furries and humans',
|
||||
[FurryPreference.HumansOnly]: 'humans only',
|
||||
[FurryPreference.HumansPreferredFurriesOk]: 'loves humans, likes furries',
|
||||
[FurryPreference.FurriesPreferredHumansOk]: 'loves furries, likes humans'
|
||||
};
|
||||
|
||||
|
||||
export interface GenderKinkIdMap {
|
||||
[key: number]: Kink
|
||||
}
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
<template>
|
||||
<div id="character-page-sidebar" class="card bg-light">
|
||||
<div class="card-header">
|
||||
<span class="character-name">{{ character.character.name }}</span>
|
||||
<div class="card-body">
|
||||
<img :src="avatarUrl(character.character.name)" class="character-avatar" style="margin-right:10px">
|
||||
|
||||
<div v-if="character.character.title" class="character-title">{{ character.character.title }}</div>
|
||||
<character-action-menu :character="character" @rename="showRename()" @delete="showDelete()"
|
||||
@block="showBlock()"></character-action-menu>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<img :src="avatarUrl(character.character.name)" class="character-avatar" style="margin-right:10px">
|
||||
|
||||
<div v-if="authenticated" class="d-flex justify-content-between flex-wrap character-links-block">
|
||||
<template v-if="character.is_self">
|
||||
<a :href="editUrl" class="edit-link"><i class="fa fa-fw fa-pencil-alt"></i>Edit</a>
|
||||
|
|
Loading…
Reference in New Issue