diff options
Diffstat (limited to 'src/client/views/nodes/WebBox.tsx')
| -rw-r--r-- | src/client/views/nodes/WebBox.tsx | 527 |
1 files changed, 274 insertions, 253 deletions
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index d0d638e98..a898398a1 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -1,18 +1,20 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction, trace } from 'mobx'; +import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as WebRequest from 'web-request'; -import { Doc, DocListCast, HeightSym, Opt, WidthSym } from '../../../fields/Doc'; +import { Doc, DocListCast, Field, HeightSym, Opt, WidthSym } from '../../../fields/Doc'; import { Id } from '../../../fields/FieldSymbols'; import { HtmlField } from '../../../fields/HtmlField'; import { InkTool } from '../../../fields/InkField'; import { List } from '../../../fields/List'; import { listSpec } from '../../../fields/Schema'; -import { Cast, ImageCast, NumCast, StrCast } from '../../../fields/Types'; +import { Cast, ImageCast, NumCast, StrCast, WebCast } from '../../../fields/Types'; import { ImageField, WebField } from '../../../fields/URLField'; import { TraceMobx } from '../../../fields/util'; -import { emptyFunction, getWordAtPoint, OmitKeys, returnEmptyString, returnFalse, returnOne, setupMoveUpEvents, smoothScroll, StopEvent, Utils } from '../../../Utils'; +import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, getWordAtPoint, removeStyleSheetRule, returnFalse, returnOne, returnTrue, returnZero, setupMoveUpEvents, smoothScroll, StopEvent, Utils } from '../../../Utils'; import { Docs, DocUtils } from '../../documents/Documents'; +import { DocumentManager } from '../../util/DocumentManager'; +import { DragManager } from '../../util/DragManager'; import { ScriptingGlobals } from '../../util/ScriptingGlobals'; import { SnappingManager } from '../../util/SnappingManager'; import { undoBatch, UndoManager } from '../../util/UndoManager'; @@ -21,21 +23,21 @@ import { CollectionFreeFormView } from '../collections/collectionFreeForm/Collec import { ContextMenu } from '../ContextMenu'; import { ContextMenuProps } from '../ContextMenuItem'; import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent'; -import { DocumentDecorations } from '../DocumentDecorations'; import { Colors } from '../global/globalEnums'; import { LightboxView } from '../LightboxView'; import { MarqueeAnnotator } from '../MarqueeAnnotator'; import { AnchorMenu } from '../pdf/AnchorMenu'; import { Annotation } from '../pdf/Annotation'; +import { GPTPopup } from '../pdf/GPTPopup/GPTPopup'; import { SidebarAnnos } from '../SidebarAnnos'; import { StyleProp } from '../StyleProvider'; -import { DocFocusOptions, DocumentView, DocumentViewInternal, DocumentViewProps } from './DocumentView'; +import { DocComponentView, DocFocusOptions, DocumentView, DocumentViewProps, OpenWhere } from './DocumentView'; import { FieldView, FieldViewProps } from './FieldView'; import { LinkDocPreview } from './LinkDocPreview'; -import { VideoBox } from './VideoBox'; +import { PinProps, PresBox } from './trails'; import './WebBox.scss'; import React = require('react'); -import { DragManager } from '../../util/DragManager'; +import { RefField } from '../../../fields/RefField'; const { CreateImage } = require('./WebBoxRenderer'); const _global = (window /* browser */ || global) /* node */ as any; const htmlToText = require('html-to-text'); @@ -46,6 +48,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps } public static openSidebarWidth = 250; public static sidebarResizerWidth = 5; + static webStyleSheet = addStyleSheet(); private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean, hide: boolean) => void); private _setBrushViewer: undefined | ((view: { width: number; height: number; panX: number; panY: number }) => void); private _mainCont: React.RefObject<HTMLDivElement> = React.createRef(); @@ -53,19 +56,19 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps private _disposers: { [name: string]: IReactionDisposer } = {}; private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef(); private _keyInput = React.createRef<HTMLInputElement>(); - private _initialScroll: Opt<number> = NumCast(this.layoutDoc.thumbScrollTop, NumCast(this.layoutDoc.scrollTop)); + private _initialScroll: Opt<number> = NumCast(this.layoutDoc.thumbScrollTop, NumCast(this.layoutDoc.layout_scrollTop)); private _sidebarRef = React.createRef<SidebarAnnos>(); private _searchRef = React.createRef<HTMLInputElement>(); private _searchString = ''; + private _scrollTimer: any; private get _getAnchor() { return AnchorMenu.Instance?.GetAnchor; } - @observable private _webUrl = ''; // url of the src parameter of the embedded iframe but not necessarily the rendered page - eg, when following a link, the rendered page changes but we don't wan the src parameter to also change as that would cause an unnecessary re-render. + @observable private _webUrl = ''; // url of the src parameter of the embedded iframe but not necessarily the rendered page - eg, when following a link, the rendered page changes but we don't want the src parameter to also change as that would cause an unnecessary re-render. @observable private _hackHide = false; // apparently changing the value of the 'sandbox' prop doesn't necessarily apply it to the active iframe. so thisforces the ifrmae to be rebuilt when allowScripts is toggled @observable private _searching: boolean = false; @observable private _showSidebar = false; - @observable private _scrollTimer: any; @observable private _webPageHasBeenRendered = false; @observable private _overlayAnnoInfo: Opt<Doc>; @observable private _marqueeing: number[] | undefined; @@ -90,7 +93,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps return this.allAnnotations.filter(a => a.textInlineAnnotations); } @computed get webField() { - return Cast(this.dataDoc[this.props.fieldKey], WebField)?.url; + return Cast(this.rootDoc[this.props.fieldKey], WebField)?.url; } @computed get webThumb() { return ( @@ -98,7 +101,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps ImageCast( this.layoutDoc['thumb-frozen'], ImageCast( - this.layoutDoc.thumbScrollTop === this.layoutDoc._scrollTop && this.layoutDoc.thumbNativeWidth === NumCast(this.layoutDoc.nativeWidth) && this.layoutDoc.thumbNativeHeight === NumCast(this.layoutDoc.nativeHeight) + this.layoutDoc.thumbScrollTop === this.layoutDoc._layout_scrollTop && this.layoutDoc.thumbNativeWidth === NumCast(this.layoutDoc.nativeWidth) && this.layoutDoc.thumbNativeHeight === NumCast(this.layoutDoc.nativeHeight) ? this.layoutDoc.thumb : undefined ) @@ -140,81 +143,65 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps }; updateThumb = async () => { - const imageBitmap = ImageCast(this.layoutDoc['thumb-frozen'])?.url.href; - const scrollTop = NumCast(this.layoutDoc._scrollTop); + if (!this._iframe) return; + const scrollTop = NumCast(this.layoutDoc._layout_scrollTop); const nativeWidth = NumCast(this.layoutDoc.nativeWidth); const nativeHeight = (nativeWidth * this.props.PanelHeight()) / this.props.PanelWidth(); - if ( - !this.rootDoc.thumbLockout && - !this.props.dontRegisterView && - this._iframe && - !imageBitmap && - (scrollTop !== this.layoutDoc.thumbScrollTop || nativeWidth !== this.layoutDoc.thumbNativeWidth || nativeHeight !== this.layoutDoc.thumbNativeHeight) - ) { - var htmlString = this._iframe.contentDocument && new XMLSerializer().serializeToString(this._iframe.contentDocument); - if (!htmlString) { - htmlString = await (await fetch(Utils.CorsProxy(this.webField!.href))).text(); - } - this.layoutDoc.thumb = undefined; - this.rootDoc.thumbLockout = true; // lock to prevent multiple thumb updates. - CreateImage(this._webUrl.endsWith('/') ? this._webUrl.substring(0, this._webUrl.length - 1) : this._webUrl, this._iframe.contentDocument?.styleSheets ?? [], htmlString, nativeWidth, nativeHeight, scrollTop) - .then((data_url: any) => { - if (data_url.includes('<!DOCTYPE')) { - console.log('BAD DATA IN THUMB CREATION'); - return; - } - VideoBox.convertDataUri(data_url, this.layoutDoc[Id] + '-icon' + new Date().getTime(), true, this.layoutDoc[Id] + '-icon').then(returnedfilename => - setTimeout( - action(() => { - this.rootDoc.thumbLockout = false; - this.layoutDoc.thumb = new ImageField(returnedfilename); - this.layoutDoc.thumbScrollTop = scrollTop; - this.layoutDoc.thumbNativeWidth = nativeWidth; - this.layoutDoc.thumbNativeHeight = nativeHeight; - }), - 500 - ) - ); - }) - .catch(function (error: any) { - console.error('oops, something went wrong!', error); - }); + var htmlString = this._iframe.contentDocument && new XMLSerializer().serializeToString(this._iframe.contentDocument); + if (!htmlString) { + htmlString = await (await fetch(Utils.CorsProxy(this.webField!.href))).text(); } + this.layoutDoc.thumb = undefined; + this.rootDoc.thumbLockout = true; // lock to prevent multiple thumb updates. + CreateImage(this._webUrl.endsWith('/') ? this._webUrl.substring(0, this._webUrl.length - 1) : this._webUrl, this._iframe.contentDocument?.styleSheets ?? [], htmlString, nativeWidth, nativeHeight, scrollTop) + .then((data_url: any) => { + if (data_url.includes('<!DOCTYPE')) { + console.log('BAD DATA IN THUMB CREATION'); + return; + } + Utils.convertDataUri(data_url, this.layoutDoc[Id] + '-icon' + new Date().getTime(), true, this.layoutDoc[Id] + '-icon').then(returnedfilename => + setTimeout( + action(() => { + this.rootDoc.thumbLockout = false; + this.layoutDoc.thumb = new ImageField(returnedfilename); + this.layoutDoc.thumbScrollTop = scrollTop; + this.layoutDoc.thumbNativeWidth = nativeWidth; + this.layoutDoc.thumbNativeHeight = nativeHeight; + }), + 500 + ) + ); + }) + .catch(function (error: any) { + console.error('oops, something went wrong!', error); + }); }; + async componentDidMount() { this.props.setContentView?.(this); // this tells the DocumentView that this WebBox is the "content" of the document. this allows the DocumentView to call WebBox relevant methods to configure the UI (eg, show back/forward buttons) runInAction(() => { - this._annotationKeySuffix = () => this._urlHash + '-annotations'; + this._annotationKeySuffix = () => this._urlHash + '_annotations'; const reqdFuncs: { [key: string]: string } = {}; - // 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) - reqdFuncs[this.fieldKey + '-annotations'] = `copyField(this["${this.fieldKey}-"+urlHash(this["${this.fieldKey}"]?.url?.toString())+"-annotations"`; - reqdFuncs[this.fieldKey + '-sidebar'] = `copyField(this["${this.fieldKey}-"+urlHash(this["${this.fieldKey}"]?.url?.toString())+"-sidebar"`; + // 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) + reqdFuncs[this.fieldKey + '_annotations'] = `copyField(this["${this.fieldKey}_"+urlHash(this["${this.fieldKey}"]?.url?.toString())+"_annotations"])`; + reqdFuncs[this.fieldKey + '_annotations-setter'] = `this["${this.fieldKey}_"+urlHash(this["${this.fieldKey}"]?.url?.toString())+"_annotations"] = value`; + reqdFuncs[this.fieldKey + '_sidebar'] = `copyField(this["${this.fieldKey}_"+urlHash(this["${this.fieldKey}"]?.url?.toString())+"_sidebar"])`; DocUtils.AssignScripts(this.dataDoc, {}, reqdFuncs); }); - reaction( - () => this.props.isSelected(true) || this.isAnyChildContentActive() || Doc.isBrushedHighlightedDegree(this.props.Document), - async selected => { - if (selected) { - this._webPageHasBeenRendered = true; - } else if ( - (!this.props.isContentActive(true) || SnappingManager.GetIsDragging()) && // update thumnail when unselected AND (no child annotation is active OR we've started dragging the document in which case no additional deselect will occur so this is the only chance to update the thumbnail) - !this.props.docViewPath().lastElement()?.docView?._pendingDoubleClick && // don't create a thumbnail when double-clicking to enter lightbox because thumbnail will be empty - LightboxView.LightboxDoc !== this.rootDoc - ) { - // don't create a thumbnail if entering Lightbox from maximize either, since thumb will be empty. - this.updateThumb(); - } - }, - { fireImmediately: this.props.isSelected(true) || this.isAnyChildContentActive() || (Doc.isBrushedHighlightedDegreeUnmemoized(this.props.Document) ? true : false) } + this._disposers.urlchange = reaction( + () => WebCast(this.rootDoc.data), + url => { + this.submitURL(url.url.href, false, false); + } ); - this._disposers.autoHeight = reaction( - () => this.layoutDoc._autoHeight, - autoHeight => { - if (autoHeight) { - this.layoutDoc._nativeHeight = NumCast(this.props.Document[this.props.fieldKey + '-nativeHeight']); - this.props.setHeight?.(NumCast(this.props.Document[this.props.fieldKey + '-nativeHeight']) * (this.props.NativeDimScaling?.() || 1)); + this._disposers.layout_autoHeight = reaction( + () => this.layoutDoc._layout_autoHeight, + layout_autoHeight => { + if (layout_autoHeight) { + this.layoutDoc._nativeHeight = NumCast(this.props.Document[this.props.fieldKey + '_nativeHeight']); + this.props.setHeight?.(NumCast(this.props.Document[this.props.fieldKey + '_nativeHeight']) * (this.props.NativeDimScaling?.() || 1)); } } ); @@ -238,7 +225,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps } this._disposers.scrollReaction = reaction( - () => NumCast(this.layoutDoc._scrollTop), + () => NumCast(this.layoutDoc._layout_scrollTop), scrollTop => { const viewTrans = StrCast(this.Document._viewTransition); const durationMiliStr = viewTrans.match(/([0-9]*)ms/); @@ -257,6 +244,10 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps // this._iframe?.contentDocument?.removeEventListener("pointerup", this.iframeUp); } + private _selectionText: string = ''; + private _selectionContent: DocumentFragment | undefined; + selectionText = () => this._selectionText; + selectionContent = () => this._selectionContent; @action createTextAnnotation = (sel: Selection, selRange: Range | undefined) => { if (this._mainCont.current && selRange) { @@ -276,6 +267,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps } } //this._selectionText = selRange.cloneContents().textContent || ""; + this._selectionContent = selRange?.cloneContents(); + this._selectionText = this._selectionContent?.textContent || ''; // clear selection if (sel.empty) sel.empty(); // Chrome @@ -283,31 +276,42 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps return this._savedAnnotations; }; - menuControls = () => this.urlEditor; // controls to be added to the top bar when a document of this type is selected - setBrushViewer = (func?: (view: { width: number; height: number; panX: number; panY: number }) => void) => (this._setBrushViewer = func); brushView = (view: { width: number; height: number; panX: number; panY: number }) => this._setBrushViewer?.(view); - scrollFocus = (doc: Doc, options: DocFocusOptions) => { - if (this._url && StrCast(doc.webUrl) !== this._url) this.submitURL(StrCast(doc.webUrl), options.instant); - if (DocListCast(this.props.Document[this.fieldKey + '-sidebar']).includes(doc) && !this.SidebarShown) { - this.toggleSidebar(options.instant); - } - if (this._sidebarRef?.current?.makeDocUnfiltered(doc)) return 1; - if (doc !== this.rootDoc && this._outerRef.current) { + focus = (anchor: Doc, options: DocFocusOptions) => { + if (anchor !== this.rootDoc && this._outerRef.current) { const windowHeight = this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1); - const scrollTo = Utils.scrollIntoView(NumCast(doc.y), doc[HeightSym](), NumCast(this.layoutDoc._scrollTop), windowHeight, windowHeight * 0.1, Math.max(NumCast(doc.y) + doc[HeightSym](), this.getScrollHeight())); - if (scrollTo !== undefined && this._initialScroll === undefined) { - const focusSpeed = options.instant ? 0 : options.zoomTime ?? 500; - this.goTo(scrollTo, focusSpeed, options.easeFunc); - return focusSpeed; - } else if (!this._webPageHasBeenRendered || !this.getScrollHeight() || this._initialScroll !== undefined) { - this._initialScroll = scrollTo; + const scrollTo = Utils.scrollIntoView(NumCast(anchor.y), anchor[HeightSym](), NumCast(this.layoutDoc._layout_scrollTop), windowHeight, windowHeight * 0.1, Math.max(NumCast(anchor.y) + anchor[HeightSym](), this._scrollHeight)); + if (scrollTo !== undefined) { + if (this._initialScroll === undefined) { + const focusTime = options.zoomTime ?? 500; + this.goTo(scrollTo, focusTime, options.easeFunc); + return focusTime; + } else { + this._initialScroll = scrollTo; + } } } - return undefined; }; - getAnchor = (addAsAnnotation: boolean) => { + @action + getView = (doc: Doc) => { + if (Doc.AreProtosEqual(doc, this.rootDoc)) return new Promise<Opt<DocumentView>>(res => res(this.props.DocumentView?.())); + if (this.rootDoc.layout_fieldKey === 'layout_icon') this.props.DocumentView?.().iconify(); + const webUrl = WebCast(doc.presData)?.url; + if (this._url && webUrl && webUrl.href !== this._url) this.setData(webUrl.href); + if (this._sidebarRef?.current?.makeDocUnfiltered(doc) && !this.SidebarShown) this.toggleSidebar(false); + return new Promise<Opt<DocumentView>>(res => DocumentManager.Instance.AddViewRenderedCb(doc, dv => res(dv))); + }; + + sidebarAddDocTab = (doc: Doc, where: OpenWhere) => { + if (DocListCast(this.props.Document[this.props.fieldKey + '_sidebar']).includes(doc) && !this.SidebarShown) { + this.toggleSidebar(false); + return true; + } + return this.props.addDocTab(doc, where); + }; + getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => { let ele: Opt<HTMLDivElement> = undefined; try { const contents = this._iframe?.contentWindow?.getSelection()?.getRangeAt(0).cloneContents(); @@ -318,14 +322,16 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps } catch (e) {} const anchor = this._getAnchor(this._savedAnnotations, false) ?? - Docs.Create.WebanchorDocument(this._url, { - title: StrCast(this.rootDoc.title + ' ' + this.layoutDoc._scrollTop), - y: NumCast(this.layoutDoc._scrollTop), - unrendered: true, + Docs.Create.WebConfigDocument({ + title: StrCast(this.rootDoc.title + ' ' + this.layoutDoc._layout_scrollTop), + y: NumCast(this.layoutDoc._layout_scrollTop), + annotationOn: this.rootDoc, }); + PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), scrollable: pinProps?.pinData ? true : false, pannable: true } }, this.rootDoc); anchor.text = ele?.textContent ?? ''; anchor.textHtml = ele?.innerHTML; - addAsAnnotation && this.addDocumentWrapper(anchor); + //addAsAnnotation && + this.addDocumentWrapper(anchor); return anchor; }; @@ -341,8 +347,13 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps const scale = (this.props.NativeDimScaling?.() || 1) * mainContBounds.scale; const sel = this._iframe.contentWindow.getSelection(); if (sel) { + this._selectionText = sel.toString(); + AnchorMenu.Instance.setSelectedText(sel.toString()); this._textAnnotationCreator = () => this.createTextAnnotation(sel, !sel.isCollapsed ? sel.getRangeAt(0) : undefined); - AnchorMenu.Instance.jumpTo(e.clientX * scale + mainContBounds.translateX, e.clientY * scale + mainContBounds.translateY - NumCast(this.layoutDoc._scrollTop) * scale); + AnchorMenu.Instance.jumpTo(e.clientX * scale + mainContBounds.translateX, e.clientY * scale + mainContBounds.translateY - NumCast(this.layoutDoc._layout_scrollTop) * scale); + // Changing which document to add the annotation to (the currently selected WebBox) + GPTPopup.Instance.setSidebarId(`${this.props.fieldKey}_${this._urlHash}_sidebar`); + GPTPopup.Instance.addDoc = this.sidebarAddDocument; } } }; @@ -354,7 +365,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps const word = getWordAtPoint(e.target, e.clientX, e.clientY); this._setPreviewCursor?.(e.clientX, e.clientY, false, true); MarqueeAnnotator.clearAnnotations(this._savedAnnotations); - e.button !== 2 && (this._marqueeing = [e.clientX * scale + mainContBounds.translateX, e.clientY * scale + mainContBounds.translateY - NumCast(this.layoutDoc._scrollTop) * scale]); + e.button !== 2 && (this._marqueeing = [e.clientX * scale + mainContBounds.translateX, e.clientY * scale + mainContBounds.translateY - NumCast(this.layoutDoc._layout_scrollTop) * scale]); if (word || ((e.target as any) || '').className.includes('rangeslider') || (e.target as any)?.onclick || (e.target as any)?.parentNode?.onclick) { setTimeout( action(() => (this._marqueeing = undefined)), @@ -376,16 +387,13 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps ContextMenu.Instance.setIgnoreEvents(true); } }; - - getScrollHeight = () => this._scrollHeight; - isFirefox = () => { return 'InstallTrigger' in window; // navigator.userAgent.indexOf("Chrome") !== -1; }; iframeClick = () => this._iframeClick; iframeScaling = () => 1 / this.props.ScreenToLocalTransform().Scale; - addStyleSheet(document: any, styleType: string = 'text/css') { + addWebStyleSheet(document: any, styleType: string = 'text/css') { if (document) { const style = document.createElement('style'); style.type = styleType; @@ -393,7 +401,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps return (sheets as any).sheet; } } - addStyleSheetRule(sheet: any, selector: any, css: any, selectorPrefix = '.') { + addWebStyleSheetRule(sheet: any, selector: any, css: any, selectorPrefix = '.') { const propText = typeof css === 'string' ? css @@ -410,8 +418,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps if (this._initialScroll !== undefined) { this.setScrollPos(this._initialScroll); } - - this.addStyleSheetRule(this.addStyleSheet(this._iframe?.contentDocument), '::selection', { color: 'white', background: 'orange' }, ''); + this._scrollHeight = this._iframe?.contentDocument?.body?.scrollHeight ?? 0; + this.addWebStyleSheetRule(this.addWebStyleSheet(this._iframe?.contentDocument), '::selection', { color: 'white', background: 'orange' }, ''); let href: Opt<string>; try { @@ -435,12 +443,19 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps .replace('search&', 'search?') .replace('?gbv=1', ''); } - this.submitURL(requrlraw, undefined, true); + this.setData(requrlraw); } const iframeContent = iframe?.contentDocument; if (iframeContent) { iframeContent.addEventListener('pointerup', this.iframeUp); iframeContent.addEventListener('pointerdown', this.iframeDown); + // iframeContent.addEventListener( + // 'wheel', + // e => { + // e.ctrlKey && e.preventDefault(); + // }, + // { passive: false } + // ); const initHeights = () => { this._scrollHeight = Math.max(this._scrollHeight, (iframeContent.body.children[0] as any)?.scrollHeight || 0); if (this._scrollHeight) { @@ -454,7 +469,6 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps action(() => initHeights), 5000 ); - iframe.setAttribute('enable-annotation', 'true'); iframeContent.addEventListener( 'click', undoBatch( @@ -465,49 +479,63 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps } const origin = this.webField?.origin; if (href && origin) { + const batch = UndoManager.StartBatch('webclick'); e.stopPropagation(); - setTimeout(() => this.submitURL(href.replace(Utils.prepend(''), origin))); + setTimeout(() => { + this.setData(href.replace(Utils.prepend(''), origin)); + batch.end(); + }); if (this._outerRef.current) { - this._outerRef.current.scrollTop = NumCast(this.layoutDoc._scrollTop); + this._outerRef.current.scrollTop = NumCast(this.layoutDoc._layout_scrollTop); this._outerRef.current.scrollLeft = 0; } } }) ) ); - iframe.contentDocument.addEventListener('wheel', this.iframeWheel, false); - //iframe.contentDocument.addEventListener('scroll', () => !this.active() && this._iframe && (this._iframe.scrollTop = NumCast(this.layoutDoc._scrollTop), false)); + iframe.contentDocument.addEventListener('wheel', this.iframeWheel, { passive: false }); } }; @action - iframeWheel = (e: any) => { + iframeWheel = (e: WheelEvent) => { if (!this._scrollTimer) { - this._scrollTimer = setTimeout( - action(() => (this._scrollTimer = undefined)), - 250 - ); // this turns events off on the iframe which allows scrolling to change direction smoothly + addStyleSheetRule(WebBox.webStyleSheet, 'webBox-iframe', { 'pointer-events': 'none' }); + this._scrollTimer = setTimeout(() => { + this._scrollTimer = undefined; + clearStyleSheetRules(WebBox.webStyleSheet); + }, 250); // this turns events off on the iframe which allows scrolling to change direction smoothly + } + if (e.ctrlKey) { + if (this._innerCollectionView) { + this._innerCollectionView.zoom(e.screenX, e.screenY, e.deltaY); + const offset = e.clientY - NumCast(this.layoutDoc._layout_scrollTop); + this.layoutDoc.freeform_panY = offset - offset / NumCast(this.layoutDoc._freeform_scale) + NumCast(this.layoutDoc._layout_scrollTop) - NumCast(this.layoutDoc._layout_scrollTop) / NumCast(this.layoutDoc._freeform_scale); + } + e.preventDefault(); } }; @action setDashScrollTop = (scrollTop: number, timeout: number = 250) => { const iframeHeight = Math.max(scrollTop, this._scrollHeight - this.panelHeight()); - this._scrollTimer && clearTimeout(this._scrollTimer); - this._scrollTimer = setTimeout( - action(() => { - this._scrollTimer = undefined; - const newScrollTop = scrollTop > iframeHeight ? iframeHeight : scrollTop; - if (!LinkDocPreview.LinkInfo && this._outerRef.current && newScrollTop !== this.layoutDoc.thumbScrollTop && (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this.props.docViewPath()))) { - this.layoutDoc.thumb = undefined; - this.layoutDoc.thumbScrollTop = undefined; - this.layoutDoc.thumbNativeWidth = undefined; - this.layoutDoc.thumbNativeHeight = undefined; - this.layoutDoc.scrollTop = this._outerRef.current.scrollTop = newScrollTop; - } else if (this._outerRef.current) this._outerRef.current.scrollTop = newScrollTop; - }), - timeout - ); + if (this._scrollTimer) { + clearTimeout(this._scrollTimer); + clearStyleSheetRules(WebBox.webStyleSheet); + } + addStyleSheetRule(WebBox.webStyleSheet, 'webBox-iframe', { 'pointer-events': 'none' }); + this._scrollTimer = setTimeout(() => { + clearStyleSheetRules(WebBox.webStyleSheet); + this._scrollTimer = undefined; + const newScrollTop = scrollTop > iframeHeight ? iframeHeight : scrollTop; + if (!LinkDocPreview.LinkInfo && this._outerRef.current && newScrollTop !== this.layoutDoc.thumbScrollTop && (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this.props.docViewPath()))) { + this.layoutDoc.thumb = undefined; + this.layoutDoc.thumbScrollTop = undefined; + this.layoutDoc.thumbNativeWidth = undefined; + this.layoutDoc.thumbNativeHeight = undefined; + this.layoutDoc.layout_scrollTop = this._outerRef.current.scrollTop = newScrollTop; + } else if (this._outerRef.current) this._outerRef.current.scrollTop = newScrollTop; + }, timeout); }; goTo = (scrollTop: number, duration: number, easeFunc: 'linear' | 'ease' | undefined) => { @@ -523,14 +551,15 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps }; forward = (checkAvailable?: boolean) => { - const future = Cast(this.dataDoc[this.fieldKey + '-future'], listSpec('string'), []); - const history = Cast(this.dataDoc[this.fieldKey + '-history'], listSpec('string'), []); + const future = Cast(this.rootDoc[this.fieldKey + '_future'], listSpec('string'), []); + const history = Cast(this.rootDoc[this.fieldKey + '_history'], listSpec('string'), []); if (checkAvailable) return future.length; runInAction(() => { if (future.length) { const curUrl = this._url; - this.dataDoc[this.fieldKey + '-history'] = new List<string>([...history, this._url]); + this.dataDoc[this.fieldKey + '_history'] = new List<string>([...history, this._url]); this.dataDoc[this.fieldKey] = new WebField(new URL(future.pop()!)); + this._scrollHeight = 0; if (this._webUrl === this._url) { this._webUrl = curUrl; setTimeout(action(() => (this._webUrl = this._url))); @@ -544,15 +573,16 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps }; back = (checkAvailable?: boolean) => { - const future = Cast(this.dataDoc[this.fieldKey + '-future'], listSpec('string')); - const history = Cast(this.dataDoc[this.fieldKey + '-history'], listSpec('string'), []); + const future = Cast(this.rootDoc[this.fieldKey + '_future'], listSpec('string')); + const history = Cast(this.rootDoc[this.fieldKey + '_history'], listSpec('string'), []); if (checkAvailable) return history.length; runInAction(() => { if (history.length) { const curUrl = this._url; - if (future === undefined) this.dataDoc[this.fieldKey + '-future'] = new List<string>([this._url]); - else this.dataDoc[this.fieldKey + '-future'] = new List<string>([...future, this._url]); + if (future === undefined) this.dataDoc[this.fieldKey + '_future'] = new List<string>([this._url]); + else this.dataDoc[this.fieldKey + '_future'] = new List<string>([...future, this._url]); this.dataDoc[this.fieldKey] = new WebField(new URL(history.pop()!)); + this._scrollHeight = 0; if (this._webUrl === this._url) { this._webUrl = curUrl; setTimeout(action(() => (this._webUrl = this._url))); @@ -579,23 +609,18 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps if (!newUrl) return; if (!newUrl.startsWith('http')) newUrl = 'http://' + newUrl; try { - const future = Cast(this.dataDoc[this.fieldKey + '-future'], listSpec('string')); - const history = Cast(this.dataDoc[this.fieldKey + '-history'], listSpec('string')); - const url = this.webField?.toString(); - if (url && !preview) { - this.dataDoc[this.fieldKey + '-history'] = new List<string>([...(history || []), url]); - this.layoutDoc._scrollTop = 0; + if (!preview) { if (this._webPageHasBeenRendered) { this.layoutDoc.thumb = undefined; this.layoutDoc.thumbScrollTop = undefined; this.layoutDoc.thumbNativeWidth = undefined; this.layoutDoc.thumbNativeHeight = undefined; } - future && (future.length = 0); } if (!preview) { - this.dataDoc[this.fieldKey] = new WebField(new URL(newUrl)); - !dontUpdateIframe && (this._webUrl = this._url); + if (!dontUpdateIframe) { + this._webUrl = this._url; + } } } catch (e) { console.log('WebBox URL error:' + this._url); @@ -609,53 +634,34 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps const uri = dataTransfer.getData('text/uri-list'); const url = uri || html || this._url || ''; const newurl = url.startsWith(window.location.origin) ? url.replace(window.location.origin, this._url?.match(/http[s]?:\/\/[^\/]*/)?.[0] || '') : url; - this.submitURL(newurl); + this.setData(newurl); e.stopPropagation(); }; + + @action + setData = (data: Field | Promise<RefField | undefined>) => { + if (!(typeof data === 'string') && !(data instanceof WebField)) return false; + if (Field.toString(data) === this._url) return false; + this._scrollHeight = 0; + const oldUrl = this._url; + const history = Cast(this.rootDoc[this.fieldKey + '_history'], listSpec('string'), []); + const weburl = new WebField(Field.toString(data)); + this.dataDoc[this.fieldKey + '_future'] = new List<string>([]); + this.dataDoc[this.fieldKey + '_history'] = new List<string>([...(history || []), oldUrl]); + this.dataDoc[this.fieldKey] = weburl; + return true; + }; onWebUrlValueKeyDown = (e: React.KeyboardEvent) => { - e.key === 'Enter' && this.submitURL(this._keyInput.current!.value); + if (e.key === 'Enter') this.setData(this._keyInput.current!.value); e.stopPropagation(); }; - @computed get urlEditor() { - return ( - <div className="collectionMenu-webUrlButtons" onDrop={this.onWebUrlDrop} onDragOver={e => e.preventDefault()}> - <input - className="collectionMenu-urlInput" - key={this._url} - placeholder="ENTER URL" - defaultValue={this._url} - onDrop={this.onWebUrlDrop} - onDragOver={e => e.preventDefault()} - onKeyDown={this.onWebUrlValueKeyDown} - onClick={e => { - this._keyInput.current!.select(); - e.stopPropagation(); - }} - ref={this._keyInput} - /> - <div style={{ display: 'flex', flexDirection: 'row', justifyContent: 'space-between', maxWidth: '250px' }}> - <button className="submitUrl" onClick={() => this.submitURL(this._keyInput.current!.value)} onDragOver={e => e.stopPropagation()} onDrop={this.onWebUrlDrop}> - GO - </button> - <button className="submitUrl" onClick={() => this.back}> - {' '} - <FontAwesomeIcon icon="caret-left" size="lg" />{' '} - </button> - <button className="submitUrl" onClick={() => this.forward}> - {' '} - <FontAwesomeIcon icon="caret-right" size="lg" />{' '} - </button> - </div> - </div> - ); - } - specificContextMenu = (e: React.MouseEvent | PointerEvent): void => { const cm = ContextMenu.Instance; const funcs: ContextMenuProps[] = []; if (!cm.findByDescription('Options...')) { - !Doc.noviceMode && funcs.push({ description: (this.layoutDoc.useCors ? "Don't Use" : 'Use') + ' Cors', event: () => (this.layoutDoc.useCors = !this.layoutDoc.useCors), icon: 'snowflake' }); + !Doc.noviceMode && + funcs.push({ description: (this.layoutDoc[this.fieldKey + '_useCors'] ? "Don't Use" : 'Use') + ' Cors', event: () => (this.layoutDoc[this.fieldKey + '_useCors'] = !this.layoutDoc[this.fieldKey + '_useCors']), icon: 'snowflake' }); funcs.push({ description: (this.layoutDoc.allowScripts ? 'Prevent' : 'Allow') + ' Scripts', event: () => { @@ -668,16 +674,17 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps icon: 'snowflake', }); funcs.push({ - description: (!this.layoutDoc.forceReflow ? 'Force' : 'Prevent') + ' Reflow', + description: (!this.layoutDoc.layout_forceReflow ? 'Force' : 'Prevent') + ' Reflow', event: () => { - const nw = !this.layoutDoc.forceReflow ? undefined : Doc.NativeWidth(this.layoutDoc) - this.sidebarWidth() / (this.props.NativeDimScaling?.() || 1); - this.layoutDoc.forceReflow = !nw; + const nw = !this.layoutDoc.layout_forceReflow ? undefined : Doc.NativeWidth(this.layoutDoc) - this.sidebarWidth() / (this.props.NativeDimScaling?.() || 1); + this.layoutDoc.layout_forceReflow = !nw; if (nw) { - Doc.SetInPlace(this.layoutDoc, this.fieldKey + '-nativeWidth', nw, true); + Doc.SetInPlace(this.layoutDoc, this.fieldKey + '_nativeWidth', nw, true); } }, icon: 'snowflake', }); + funcs.push({ description: 'Create Thumbnail', event: () => this.updateThumb(), icon: 'portrait' }); cm.addItem({ description: 'Options...', subitems: funcs, icon: 'asterisk' }); } }; @@ -718,57 +725,43 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps }; @computed get urlContent() { - if (this._hackHide || (this.webThumb && !this._webPageHasBeenRendered && LightboxView.LightboxDoc !== this.rootDoc)) return null; - this.props.thumbShown?.(); - const field = this.dataDoc[this.props.fieldKey]; - let view; + setTimeout( + action(() => { + if (this._initialScroll === undefined && !this._webPageHasBeenRendered) { + this.setScrollPos(NumCast(this.layoutDoc.thumbScrollTop, NumCast(this.layoutDoc.layout_scrollTop))); + } + this._webPageHasBeenRendered = true; + }) + ); + const field = this.rootDoc[this.props.fieldKey]; if (field instanceof HtmlField) { - view = <span className="webBox-htmlSpan" contentEditable onPointerDown={e => e.stopPropagation()} dangerouslySetInnerHTML={{ __html: field.html }} />; - } else if (field instanceof WebField) { - const url = this.layoutDoc.useCors ? Utils.CorsProxy(this._webUrl) : this._webUrl; - view = ( + return <span className="webBox-htmlSpan" contentEditable onPointerDown={e => e.stopPropagation()} dangerouslySetInnerHTML={{ __html: field.html }} />; + } + if (field instanceof WebField) { + const url = this.layoutDoc[this.fieldKey + '_useCors'] ? Utils.CorsProxy(this._webUrl) : this._webUrl; + return ( <iframe className="webBox-iframe" - enable-annotation={'true'} - style={{ pointerEvents: this._scrollTimer ? 'none' : undefined }} ref={action((r: HTMLIFrameElement | null) => (this._iframe = r))} src={url} onLoad={this.iframeLoaded} + scrolling="no" // ugh.. on windows, I get an inner scroll bar for the iframe's body even though the scrollHeight should be set to the full height of the document. // the 'allow-top-navigation' and 'allow-top-navigation-by-user-activation' attributes are left out to prevent iframes from redirecting the top-level Dash page // sandbox={"allow-forms allow-modals allow-orientation-lock allow-pointer-lock allow-popups allow-popups-to-escape-sandbox allow-presentation allow-same-origin allow-scripts"} />; sandbox={`${this.layoutDoc.allowScripts ? 'allow-scripts' : ''} allow-forms allow-modals allow-orientation-lock allow-pointer-lock allow-popups allow-popups-to-escape-sandbox allow-presentation allow-same-origin`} /> ); - } else { - view = ( - <iframe - className="webBox-iframe" - enable-annotation={'true'} - style={{ pointerEvents: this._scrollTimer ? 'none' : undefined }} // if we allow pointer events when scrolling is on, then reversing direction does not work smoothly - ref={action((r: HTMLIFrameElement | null) => (this._iframe = r))} - src={'https://crossorigin.me/https://cs.brown.edu'} - /> - ); } - setTimeout( - action(() => { - this._scrollHeight = Math.max(this._scrollHeight, this._iframe && this._iframe.contentDocument && this._iframe.contentDocument.body ? this._iframe.contentDocument.body.scrollHeight : 0); - if (this._initialScroll === undefined && !this._webPageHasBeenRendered) { - this.setScrollPos(NumCast(this.layoutDoc.thumbScrollTop, NumCast(this.layoutDoc.scrollTop))); - } - this._webPageHasBeenRendered = true; - }) - ); - return view; + return <iframe className="webBox-iframe" ref={action((r: HTMLIFrameElement | null) => (this._iframe = r))} src={'https://crossorigin.me/https://cs.brown.edu'} />; } addDocumentWrapper = (doc: Doc | Doc[], annotationKey?: string) => { - (doc instanceof Doc ? [doc] : doc).forEach(doc => (doc.webUrl = this._url)); + (doc instanceof Doc ? [doc] : doc).forEach(doc => (doc.presData = new WebField(this._url))); return this.addDocument(doc, annotationKey); }; sidebarAddDocument = (doc: Doc | Doc[], sidebarKey?: string) => { - if (!this.layoutDoc._showSidebar) this.toggleSidebar(); + if (!this.layoutDoc._layout_showSidebar) this.toggleSidebar(); return this.addDocumentWrapper(doc, sidebarKey); }; @observable _draggingSidebar = false; @@ -784,15 +777,15 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps .ScreenToLocalTransform() .scale(this.props.NativeDimScaling?.() || 1) .transformDirection(delta[0], delta[1]); - const nativeWidth = NumCast(this.layoutDoc[this.fieldKey + '-nativeWidth']); - const nativeHeight = NumCast(this.layoutDoc[this.fieldKey + '-nativeHeight']); + const nativeWidth = NumCast(this.layoutDoc[this.fieldKey + '_nativeWidth']); + const nativeHeight = NumCast(this.layoutDoc[this.fieldKey + '_nativeHeight']); const curNativeWidth = NumCast(this.layoutDoc.nativeWidth, nativeWidth); const ratio = (curNativeWidth + ((onButton ? 1 : -1) * localDelta[0]) / (this.props.NativeDimScaling?.() || 1)) / nativeWidth; if (ratio >= 1) { this.layoutDoc.nativeWidth = nativeWidth * ratio; this.layoutDoc.nativeHeight = nativeHeight * (1 + ratio); onButton && (this.layoutDoc._width = this.layoutDoc[WidthSym]() + localDelta[0]); - this.layoutDoc._showSidebar = nativeWidth !== this.layoutDoc._nativeWidth; + this.layoutDoc._layout_showSidebar = nativeWidth !== this.layoutDoc._nativeWidth; } return false; }), @@ -814,7 +807,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps title="Toggle Sidebar" style={{ display: !this.props.isContentActive() ? 'none' : undefined, - top: StrCast(this.rootDoc._showTitle) === 'title' ? 20 : 5, + top: StrCast(this.layoutDoc._layout_showTitle) === 'title' ? 20 : 5, backgroundColor: this.SidebarShown ? Colors.MEDIUM_BLUE : Colors.BLACK, }} onPointerDown={e => this.sidebarBtnDown(e, true)}> @@ -825,12 +818,12 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps @observable _previewNativeWidth: Opt<number> = undefined; @observable _previewWidth: Opt<number> = undefined; toggleSidebar = action((preview: boolean = false) => { - var nativeWidth = NumCast(this.layoutDoc[this.fieldKey + '-nativeWidth']); + var nativeWidth = NumCast(this.layoutDoc[this.fieldKey + '_nativeWidth']); if (!nativeWidth) { - const defaultNativeWidth = this.dataDoc[this.fieldKey] instanceof WebField ? 850 : this.Document[WidthSym](); + const defaultNativeWidth = this.rootDoc[this.fieldKey] instanceof WebField ? 850 : this.Document[WidthSym](); Doc.SetNativeWidth(this.dataDoc, Doc.NativeWidth(this.dataDoc) || defaultNativeWidth); Doc.SetNativeHeight(this.dataDoc, Doc.NativeHeight(this.dataDoc) || (this.Document[HeightSym]() / this.Document[WidthSym]()) * defaultNativeWidth); - nativeWidth = NumCast(this.layoutDoc[this.fieldKey + '-nativeWidth']); + nativeWidth = NumCast(this.layoutDoc[this.fieldKey + '_nativeWidth']); } const sideratio = ((!this.layoutDoc.nativeWidth || this.layoutDoc.nativeWidth === nativeWidth ? WebBox.openSidebarWidth : 0) + nativeWidth) / nativeWidth; const pdfratio = ((!this.layoutDoc.nativeWidth || this.layoutDoc.nativeWidth === nativeWidth ? WebBox.openSidebarWidth + WebBox.sidebarResizerWidth : 0) + nativeWidth) / nativeWidth; @@ -840,27 +833,42 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps this._previewWidth = (this.layoutDoc[WidthSym]() * nativeWidth * sideratio) / curNativeWidth; this._showSidebar = true; } else { - this.layoutDoc._showSidebar = !this.layoutDoc._showSidebar; + this.layoutDoc._layout_showSidebar = !this.layoutDoc._layout_showSidebar; this.layoutDoc._width = (this.layoutDoc[WidthSym]() * nativeWidth * pdfratio) / curNativeWidth; - if (!this.layoutDoc._showSidebar && !(this.dataDoc[this.fieldKey] instanceof WebField)) { - this.layoutDoc.nativeWidth = this.dataDoc[this.fieldKey + '-nativeWidth'] = undefined; + if (!this.layoutDoc._layout_showSidebar && !(this.dataDoc[this.fieldKey] instanceof WebField)) { + this.layoutDoc.nativeWidth = this.dataDoc[this.fieldKey + '_nativeWidth'] = undefined; } else { this.layoutDoc.nativeWidth = nativeWidth * pdfratio; } } }); + @action + onZoomWheel = (e: React.WheelEvent) => { + if (this.props.isContentActive(true)) { + e.stopPropagation(); + } + }; sidebarWidth = () => { if (!this.SidebarShown) return 0; if (this._previewWidth) return WebBox.sidebarResizerWidth + WebBox.openSidebarWidth; // return default sidebar if previewing (as in viewing a link target) const nativeDiff = NumCast(this.layoutDoc.nativeWidth) - Doc.NativeWidth(this.dataDoc); return WebBox.sidebarResizerWidth + nativeDiff * (this.props.NativeDimScaling?.() || 1); }; + _innerCollectionView: CollectionFreeFormView | undefined; + zoomScaling = () => this._innerCollectionView?.zoomScaling() ?? 1; + setInnerContent = (component: DocComponentView) => (this._innerCollectionView = component as CollectionFreeFormView); + @computed get content() { - const interactive = - !this.props.docViewPath().lastElement()?.docView?._pendingDoubleClick && this.props.isContentActive() && this.props.pointerEvents?.() !== 'none' && Doc.ActiveTool === InkTool.None && !DocumentDecorations.Instance?.Interacting; + const interactive = this.props.isContentActive() && this.props.pointerEvents?.() !== 'none' && Doc.ActiveTool === InkTool.None; return ( - <div className={'webBox-cont' + (interactive ? '-interactive' : '')} onKeyDown={e => e.stopPropagation()} style={{ width: !this.layoutDoc.forceReflow ? NumCast(this.layoutDoc[this.fieldKey + '-nativeWidth']) || `100%` : '100%' }}> - {this.urlContent} + <div + className={'webBox-cont' + (interactive ? '-interactive' : '')} + onKeyDown={e => e.stopPropagation()} + style={{ + width: !this.layoutDoc.layout_forceReflow ? NumCast(this.layoutDoc[this.fieldKey + '_nativeWidth']) || `100%` : '100%', + transform: `scale(${this.zoomScaling()}) translate(${-NumCast(this.layoutDoc.freeform_panX)}px, ${-NumCast(this.layoutDoc.freeform_panY)}px)`, + }}> + {this._hackHide || (this.webThumb && !this._webPageHasBeenRendered && LightboxView.LightboxDoc !== this.rootDoc) ? null : this.urlContent} </div> ); } @@ -868,7 +876,13 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps @computed get annotationLayer() { TraceMobx(); return ( - <div className="webBox-annotationLayer" style={{ height: Doc.NativeHeight(this.Document) || undefined }} ref={this._annotationLayer}> + <div + className="webBox-annotationLayer" + style={{ + transform: `scale(${this.zoomScaling()}) translate(${-NumCast(this.layoutDoc.freeform_panX)}px, ${-NumCast(this.layoutDoc.freeform_panY)}px)`, + height: Doc.NativeHeight(this.Document) || undefined, + }} + ref={this._annotationLayer}> {this.inlineTextAnnotations .sort((a, b) => NumCast(a.y) - NumCast(b.y)) .map(anno => ( @@ -878,51 +892,58 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps ); } @computed get SidebarShown() { - return this._showSidebar || this.layoutDoc._showSidebar ? true : false; + return this._showSidebar || this.layoutDoc._layout_showSidebar ? true : false; } @computed get webpage() { const previewScale = this._previewNativeWidth ? 1 - this.sidebarWidth() / this._previewNativeWidth : 1; const pointerEvents = this.layoutDoc._lockedPosition ? 'none' : (this.props.pointerEvents?.() as any); const scale = previewScale * (this.props.NativeDimScaling?.() || 1); - const renderAnnotations = (docFilters?: () => string[]) => ( + const renderAnnotations = (docFilters: () => string[]) => ( <CollectionFreeFormView - {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight', 'setContentView']).omit} + {...this.props} + setContentView={this.setInnerContent} + NativeWidth={returnZero} + NativeHeight={returnZero} + originTopLeft={false} + isAnnotationOverlayScrollable={true} renderDepth={this.props.renderDepth + 1} isAnnotationOverlay={true} fieldKey={this.annotationKey} - CollectionView={undefined} setPreviewCursor={this.setPreviewCursor} setBrushViewer={this.setBrushViewer} PanelWidth={this.panelWidth} PanelHeight={this.panelHeight} ScreenToLocalTransform={this.scrollXf} NativeDimScaling={returnOne} - dropAction={'alias'} + focus={this.focus} + dropAction="embed" docFilters={docFilters} select={emptyFunction} + isAnyChildContentActive={returnFalse} bringToFront={emptyFunction} styleProvider={this.childStyleProvider} whenChildContentsActiveChanged={this.whenChildContentsActiveChanged} removeDocument={this.removeDocument} moveDocument={this.moveDocument} - addDocument={this.addDocument} + addDocument={this.addDocumentWrapper} childPointerEvents={this.props.isContentActive() ? 'all' : undefined} pointerEvents={this.annotationPointerEvents} /> ); return ( <div - className={'webBox-outerContent'} + className="webBox-outerContent" ref={this._outerRef} style={{ height: `${100 / scale}%`, pointerEvents, }} - onWheel={StopEvent} // block wheel events from propagating since they're handled by the iframe + // when active, block wheel events from propagating since they're handled by the iframe + onWheel={this.onZoomWheel} onScroll={e => this.setDashScrollTop(this._outerRef.current?.scrollTop || 0)} onPointerDown={this.onMarqueeDown}> - <div className={'webBox-innerContent'} style={{ height: this._webPageHasBeenRendered && this._scrollHeight ? this.scrollHeight : '100%', pointerEvents }}> + <div className="webBox-innerContent" style={{ height: (this._webPageHasBeenRendered && this._scrollHeight) || '100%', pointerEvents }}> {this.content} {<div style={{ display: DragManager.docsBeingDragged.length ? 'none' : undefined, mixBlendMode: 'multiply' }}>{renderAnnotations(this.transparentFilter)}</div>} {renderAnnotations(this.opaqueFilter)} @@ -969,15 +990,15 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps searchStringChanged = (e: React.ChangeEvent<HTMLInputElement>) => (this._searchString = e.currentTarget.value); showInfo = action((anno: Opt<Doc>) => (this._overlayAnnoInfo = anno)); setPreviewCursor = (func?: (x: number, y: number, drag: boolean, hide: boolean) => void) => (this._setPreviewCursor = func); - panelWidth = () => this.props.PanelWidth() / (this.props.NativeDimScaling?.() || 1) - this.sidebarWidth() + WebBox.sidebarResizerWidth; // (this.Document.scrollHeight || Doc.NativeHeight(this.Document) || 0); - panelHeight = () => this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1); // () => this._pageSizes.length && this._pageSizes[0] ? this._pageSizes[0].width : Doc.NativeWidth(this.Document); - scrollXf = () => this.props.ScreenToLocalTransform().translate(0, NumCast(this.layoutDoc._scrollTop)); + panelWidth = () => this.props.PanelWidth() / (this.props.NativeDimScaling?.() || 1) - this.sidebarWidth() + WebBox.sidebarResizerWidth; + panelHeight = () => this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1); + scrollXf = () => this.props.ScreenToLocalTransform().translate(0, NumCast(this.layoutDoc._layout_scrollTop)); anchorMenuClick = () => this._sidebarRef.current?.anchorMenuClick; transparentFilter = () => [...this.props.docFilters(), Utils.IsTransparentFilter()]; opaqueFilter = () => [...this.props.docFilters(), Utils.noDragsDocFilter, ...(DragManager.docsBeingDragged.length ? [] : [Utils.IsOpaqueFilter()])]; childStyleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string): any => { if (doc instanceof Doc && property === StyleProp.PointerEvents) { - if (doc.textInlineAnnotations) return 'none'; + if (this.inlineTextAnnotations.includes(doc)) return 'none'; } return this.props.styleProvider?.(doc, props, property); }; @@ -1016,7 +1037,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps docView={this.props.docViewPath().lastElement()} finishMarquee={this.finishMarquee} savedAnnotations={this.savedAnnotationsCreator} - selectionText={returnEmptyString} + selectionText={this.selectionText} annotationLayer={this._annotationLayer.current} mainCont={this._mainCont.current} /> @@ -1037,7 +1058,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps ref={this._sidebarRef} {...this.props} whenChildContentsActiveChanged={this.whenChildContentsActiveChanged} - fieldKey={this.fieldKey + '-' + this._urlHash} + fieldKey={this.fieldKey + '_' + this._urlHash} rootDoc={this.rootDoc} layoutDoc={this.layoutDoc} dataDoc={this.dataDoc} |
