Better caching for character data

This commit is contained in:
Mr. Stallion 2019-09-24 12:53:43 -07:00
parent 379604bfb1
commit c35136c6dd
11 changed files with 352 additions and 152 deletions

View File

@ -48,11 +48,12 @@
</div>
</modal>
<modal :buttons="false" ref="profileViewer" dialogClass="profile-viewer">
<character-page :authenticated="true" :oldApi="true" :name="profileName" :image-preview="true"></character-page>
<character-page :authenticated="true" :oldApi="true" :name="profileName" :image-preview="true" ref="characterPage"></character-page>
<template slot="title">
{{profileName}}
<a class="btn" @click="openProfileInBrowser"><i class="fa fa-external-link-alt"/></a>
<a class="btn" @click="openConversation"><i class="fa fa-comment"></i></a>
<a class="btn" @click="reloadCharacter"><i class="fa fa-sync" /></a>
</template>
</modal>
<modal :action="l('fixLogs.action')" ref="fixLogsModal" @submit="fixLogs" buttonClass="btn-danger">
@ -280,6 +281,12 @@
(this.$refs.profileViewer as any).hide();
}
reloadCharacter(): void {
// tslint:disable-next-line: no-any no-unsafe-any
(this.$refs.characterPage as any).reload();
}
get styling(): string {
try {
return `<style>${fs.readFileSync(path.join(__dirname, `themes/${this.settings.theme}.css`))}</style>`;

View File

@ -588,7 +588,7 @@ export class Matcher {
if ((theirAge < 16) && (ageplayScore === null))
return Matcher.formatKinkScore(KinkPreference.No, `ages of ${theirAge}`);
if ((theirAge < 18) && (underageScore !== null))
if ((theirAge < 18) && (theirAge >= 16) && (underageScore !== null))
return Matcher.formatKinkScore(underageScore, `ages of ${theirAge}`);
const yourAge = this.yourAnalysis.age;
@ -742,8 +742,7 @@ export class Matcher {
}
);
// tslint:disable-next-line: strict-type-predicates
return (foundSpeciesId === null) ? null : parseInt(foundSpeciesId, 10);
return foundSpeciesId;
}

View File

@ -1,10 +1,21 @@
import * as _ from 'lodash';
import core from '../chat/core';
import { Character as ComplexCharacter } from '../site/character_page/interfaces';
import {Character as ComplexCharacter, CharacterFriend, CharacterGroup, GuestbookState} from '../site/character_page/interfaces';
import { AsyncCache } from './async-cache';
import { Matcher, Score, Scoring } from './matcher';
import { PermanentIndexedStore } from './store/sql-store';
import {CharacterImage} from '../interfaces';
export interface MetaRecord {
images: CharacterImage[] | null;
groups: CharacterGroup[] | null;
friends: CharacterFriend[] | null;
guestbook: GuestbookState | null;
lastFetched: Date | null;
}
export interface CountRecord {
groupCount: number | null;
@ -18,7 +29,8 @@ export interface CharacterCacheRecord {
lastFetched: Date;
added: Date;
matchScore: number;
counts?: CountRecord;
// counts?: CountRecord;
meta?: MetaRecord;
}
export class ProfileCache extends AsyncCache<CharacterCacheRecord> {
@ -63,18 +75,42 @@ export class ProfileCache extends AsyncCache<CharacterCacheRecord> {
cacheRecord.lastFetched = new Date(pd.lastFetched * 1000);
cacheRecord.added = new Date(pd.firstSeen * 1000);
cacheRecord.counts = {
cacheRecord.meta = {
lastFetched: pd.lastMetaFetched ? new Date(pd.lastMetaFetched) : null,
groups: pd.groups,
friends: pd.friends,
images: pd.images,
guestbook: pd.guestbook
};
/* cacheRecord.counts = {
lastCounted: pd.lastCounted,
groupCount: pd.groupCount,
friendCount: pd.friendCount,
guestbookCount: pd.guestbookCount
};
}; */
return cacheRecord;
}
async registerCount(name: string, counts: CountRecord): Promise<void> {
// async registerCount(name: string, counts: CountRecord): Promise<void> {
// const record = await this.get(name);
//
// if (!record) {
// // coward's way out
// return;
// }
//
// record.counts = counts;
//
// if (this.store) {
// await this.store.updateProfileCounts(name, counts.guestbookCount, counts.friendCount, counts.groupCount);
// }
// }
async registerMeta(name: string, meta: MetaRecord): Promise<void> {
const record = await this.get(name);
if (!record) {
@ -82,10 +118,10 @@ export class ProfileCache extends AsyncCache<CharacterCacheRecord> {
return;
}
record.counts = counts;
record.meta = meta;
if (this.store) {
await this.store.updateProfileCounts(name, counts.guestbookCount, counts.friendCount, counts.groupCount);
await this.store.updateProfileMeta(name, meta.images, meta.guestbook, meta.friends, meta.groups);
}
}

View File

@ -1,8 +1,9 @@
import * as _ from 'lodash';
import { Character as ComplexCharacter } from '../../site/character_page/interfaces';
import {Character as ComplexCharacter, CharacterFriend, CharacterGroup, GuestbookState} from '../../site/character_page/interfaces';
import { CharacterAnalysis } from '../matcher';
import { PermanentIndexedStore, ProfileRecord } from './sql-store';
import {CharacterImage} from '../../interfaces';
async function promisifyRequest<T>(req: IDBRequest): Promise<T> {
@ -82,14 +83,21 @@ export class IndexedStore implements PermanentIndexedStore {
age: ca.age,
domSubRole: null, // domSubRole
position: null, // position
lastCounted: null,
guestbookCount: null,
friendCount: null,
groupCount: null
lastMetaFetched: null,
guestbook: null,
images: null,
friends: null,
groups: null
// lastCounted: null,
// guestbookCount: null,
// friendCount: null,
// groupCount: null
};
return (existing)
? _.merge(existing, data, _.pick(existing, ['firstSeen', 'lastCounted', 'guestbookCount', 'friendCount', 'groupCount']))
? _.merge(existing, data, _.pick(existing, ['firstSeen', 'lastMetaFetched', 'guestbook', 'images', 'friends', 'groups']))
: data;
}
@ -140,6 +148,41 @@ export class IndexedStore implements PermanentIndexedStore {
// console.log('IDX update counts', name, data);
}
async updateProfileMeta(
name: string,
images: CharacterImage[] | null,
guestbook: GuestbookState | null,
friends: CharacterFriend[] | null,
groups: CharacterGroup[] | null
): Promise<void> {
const existing = await this.getProfile(name);
if (!existing) {
return;
}
const data = _.merge(
existing,
{
lastFetched: Math.round(Date.now() / 1000),
guestbook,
friends,
groups,
images
}
);
const tx = this.db.transaction(IndexedStore.STORE_NAME, 'readwrite');
const store = tx.objectStore(IndexedStore.STORE_NAME);
const putRequest = store.put(data);
// tslint:disable-next-line no-any
await promisifyRequest<any>(putRequest);
// console.log('IDX update counts', name, data);
}
async start(): Promise<void> {
// empty
}

View File

@ -1,9 +1,10 @@
// tslint:disable-next-line:no-duplicate-imports
import * as path from 'path';
import core from '../../chat/core';
// import * as path from 'path';
// import core from '../../chat/core';
import { Orientation, Gender, FurryPreference, Species, CharacterAnalysis } from '../matcher';
import { Character as ComplexCharacter } from '../../site/character_page/interfaces';
import { Orientation, Gender, FurryPreference, Species } from '../matcher';
import {Character as ComplexCharacter, CharacterFriend, CharacterGroup, GuestbookState} from '../../site/character_page/interfaces';
import {CharacterImage} from '../../interfaces';
export interface ProfileRecord {
id: string;
@ -18,10 +19,17 @@ export interface ProfileRecord {
age: number | null;
domSubRole: number | null;
position: number | null;
lastCounted: number | null;
guestbookCount: number | null;
friendCount: number | null;
groupCount: number | null;
// lastCounted: number | null;
// guestbookCount: number | null;
// friendCount: number | null;
// groupCount: number | null;
lastMetaFetched: number | null;
guestbook: GuestbookState | null;
images: CharacterImage[] | null;
friends: CharacterFriend[] | null;
groups: CharacterGroup[] | null;
}
// export type Statement = any;
@ -30,66 +38,83 @@ export interface ProfileRecord {
export interface PermanentIndexedStore {
getProfile(name: string): Promise<ProfileRecord | undefined>;
storeProfile(c: ComplexCharacter): Promise<void>;
updateProfileCounts(name: string, guestbookCount: number | null, friendCount: number | null, groupCount: number | null): Promise<void>;
updateProfileMeta(
name: string,
images: CharacterImage[] | null,
guestbook: GuestbookState | null,
friends: CharacterFriend[] | null,
groups: CharacterGroup[] | null
): Promise<void>;
start(): Promise<void>;
stop(): Promise<void>;
}
export abstract class SqlStore implements PermanentIndexedStore {
protected dbFile: string;
protected checkpointTimer: NodeJS.Timer | null = null;
constructor(dbName: string = 'fchat-ascending.sqlite') {
this.dbFile = path.join(core.state.generalSettings!.logDirectory, dbName);
}
// tslint:disable-next-line: prefer-function-over-method
protected toProfileId(name: string): string {
return name.toLowerCase();
}
abstract getProfile(name: string): Promise<ProfileRecord | undefined>;
abstract start(): Promise<void>;
abstract stop(): Promise<void>;
// tslint:disable-next-line no-any
protected abstract run(statementName: 'stmtStoreProfile' | 'stmtUpdateCounts', data: any[]): Promise<void>;
async storeProfile(c: ComplexCharacter): Promise<void> {
const ca = new CharacterAnalysis(c.character);
const data = [
this.toProfileId(c.character.name),
c.character.name,
JSON.stringify(c),
Math.round(Date.now() / 1000),
Math.round(Date.now() / 1000),
ca.gender,
ca.orientation,
ca.furryPreference,
ca.species,
ca.age,
null, // domSubRole
null // position
];
await this.run('stmtStoreProfile', data);
}
async updateProfileCounts(
name: string,
guestbookCount: number | null,
friendCount: number | null,
groupCount: number | null
): Promise<void> {
await this.run(
'stmtUpdateCounts',
[Math.round(Date.now() / 1000), guestbookCount, friendCount, groupCount, this.toProfileId(name)]
);
}
}
// export abstract class SqlStore implements PermanentIndexedStore {
// protected dbFile: string;
// protected checkpointTimer: NodeJS.Timer | null = null;
//
// constructor(dbName: string = 'fchat-ascending.sqlite') {
// this.dbFile = path.join(core.state.generalSettings!.logDirectory, dbName);
// }
//
// // tslint:disable-next-line: prefer-function-over-method
// protected toProfileId(name: string): string {
// return name.toLowerCase();
// }
//
// abstract getProfile(name: string): Promise<ProfileRecord | undefined>;
//
// abstract start(): Promise<void>;
// abstract stop(): Promise<void>;
//
// // tslint:disable-next-line no-any
// protected abstract run(statementName: 'stmtStoreProfile' | 'stmtUpdateCounts', data: any[]): Promise<void>;
//
//
// async storeProfile(c: ComplexCharacter): Promise<void> {
// const ca = new CharacterAnalysis(c.character);
//
// const data = [
// this.toProfileId(c.character.name),
// c.character.name,
// JSON.stringify(c),
// Math.round(Date.now() / 1000),
// Math.round(Date.now() / 1000),
// ca.gender,
// ca.orientation,
// ca.furryPreference,
// ca.species,
// ca.age,
// null, // domSubRole
// null // position
// ];
//
// await this.run('stmtStoreProfile', data);
// }
//
// async updateProfileMeta(
// name: string,
// images: CharacterImage[] | null,
// guestbook: GuestbookState | null,
// friends: CharacterFriend[] | null,
// groups: CharacterGroup[] | null
// ): Promise<void> {
// throw new Error('Not implemented');
// }
//
// // async updateProfileCounts(
// // name: string,
// // guestbookCount: number | null,
// // friendCount: number | null,
// // groupCount: number | null
// // ): Promise<void> {
// // await this.run(
// // 'stmtUpdateCounts',
// // [Math.round(Date.now() / 1000), guestbookCount, friendCount, groupCount, this.toProfileId(name)]
// // );
// // }
// }
//

View File

@ -1,24 +1,31 @@
# F-Chat Ascending
# F-Chat Rising
This repository contains a modified version of the mainline F-Chat 3.0 client.
This repository contains a heavily customized version of the mainline F-Chat 3.0 client.
## Key Differences
1. **Profile matching** automatically compares your profile with others to determine with whom you are compatible.
1. **Automatic ad posting** repeatedly posts and rotates ads on selected channels.
1. **Link previews** pop up to show a preview of the image when you hover your mouse over a link.
### Details
* Ads view
* Highlight ads from characters most interesting to you
* View a character's recent ads
* View characters' recent ads
* Ad auto-posting
* Manage channel's ad settings via "Tab Settings"
* Manage channel ad settings via "Tab Settings"
* Automatically re-post ads every 11-18 minutes (randomized) for up to 180 minutes
* Rotate multiple ads on a single channel
* Rotate multiple ads on a single channel by saving multiple ads in "Tab Settings"
* Ad ratings
* LFP ads are automatically rated and matched against your profile
* Link previews
* Hover cursor over any `[url]` to see a preview of it
* Middle click any `[url]` to turn the preview into a sticky / interactive mode
* Profile
* Kinks are auto-compared when profile is loaded
* Kinks are auto-compared when viewing character profile
* Custom kink explanations can be expanded inline
* Custom kinks are highlighted
* Gender, anthro/human preference, age, and sexual preference are highlighted if compatible or incompatible
@ -28,12 +35,26 @@ This repository contains a modified version of the mainline F-Chat 3.0 client.
* Less informative side bar details (views, contact) are separated and shown in a less prominent way
* Cleaner guestbook view
* Character Search
* Display pairing score on search results
* Current search filters are listed on the search dialog
* Search results are sorted based on match scores
* Display match score in search results
* Current search filters are listed in the search dialog
* Search filters can be reset
* General
* Character profiles, guestbooks, friend lists, and image lists are cached for faster access.
* Open conversation dialog for typing in a character name
## FAQ
1. Non-binary gender preference matches rely on kink preferences.
1. 'Underage' kink is considered to apply to characters aged 16 or above.
1. 'Ageplay' kink is considered to apply to characters aged 16 or below.
1. 'Older characters' and 'younger characters' kink preferences are interpreted as age difference of 5+ years.
## Todo / Ideas
* Collect data on ads / responses to determine which ads work best
* Preview mode should allow detaching from the main window
* Split chat view
* Improvements to log browsing
@ -41,11 +62,13 @@ This repository contains a modified version of the mainline F-Chat 3.0 client.
* Which channels my chart partner is on?
* Reposition ad settings and toggle
* Cache image list, guestbook pages
* Save character status messages
* Bug: Invalid Ticket
* Bug: Posting on the same second
* Bug: Images tab count is off
# F-List Exported
This repository contains the open source parts of F-list and F-Chat 3.0.
All necessary files to build F-Chat 3.0 as an Electron, mobile or web application are included.
@ -66,9 +89,7 @@ All necessary files to build F-Chat 3.0 as an Electron, mobile or web applicatio
### Building on Windows
```
npm install --global --production windows-build-tools
npm install --global --production --vs2015 --add-python-to-path windows-build-tools
npm install --global --production --add-python-to-path windows-build-tools node-gyp
npm install --global --production --vs2015 --add-python-to-path windows-build-tools node-gyp
```
### Packaging

View File

@ -27,10 +27,10 @@
<tabs class="card-header-tabs" v-model="tab">
<span>Overview</span>
<span>Info</span>
<span v-if="!oldApi">Groups <span class="tab-count" v-if="groupCount !== null">({{ groupCount }})</span></span>
<span v-if="!oldApi">Groups <span class="tab-count" v-if="groups !== null">({{ groups.length }})</span></span>
<span>Images <span class="tab-count">({{ character.character.image_count }})</span></span>
<span v-if="character.settings.guestbook">Guestbook <span class="tab-count" v-if="guestbookPostCount !== null">({{ guestbookPostCount }})</span></span>
<span v-if="character.is_self || character.settings.show_friends">Friends <span class="tab-count" v-if="friendCount !== null">({{ friendCount }})</span></span>
<span v-if="character.settings.guestbook">Guestbook <span class="tab-count" v-if="guestbook !== null">({{ guestbook.posts.length }})</span></span>
<span v-if="character.is_self || character.settings.show_friends">Friends <span class="tab-count" v-if="friends !== null">({{ friends.length }})</span></span>
</tabs>
</div>
<div class="card-body">
@ -75,7 +75,7 @@
import { CharacterCacheRecord } from '../../learn/profile-cache';
import * as Utils from '../utils';
import {methods, Store} from './data_store';
import {Character, SharedStore} from './interfaces';
import {Character, CharacterFriend, CharacterGroup, GuestbookState, SharedStore} from './interfaces';
import DateDisplay from '../../components/date_display.vue';
import Tabs from '../../components/tabs';
@ -89,9 +89,10 @@
import core from '../../chat/core';
import { Matcher, MatchReport } from '../../learn/matcher';
import MatchReportView from './match-report.vue';
import {CharacterImage} from '../../interfaces';
const CHARACTER_CACHE_EXPIRE = 7 * 24 * 60 * 60 * 1000;
const CHARACTER_COUNT_CACHE_EXPIRE = 10 * 24 * 60 * 60 * 1000;
const CHARACTER_CACHE_EXPIRE = 7 * 24 * 60 * 60 * 1000; // 7 days (milliseconds)
const CHARACTER_META_CACHE_EXPIRE = 10 * 24 * 60 * 60 * 1000; // 10 days (milliseconds)
interface ShowableVueTab extends Vue {
show?(): void
@ -127,10 +128,14 @@
error = '';
tab = '0';
guestbookPostCount: number | null = null;
/* guestbookPostCount: number | null = null;
friendCount: number | null = null;
groupCount: number | null = null;
groupCount: number | null = null; */
guestbook: GuestbookState | null = null;
friends: CharacterFriend[] | null = null;
groups: CharacterGroup[] | null = null;
images: CharacterImage[] | null = null;
selfCharacter: Character | undefined;
@ -178,7 +183,13 @@
);
}
async load(mustLoad: boolean = true): Promise<void> {
async reload(): Promise<void> {
await this.load(true, true);
}
async load(mustLoad: boolean = true, skipCache: boolean = false): Promise<void> {
this.loading = true;
this.error = '';
@ -194,7 +205,7 @@
due.push(this.loadSelfCharacter());
if((mustLoad) || (this.character === undefined))
due.push(this._getCharacter());
due.push(this._getCharacter(skipCache));
await Promise.all(due);
} catch(e) {
@ -208,73 +219,83 @@
}
async countGuestbookPosts(): Promise<void> {
async updateGuestbook(): Promise<void> {
try {
if ((!this.character) || (!_.get(this.character, 'settings.guestbook'))) {
this.guestbookPostCount = null;
this.guestbook = null;
return;
}
const guestbookState = await methods.guestbookPageGet(this.character.character.id, 1, false);
this.guestbookPostCount = guestbookState.posts.length;
// `${guestbookState.posts.length}${guestbookState.nextPage ? '+' : ''}`;
this.guestbook = await methods.guestbookPageGet(this.character.character.id, 1, false);
} catch (err) {
console.error(err);
this.guestbookPostCount = null;
this.guestbook = null;
}
}
async countGroups(): Promise<void> {
async updateGroups(): Promise<void> {
try {
if ((!this.character) || (this.oldApi)) {
this.groupCount = null;
this.groups = null;
return;
}
const groups = await methods.groupsGet(this.character.character.id);
this.groupCount = groups.length; // `${groups.length}`;
this.groups = await methods.groupsGet(this.character.character.id);
} catch (err) {
console.error(err);
this.groupCount = null;
console.error('Update groups', err);
this.groups = null;
}
}
async countFriends(): Promise<void> {
async updateFriends(): Promise<void> {
try {
if (
(!this.character)
|| (!this.character.is_self) && (!this.character.settings.show_friends)
) {
this.friendCount = null;
this.friends = null;
return;
}
const friends = await methods.friendsGet(this.character.character.id);
this.friendCount = friends.length; // `${friends.length}`;
this.friends = await methods.friendsGet(this.character.character.id);
} catch (err) {
console.error(err);
this.friendCount = null;
console.error('Update friends', err);
this.friends = null;
}
}
async updateCounts(name: string): Promise<void> {
await this.countGuestbookPosts();
await this.countFriends();
await this.countGroups();
async updateImages(): Promise<void> {
try {
if (!this.character) {
this.images = null;
return;
}
await core.cache.profileCache.registerCount(
this.images = await methods.imagesGet(this.character.character.id);
} catch (err) {
console.error('Update images', err);
this.images = null;
}
}
async updateMeta(name: string): Promise<void> {
await this.updateImages();
await this.updateGuestbook();
await this.updateFriends();
await this.updateGroups();
await core.cache.profileCache.registerMeta(
name,
{
lastCounted: Date.now() / 1000,
groupCount: this.groupCount,
friendCount: this.friendCount,
guestbookCount: this.guestbookPostCount
lastFetched: new Date(),
groups: this.groups,
friends: this.friends,
guestbook: this.guestbook,
images: this.images
}
);
}
@ -318,11 +339,12 @@
return null;
}
private async _getCharacter(): Promise<void> {
private async _getCharacter(skipCache: boolean = false): Promise<void> {
this.character = undefined;
this.friendCount = null;
this.groupCount = null;
this.guestbookPostCount = null;
this.friends = null;
this.groups = null;
this.guestbook = null;
this.images = null;
if (!this.name) {
return;
@ -330,7 +352,7 @@
const cache = await this.fetchCharacterCache();
this.character = (cache)
this.character = (cache && !skipCache)
? cache.character
: await methods.characterData(this.name, this.characterid, false);
@ -338,18 +360,19 @@
standardParser.inlines = this.character.character.inlines;
if (
(cache)
&& (cache.counts)
&& (cache.counts.lastCounted)
&& ((Date.now() / 1000) - cache.counts.lastCounted < CHARACTER_COUNT_CACHE_EXPIRE)
(cache && !skipCache)
&& (cache.meta)
&& (cache.meta.lastFetched)
&& (Date.now() - cache.meta.lastFetched.getTime() < CHARACTER_META_CACHE_EXPIRE)
) {
this.guestbookPostCount = cache.counts.guestbookCount;
this.friendCount = cache.counts.friendCount;
this.groupCount = cache.counts.groupCount;
this.guestbook = cache.meta.guestbook;
this.friends = cache.meta.friends;
this.groups = cache.meta.groups;
this.images = cache.meta.images;
} else {
// No awaits on these on purpose:
// tslint:disable-next-line no-floating-promises
this.updateCounts(this.name);
this.updateMeta(this.name);
}
// console.log('LoadChar', this.name, this.character);

View File

@ -16,6 +16,7 @@
import * as Utils from '../utils';
import {methods} from './data_store';
import {Character, CharacterFriend} from './interfaces';
import core from '../../chat/core';
@Component
export default class FriendsView extends Vue {
@ -35,7 +36,7 @@
this.error = '';
this.shown = true;
this.loading = true;
this.friends = await methods.friendsGet(this.character.character.id);
this.friends = await this.resolveFriends();
} catch(e) {
this.shown = false;
if(Utils.isJSONError(e))
@ -44,5 +45,15 @@
}
this.loading = false;
}
async resolveFriends(): Promise<CharacterFriend[]> {
const c = await core.cache.profileCache.get(this.character.character.name);
if ((c) && (c.meta) && (c.meta.friends)) {
return c.meta.friends;
}
return methods.friendsGet(this.character.character.id);
}
}
</script>

View File

@ -16,6 +16,7 @@
import * as Utils from '../utils';
import {methods} from './data_store';
import {Character, CharacterGroup} from './interfaces';
import core from '../../chat/core';
@Component
export default class GroupsView extends Vue {
@ -36,7 +37,7 @@
this.error = '';
this.shown = true;
this.loading = true;
this.groups = await methods.groupsGet(this.character.character.id);
this.groups = await this.resolveGroups();
} catch(e) {
this.shown = false;
if(Utils.isJSONError(e))
@ -45,5 +46,15 @@
}
this.loading = false;
}
async resolveGroups(): Promise<CharacterGroup[]> {
const c = await core.cache.profileCache.get(this.character.character.name);
if ((c) && (c.meta) && (c.meta.groups)) {
return c.meta.groups;
}
return methods.groupsGet(this.character.character.id);
}
}
</script>

View File

@ -30,9 +30,10 @@
import Vue from 'vue';
import * as Utils from '../utils';
import {methods, Store} from './data_store';
import {Character, GuestbookPost} from './interfaces';
import {Character, GuestbookPost, GuestbookState} from './interfaces';
import GuestbookPostView from './guestbook_post.vue';
import core from '../../chat/core';
@Component({
components: {'guestbook-post': GuestbookPostView}
@ -73,7 +74,7 @@
async getPage(): Promise<void> {
try {
this.loading = true;
const guestbookState = await methods.guestbookPageGet(this.character.character.id, this.page, this.unapprovedOnly);
const guestbookState = await this.resolvePage();
this.posts = guestbookState.posts;
this.hasNextPage = guestbookState.nextPage;
this.canEdit = guestbookState.canEdit;
@ -103,8 +104,20 @@
}
}
async resolvePage(): Promise<GuestbookState> {
if (this.page === 1) {
const c = await core.cache.profileCache.get(this.character.character.name);
if ((c) && (c.meta) && (c.meta.guestbook)) {
return c.meta.guestbook;
}
}
return methods.guestbookPageGet(this.character.character.id, this.page, this.unapprovedOnly);
}
async show(): Promise<void> {
return this.getPage();
await this.getPage();
}
}
</script>

View File

@ -36,6 +36,7 @@
import * as Utils from '../utils';
import {methods} from './data_store';
import {Character} from './interfaces';
import core from '../../chat/core';
@Component
export default class ImagesView extends Vue {
@ -58,7 +59,7 @@
this.error = '';
this.shown = true;
this.loading = true;
this.images = await methods.imagesGet(this.character.character.id);
this.images = await this.resolveImages();
} catch(e) {
this.shown = false;
if(Utils.isJSONError(e))
@ -68,6 +69,16 @@
this.loading = false;
}
async resolveImages(): Promise<CharacterImage[]> {
const c = await core.cache.profileCache.get(this.character.character.name);
if ((c) && (c.meta) && (c.meta.images)) {
return c.meta.images;
}
return methods.imagesGet(this.character.character.id);
}
handleImageClick(e: MouseEvent, image: CharacterImage): void {
if(this.usePreview) {
this.previewImage = methods.imageUrl(image);