From a6d904bcd18a2c9962abfd9b5b325340f6b18b0d Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 9 Feb 2022 08:59:32 -0500 Subject: speeding up rendering using bitmaps for webpages and other heavyweight docs. --- src/client/views/nodes/WebBox.tsx | 61 +++++++++++++++++++++++++++++++-------- 1 file changed, 49 insertions(+), 12 deletions(-) (limited to 'src/client/views/nodes/WebBox.tsx') diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index 9956cc36b..a1910c722 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -10,8 +10,8 @@ import { InkTool } from "../../../fields/InkField"; import { List } from "../../../fields/List"; import { listSpec, makeInterface } from "../../../fields/Schema"; import { ComputedField } from "../../../fields/ScriptField"; -import { Cast, NumCast, StrCast } from "../../../fields/Types"; -import { WebField } from "../../../fields/URLField"; +import { Cast, NumCast, StrCast, WebCast, ImageCast } from "../../../fields/Types"; +import { WebField, ImageField } from "../../../fields/URLField"; import { TraceMobx } from "../../../fields/util"; import { emptyFunction, getWordAtPoint, OmitKeys, returnFalse, returnOne, setupMoveUpEvents, smoothScroll, Utils } from "../../../Utils"; import { Docs } from "../../documents/Documents"; @@ -38,9 +38,11 @@ import { FieldView, FieldViewProps } from './FieldView'; import { LinkDocPreview } from "./LinkDocPreview"; import "./WebBox.scss"; import React = require("react"); +import { RequestOptions } from "https"; +import { VideoBox } from "./VideoBox"; +const { CreateImage } = require("./WebBoxRenderer"); const _global = (window /* browser */ || global /* node */) as any; const htmlToText = require("html-to-text"); - type WebDocument = makeInterface<[typeof documentSchema]>; const WebDocument = makeInterface(documentSchema); @@ -76,6 +78,7 @@ export class WebBox extends ViewBoxAnnotatableComponent a.textInlineAnnotations); } @computed get webField() { return Cast(this.dataDoc[this.props.fieldKey], WebField)?.url; } + @computed get webThumb() { return ImageCast(this.layoutDoc["thumb-frozen"], ImageCast(this.layoutDoc.thumb))?.url; } constructor(props: any) { super(props); @@ -108,9 +111,47 @@ export class WebBox extends ViewBoxAnnotatableComponent { this._annotationKeySuffix = () => this._urlHash + "-annotations"; // bcz: need to make sure that doc.data-annotations points to the currently active web page's annotations (this could/should be when the doc is created) - this.dataDoc[this.fieldKey + "-annotations"] = ComputedField.MakeFunction(`copyField(this["${this.fieldKey}-"+urlHash(this["${this.fieldKey}"]?.url?.toString())+"-annotations"`); - this.dataDoc[this.fieldKey + "-sidebar"] = ComputedField.MakeFunction(`copyField(this["${this.fieldKey}-"+urlHash(this["${this.fieldKey}"]?.url?.toString())+"-sidebar"`); + this.dataDoc[this.fieldKey + "-annotations"] = ComputedField.GetAnnoCopyField(this.fieldKey); + this.dataDoc[this.fieldKey + "-sidebar"] = ComputedField.GetSidebarCopyField(this.fieldKey); }); + reaction(() => this.props.isSelected(), + async (selected) => { + if (selected) { + setTimeout(action(() => { + this._scrollHeight = Math.max(this.scrollHeight, this._iframe?.contentDocument?.body.scrollHeight || 0); + if (this._initialScroll !== undefined && this._outerRef.current) { + setTimeout(() => { + this._outerRef.current!.scrollTop = this._initialScroll!; + this._initialScroll = undefined; + }); + } + })); + } else if (!this.props.isContentActive()) { + const imageBitmap = ImageCast(this.layoutDoc["thumb-frozen"])?.url.href; + if (this._iframe && !imageBitmap) { + var htmlString = this._iframe.contentDocument && new XMLSerializer().serializeToString(this._iframe.contentDocument); + if (!htmlString) { + htmlString = await (await fetch(Utils.CorsProxy(this.webField!.href))).text(); + } + const nativeWidth = NumCast(this.layoutDoc.nativeWidth); + CreateImage( + this._webUrl.endsWith("/") ? this._webUrl.substring(0, this._webUrl.length - 1) : this._webUrl, + this._iframe.contentDocument?.styleSheets ?? [], + htmlString, + nativeWidth, + nativeWidth * this.props.PanelHeight() / this.props.PanelWidth(), + NumCast(this.layoutDoc._scrollTop) + ).then + (action((dataUrl: any) => { + VideoBox.convertDataUri(dataUrl, this.layoutDoc[Id] + "-thumb", true).then( + returnedfilename => this.layoutDoc.thumb = new ImageField(returnedfilename)); + })) + .catch(function (error: any) { + console.error('oops, something went wrong!', error); + }); + } + } + }); this._disposers.autoHeight = reaction(() => this.layoutDoc._autoHeight, autoHeight => { @@ -292,12 +333,6 @@ export class WebBox extends ViewBoxAnnotatableComponent this._scrollHeight = Math.max(this.scrollHeight, iframe?.contentDocument?.body.scrollHeight || 0)), 5000); - const initialScroll = this._initialScroll; - if (initialScroll !== undefined && this._outerRef.current) { - // bcz: not sure why this happens, but if the webpage isn't ready yet, it's scroll height seems to be limited. So we need to wait tp set scroll location to what we want. - setTimeout(() => this._outerRef.current!.scrollTop = initialScroll); - this._initialScroll = undefined; - } iframe.setAttribute("enable-annotation", "true"); iframe.contentDocument.addEventListener("click", undoBatch(action((e: MouseEvent) => { let href = ""; @@ -665,7 +700,8 @@ export class WebBox extends ViewBoxAnnotatableComponent -
+ }> - {this.linkButtonInner} - - : this.linkButtonInner; + return (!Array.from(this.filteredLinks).length && !this.props.AlwaysOn) || !this.props.ScreenToLocalTransform ? (null) : +
+ { + (this.props.InMenu && (DocumentLinksButton.StartLink || this.props.StartLink)) || + (!DocumentLinksButton.LinkEditorDocView && !this.props.InMenu) ? + {title}
}> + {this.linkButtonInner} + + : this.linkButtonInner + } +
; } } diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss index 9fcd45e72..4565f8504 100644 --- a/src/client/views/nodes/DocumentView.scss +++ b/src/client/views/nodes/DocumentView.scss @@ -58,9 +58,10 @@ .documentView-audioBackground { display: inline-block; width: 10%; + height: 25; position: absolute; - top: 0px; - left: 0px; + top: 10px; + left: 10px; border-radius: 25px; background: white; opacity: 0.3; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index a83fba37d..19b16f071 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -1,5 +1,5 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx"; +import { action, computed, IReactionDisposer, observable, reaction, runInAction, trace } from "mobx"; import { observer } from "mobx-react"; import { AclAdmin, AclEdit, AclPrivate, DataSym, Doc, DocListCast, Field, Opt, StrListCast } from "../../../fields/Doc"; import { Document } from '../../../fields/documentSchemas'; @@ -148,7 +148,6 @@ export interface DocumentViewSharedProps { export interface DocumentViewProps extends DocumentViewSharedProps { // properties specific to DocumentViews but not to FieldView freezeDimensions?: boolean; - hideContent?: boolean; // whether to show content or not (used to speed up rendering complex scenes when documents are not in view) hideResizeHandles?: boolean; // whether to suppress DocumentDecorations when this document is selected hideTitle?: boolean; // forces suppression of title. e.g, treeView document labels suppress titles in case they are globally active via settings hideDecorationTitle?: boolean; // forces suppression of title. e.g, treeView document labels suppress titles in case they are globally active via settings @@ -488,6 +487,7 @@ export class DocumentViewInternal extends DocComponent this.onClickHandler.script.run({ this: this.layoutDoc, self: this.rootDoc, + _readOnly_: false, scriptContext: this.props.scriptContext, thisContainer: this.props.ContainingCollectionDoc, documentView: this.props.DocumentView(), @@ -840,13 +840,9 @@ export class DocumentViewInternal extends DocComponent +
@@ -872,11 +868,12 @@ export class DocumentViewInternal extends DocComponent this.props.isSelected(), async (selected) => { @@ -388,42 +388,46 @@ export class WebBox extends ViewBoxAnnotatableComponent { + forward = (checkAvailable?: boolean) => { const future = Cast(this.dataDoc[this.fieldKey + "-future"], listSpec("string"), []); const history = Cast(this.dataDoc[this.fieldKey + "-history"], listSpec("string"), []); - if (future.length) { - const curUrl = this._url; - this.dataDoc[this.fieldKey + "-history"] = new List([...history, this._url]); - this.dataDoc[this.fieldKey] = new WebField(new URL(future.pop()!)); - if (this._webUrl === this._url) { - this._webUrl = curUrl; - setTimeout(action(() => this._webUrl = this._url)); - } else { - this._webUrl = this._url; + if (checkAvailable) return future.length; + runInAction(() => { + if (future.length) { + const curUrl = this._url; + this.dataDoc[this.fieldKey + "-history"] = new List([...history, this._url]); + this.dataDoc[this.fieldKey] = new WebField(new URL(future.pop()!)); + if (this._webUrl === this._url) { + this._webUrl = curUrl; + setTimeout(action(() => this._webUrl = this._url)); + } else { + this._webUrl = this._url; + } + return true; } - return true; - } + }); return false; } - @action - back = () => { + back = (checkAvailable?: boolean) => { const future = Cast(this.dataDoc[this.fieldKey + "-future"], listSpec("string")); const history = Cast(this.dataDoc[this.fieldKey + "-history"], listSpec("string"), []); - if (history.length) { - const curUrl = this._url; - if (future === undefined) this.dataDoc[this.fieldKey + "-future"] = new List([this._url]); - else this.dataDoc[this.fieldKey + "-future"] = new List([...future, this._url]); - this.dataDoc[this.fieldKey] = new WebField(new URL(history.pop()!)); - if (this._webUrl === this._url) { - this._webUrl = curUrl; - setTimeout(action(() => this._webUrl = this._url)); - } else { - this._webUrl = this._url; + if (checkAvailable) return history.length; + runInAction(() => { + if (history.length) { + const curUrl = this._url; + if (future === undefined) this.dataDoc[this.fieldKey + "-future"] = new List([this._url]); + else this.dataDoc[this.fieldKey + "-future"] = new List([...future, this._url]); + this.dataDoc[this.fieldKey] = new WebField(new URL(history.pop()!)); + if (this._webUrl === this._url) { + this._webUrl = curUrl; + setTimeout(action(() => this._webUrl = this._url)); + } else { + this._webUrl = this._url; + } + return true; } - return true; - } + }); return false; } @@ -487,8 +491,8 @@ export class WebBox extends ViewBoxAnnotatableComponent this.submitURL(this._keyInput.current!.value)} onDragOver={e => e.stopPropagation()} onDrop={this.onWebUrlDrop}> GO - - + +
); @@ -699,7 +703,7 @@ export class WebBox extends ViewBoxAnnotatableComponent; return (
+ style={{ pointerEvents: this.pointerEvents(), display: !this.props.isSelected() && !this.isAnyChildContentActive() ? "none" : undefined }} >