Faster preview

This commit is contained in:
Mr. Stallion 2019-10-06 18:08:22 -05:00
parent c35136c6dd
commit c8f0ffddd5
4 changed files with 250 additions and 103 deletions

View File

@ -8,11 +8,11 @@
<a @click="toggleStickyMode()" :class="{toggled: sticky}" title="Toggle Stickyness"><i class="fa fa-thumbtack"></i></a>
</div>
<webview src="about:blank" webpreferences="allowRunningInsecureContent, autoplayPolicy=no-user-gesture-required" id="image-preview-ext" ref="imagePreviewExt" class="image-preview-external" :style="{display: externalUrlVisible ? 'flex' : 'none'}"></webview>
<webview src="about:blank" webpreferences="allowRunningInsecureContent, autoplayPolicy=no-user-gesture-required" id="image-preview-ext" ref="imagePreviewExt" class="image-preview-external" :style="{display: externalPreviewHelper.isVisible() ? 'flex' : 'none'}"></webview>
<div
class="image-preview-local"
:style="{backgroundImage: `url(${internalUrl})`, display: internalUrlVisible ? 'block' : 'none'}"
:style="{backgroundImage: `url(${localPreviewHelper.getUrl()})`, display: localPreviewHelper.isVisible() ? 'block' : 'none'}"
>
</div>
</div>
@ -40,19 +40,112 @@
}
abstract class ImagePreviewHelper {
protected visible = false;
protected url: string | null = 'about:blank';
protected parent: ImagePreview;
abstract show(url: string): void;
abstract hide(): void;
abstract match(domainName: string): boolean;
constructor(parent: ImagePreview) {
this.parent = parent;
}
isVisible(): boolean {
return this.visible;
}
getUrl(): string | null {
return this.url;
}
}
class LocalImagePreviewHelper extends ImagePreviewHelper {
hide(): void {
this.visible = false;
this.url = null;
}
show(url: string): void {
this.visible = true;
this.url = url;
}
match(domainName: string): boolean {
return ((domainName === 'f-list.net') || (domainName === 'static.f-list.net'));
}
}
class ExternalImagePreviewHelper extends ImagePreviewHelper {
protected lastExternalUrl: string | null = null;
protected allowCachedUrl = true;
hide(): void {
const wasVisible = this.visible;
if (this.parent.debug)
console.log('ImagePreview: exec hide mutator');
if (wasVisible) {
const webview = this.parent.getWebview();
if (this.allowCachedUrl) {
webview.executeJavaScript(this.parent.jsMutator.getHideMutator());
} else {
webview.loadURL('about:blank');
}
this.visible = false;
}
}
show(url: string): void {
const webview = this.parent.getWebview();
try {
if ((this.allowCachedUrl) && ((webview.getURL() === url) || (url === this.lastExternalUrl))) {
if (this.parent.debug)
console.log('ImagePreview: exec re-show mutator');
webview.executeJavaScript(this.parent.jsMutator.getReShowMutator());
} else {
if (this.parent.debug)
console.log('ImagePreview: must load; skip re-show because urls don\'t match', this.url, webview.getURL());
webview.loadURL(url);
}
} catch (err) {
console.error('ImagePreview: Webview reuse error', err);
}
this.url = url;
this.lastExternalUrl = url;
this.visible = true;
}
match(domainName: string): boolean {
return !((domainName === 'f-list.net') || (domainName === 'static.f-list.net'));
}
}
@Component
export default class ImagePreview extends Vue {
private readonly MinTimePreviewVisible = 100;
visible = false;
externalUrlVisible = false;
internalUrlVisible = false;
externalUrl: string | null = null;
internalUrl: string | null = null;
lastExternalUrl = '';
externalPreviewHelper = new ExternalImagePreviewHelper(this);
localPreviewHelper = new LocalImagePreviewHelper(this);
url: string | null = null;
domain: string | undefined;
@ -61,7 +154,8 @@
runJs = true;
debug = false;
private jsMutator = new ImagePreviewMutator(this.debug);
jsMutator = new ImagePreviewMutator(this.debug);
private interval: Timer | null = null;
private exitInterval: Timer | null = null;
@ -104,16 +198,31 @@
const webview = this.getWebview();
webview.addEventListener(
'dom-ready',
'update-target-url', // 'did-navigate', // 'dom-ready',
(event: EventBusEvent) => {
const url = webview.getURL();
const js = this.jsMutator.getMutatorJsForSite(url);
const js = this.jsMutator.getMutatorJsForSite(url, 'update-target-url');
if (this.debug)
console.log('ImagePreview update-target', event, js);
if ((js) && (this.runJs))
webview.executeJavaScript(js);
}
);
webview.addEventListener(
'dom-ready', // 'did-navigate', // 'dom-ready',
(event: EventBusEvent) => {
const url = webview.getURL();
const js = this.jsMutator.getMutatorJsForSite(url, 'dom-ready');
if (this.debug)
console.log('ImagePreview dom-ready', event, js);
if ((js) && (this.runJs))
webview.executeJavaScript(js);
webview.executeJavaScript(js, true);
}
);
@ -168,13 +277,13 @@
_.each(
['did-start-loading', 'load-commit', 'will-navigate', 'did-navigate', 'did-navigate-in-page', 'update-target-url'],
['did-start-loading', 'load-commit', 'dom-ready', 'will-navigate', 'did-navigate', 'did-navigate-in-page', 'update-target-url'],
(en: string) => {
webview.addEventListener(
en,
(event: Event) => {
if (this.debug)
console.log(`ImagePreview ${en}`, event);
console.log(`ImagePreview ${en} ${Date.now()}`, event);
}
);
}
@ -200,27 +309,15 @@
hide(): void {
if (this.debug)
console.log('ImagePreview: hide', this.externalUrlVisible, this.internalUrlVisible);
console.log('ImagePreview: hide', this.externalPreviewHelper.isVisible(), this.localPreviewHelper.isVisible());
this.cancelExitTimer();
this.url = null;
this.visible = false;
if (this.externalUrlVisible) {
const webview = this.getWebview();
if (this.debug)
console.log('ImagePreview: exec hide mutator');
webview.executeJavaScript(this.jsMutator.getHideMutator());
}
this.internalUrlVisible = false;
this.externalUrlVisible = false;
// this.externalUrl = null; // 'about:blank';
this.internalUrl = null; // 'about:blank';
this.localPreviewHelper.hide();
this.externalPreviewHelper.hide();
this.exitUrl = null;
this.exitInterval = null;
@ -262,7 +359,7 @@
return;
if (this.debug)
console.log('ImagePreview: dismiss.exec', this.externalUrlVisible, this.internalUrlVisible, url);
console.log('ImagePreview: dismiss.exec', this.externalPreviewHelper.isVisible(), this.localPreviewHelper.isVisible(), 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
@ -278,7 +375,7 @@
const url = this.jsMutator.mutateUrl(initialUrl);
if (this.debug)
console.log('ImagePreview: show', this.externalUrlVisible, this.internalUrlVisible,
console.log('ImagePreview: show', this.externalPreviewHelper.isVisible(), this.localPreviewHelper.isVisible(),
this.visible, this.hasMouseMovedSince(), !!this.interval, this.sticky, url);
// console.log('SHOW');
@ -325,36 +422,13 @@
if (this.debug)
console.log('ImagePreview: show.timeout', this.url);
const isInternal = this.isInternalUrl();
this.localPreviewHelper.match(this.domain as string)
? this.localPreviewHelper.show(this.url as string)
: this.localPreviewHelper.hide();
this.internalUrlVisible = isInternal;
this.externalUrlVisible = !isInternal;
if (isInternal) {
this.internalUrl = this.url;
} else {
const webview = this.getWebview();
try {
if ((webview.getURL() === this.url) || (this.url === this.lastExternalUrl)) {
if (this.debug)
console.log('ImagePreview: exec re-show mutator');
webview.executeJavaScript(this.jsMutator.getReShowMutator());
} else {
if (this.debug)
console.log('ImagePreview: must load; skip re-show because urls don\'t match', this.url, webview.getURL());
webview.loadURL(this.url as string);
}
} catch (err) {
console.error('ImagePreview: Webview reuse error', err);
}
this.externalUrl = this.url;
this.lastExternalUrl = this.url as string;
}
this.externalPreviewHelper.match(this.domain as string)
? this.externalPreviewHelper.show(this.url as string)
: this.externalPreviewHelper.hide();
this.visible = true;
this.visibleSince = Date.now();
@ -401,14 +475,14 @@
return this.url;
}
isExternalUrl(): boolean {
/* isExternalUrl(): boolean {
// 'f-list.net' is tested here on purpose, because keeps the character URLs from being previewed
return !((this.domain === 'f-list.net') || (this.domain === 'static.f-list.net'));
}
isInternalUrl(): boolean {
return !this.isExternalUrl();
}
}*/
toggleDevMode(): void {
this.debug = !this.debug;
@ -434,14 +508,13 @@
}
reloadUrl(): void {
if (this.externalUrlVisible) {
if (this.externalPreviewHelper.isVisible()) {
const webview = this.getWebview();
webview.reload();
}
}
getWebview(): WebviewTag {
return this.$refs.imagePreviewExt as WebviewTag;
}

View File

@ -9,6 +9,7 @@ import { domain as extractDomain } from '../bbcode/core';
export interface PreviewMutator {
match: string | RegExp;
injectJs: string;
eventName: string;
urlMutator?(url: string): string;
}
@ -45,12 +46,15 @@ export class ImagePreviewMutator {
}
getMutatorJsForSite(url: string): string | undefined {
getMutatorJsForSite(url: string, eventName: string): string | undefined {
let mutator = this.matchMutator(url);
if (!mutator)
mutator = this.hostMutators['default'];
if (mutator.eventName !== eventName)
return;
return this.wrapJs(mutator.injectJs) + this.getReShowMutator();
}
@ -77,13 +81,19 @@ export class ImagePreviewMutator {
return `(() => { try { ${mutatorJs} } catch (err) { console.error('Mutator error', err); } })();`;
}
protected add(domain: string | RegExp, mutatorJs: string, urlMutator?: (url: string) => string): void {
protected add(
domain: string | RegExp,
mutatorJs: string,
urlMutator?: (url: string) => string,
eventName: string = 'update-target-url'
): void {
if (domain instanceof RegExp) {
this.regexMutators.push(
{
match: domain,
injectJs: mutatorJs,
urlMutator
urlMutator,
eventName
}
);
@ -93,30 +103,34 @@ export class ImagePreviewMutator {
this.hostMutators[domain] = {
match: domain,
injectJs: mutatorJs,
urlMutator
urlMutator,
eventName
};
}
protected init(): void {
this.add('default', this.getBaseJsMutatorScript('#video, #image, video, img'));
this.add('e621.net', this.getBaseJsMutatorScript('#image, video'));
this.add('e-hentai.org', this.getBaseJsMutatorScript('#img, video'));
this.add('gelbooru.com', this.getBaseJsMutatorScript('#image, video'));
this.add('chan.sankakucomplex.com', this.getBaseJsMutatorScript('#image, video'));
this.add('danbooru.donmai.us', this.getBaseJsMutatorScript('#image, video'));
this.add('gfycat.com', this.getBaseJsMutatorScript('video'));
this.add('gfycatporn.com', this.getBaseJsMutatorScript('video'));
this.add('youtube.com', this.getBaseJsMutatorScript('video'));
this.add('instantfap.com', this.getBaseJsMutatorScript('#post img, #post video'));
this.add('webmshare.com', this.getBaseJsMutatorScript('video'));
this.add('pornhub.com', this.getBaseJsMutatorScript('.mainPlayerDiv video, .photoImageSection img'));
this.add('sex.com', this.getBaseJsMutatorScript('.image_frame img, .image_frame video'));
this.add('redirect.media.tumblr.com', this.getBaseJsMutatorScript('picture img, picture video'));
this.add('i.imgur.com', this.getBaseJsMutatorScript('video, img'));
this.add('postimg.cc', this.getBaseJsMutatorScript('#main-image, video'));
this.add('gifsauce.com', this.getBaseJsMutatorScript('video'));
this.add('motherless.com', this.getBaseJsMutatorScript('.content video, .content img'));
this.add(/^media[0-9]\.giphy\.com$/, this.getBaseJsMutatorScript('video, img[alt]'));
this.add('default', this.getBaseJsMutatorScript(['#video, video', '#image, img']));
this.add('e621.net', this.getBaseJsMutatorScript(['video', '#image']));
this.add('e-hentai.org', this.getBaseJsMutatorScript(['video', '#img']));
this.add('gelbooru.com', this.getBaseJsMutatorScript(['video', '#image']));
this.add('chan.sankakucomplex.com', this.getBaseJsMutatorScript(['video', '#image']));
this.add('danbooru.donmai.us', this.getBaseJsMutatorScript(['video', '#image']));
this.add('gfycat.com', this.getBaseJsMutatorScript(['video']), undefined, 'dom-ready');
this.add('gfycatporn.com', this.getBaseJsMutatorScript(['video']), undefined, 'dom-ready');
this.add('youtube.com', this.getBaseJsMutatorScript(['video']), undefined, 'dom-ready');
this.add('instantfap.com', this.getBaseJsMutatorScript(['#post video', '#post img']));
this.add('webmshare.com', this.getBaseJsMutatorScript(['video']));
this.add('pornhub.com', this.getBaseJsMutatorScript(['.mainPlayerDiv video', '.photoImageSection img']));
this.add('sex.com', this.getBaseJsMutatorScript(['.image_frame video', '.image_frame img']));
this.add('redirect.media.tumblr.com', this.getBaseJsMutatorScript(['picture video', 'picture img']));
this.add('i.imgur.com', this.getBaseJsMutatorScript(['video', 'img']));
this.add('postimg.cc', this.getBaseJsMutatorScript(['video', '#main-image']));
this.add('gifsauce.com', this.getBaseJsMutatorScript(['video']));
this.add('motherless.com', this.getBaseJsMutatorScript(['.content video', '.content img']));
this.add(/^media[0-9]\.giphy\.com$/, this.getBaseJsMutatorScript(['video', 'img[alt]']));
this.add('giphy.com', this.getBaseJsMutatorScript(['video', 'a > div > img']));
this.add(/^media[0-9]\.tenor\.com$/, this.getBaseJsMutatorScript(['#view .file video', '#view .file img']));
this.add('tenor.com', this.getBaseJsMutatorScript(['#view video', '#view img']));
// tslint:disable max-line-length
this.add(
@ -124,7 +138,7 @@ export class ImagePreviewMutator {
`
const imageCount = $('.post-container video, .post-container img').length;
${this.getBaseJsMutatorScript('.post-container video, .post-container img', true)}
${this.getBaseJsMutatorScript(['.post-container video', '.post-container img'], true)}
if(imageCount > 1)
$('#flistWrapper').append('<div id="imageCount" style="position: absolute; bottom: 0; right: 0; background: green; border: 2px solid lightgreen; width: 5rem; height: 5rem; font-size: 2rem; font-weight: bold; color: white; border-radius: 5rem; margin: 0.75rem;"><div style="position: absolute; top: 50%; left: 50%; transform: translateY(-50%) translateX(-50%);">+' + (imageCount - 1) + '</div></div>');
@ -133,16 +147,19 @@ export class ImagePreviewMutator {
this.add(
'rule34.xxx',
`${this.getBaseJsMutatorScript('#image, video')}
`${this.getBaseJsMutatorScript(['video', '#image'])}
const content = document.querySelector('#content');
content.remove();
`
if (content) content.remove();
`,
undefined,
'dom-ready'
);
this.add(
'hentai-foundry.com',
this.getBaseJsMutatorScript('main video, main img'),
this.getBaseJsMutatorScript(['main video', 'main img']),
(url: string): string => {
const u = urlHelper.parse(url, true);
@ -159,14 +176,23 @@ export class ImagePreviewMutator {
);
}
getBaseJsMutatorScript(elSelector: string, skipElementRemove: boolean = false): string {
getBaseJsMutatorScript(elSelector: string[], skipElementRemove: boolean = false): string {
return `const body = document.querySelector('body');
let selected = Array.from(document.querySelectorAll('${elSelector}'))
.filter((i) => ((i.width !== 1) && (i.height !== 1)));
const selectors = ${JSON.stringify(elSelector)};
// writing this out because sometimes .map and .reduce are overridden
let selected = [];
for (selector of selectors) {
const selectedElements = (Array.from(document.querySelectorAll(selector)).filter((i) => ((i.width !== 1) && (i.height !== 1))));
selected = selected.concat(selectedElements);
}
${true /*this.debug*/ ? `console.log('Selector', '${elSelector}'); console.log('Selected', selected);` : ''}
const img = selected.shift();
${this.debug ? `console.log('Selector', '${elSelector}'); console.log('Selected', selected); console.log('Img', img);` : ''}
${true /*this.debug*/ ? `console.log('Img', img);` : ''}
if (!img) { return; }
@ -179,7 +205,18 @@ export class ImagePreviewMutator {
+ 'background-repeat: no-repeat !important; background-position: top left !important;'
+ 'opacity: 1 !important; padding: 0 !important; border: 0 !important; margin: 0 !important;';
img.remove();
try {
img.remove();
} catch(err) {
console.error('Failed remove()', err);
try {
img.parentNode.removeChild(img);
} catch(err2) {
console.error('Failed removeChild()', err2);
}
}
el.append(img);
body.append(el);
body.class = '';
@ -197,7 +234,33 @@ export class ImagePreviewMutator {
${this.debug ? "console.log('Wrapper', el);" : ''}
if (img.play) { img.muted = true; img.play(); }
document.addEventListener('DOMContentLoaded', (event) => {
${true /*this.debug*/ ? "console.log('on DOMContentLoaded');" : ''}
if (
(img.play)
&& ((!img.paused) && (!img.ended) && (!(img.currentTime > 0)))
)
{ img.muted = true; img.play(); }
});
document.addEventListener('load', (event) => {
${true /*this.debug*/ ? "console.log('on load');" : ''}
if (
(img.play)
&& ((!img.paused) && (!img.ended) && (!(img.currentTime > 0)))
)
{ img.muted = true; img.play(); }
});
try {
if (img.play) { img.muted = true; img.play(); }
} catch (err) {
console.error('Failed img.play()', err);
}
let removeList = [];
const safeIds = ['flistWrapper', 'flistError', 'flistHider'];

View File

@ -666,6 +666,10 @@
.vs, .scores {
display: none;
}
.minimize-btn {
opacity: 0.6;
}
}
h3 {
@ -689,12 +693,19 @@
.scores {
float: left;
flex: 1;
margin-right: 1rem;
margin: 0;
max-width: 25rem;
&.you {
margin-right: 1rem;
}
&.them {
margin-left: 1rem;
}
ul {
padding: 0;
padding-left: 0rem;
list-style: none;
}

View File

@ -1,6 +1,6 @@
<template>
<div id="match-report" :class="{'match-report': true, minimized: isMinimized}" v-if="(haveScores(characterMatch.you) || haveScores(characterMatch.them))">
<a class="minimize-btn" @click="toggleMinimize()"><i :class="{fa: true, 'fa-plus': isMinimized, 'fa-minus': !isMinimized}"></i></a>
<a class="minimize-btn" @click.prevent="toggleMinimize()"><i :class="{fa: true, 'fa-plus': isMinimized, 'fa-minus': !isMinimized}"></i></a>
<div class="scores you">
<h3>
@ -54,7 +54,7 @@
isMinimized = false;
@Watch('isMinimized')
@Watch('minimized')
onMinimizedChange(): void {
this.isMinimized = this.minimized;
}