diff options
Diffstat (limited to 'src/client/views/nodes/WebBox.tsx')
-rw-r--r-- | src/client/views/nodes/WebBox.tsx | 348 |
1 files changed, 223 insertions, 125 deletions
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index ca9f363c1..66d0fd385 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -11,8 +11,10 @@ import { listSpec } from '../../../fields/Schema'; import { Cast, ImageCast, NumCast, StrCast } from '../../../fields/Types'; import { ImageField, WebField } from '../../../fields/URLField'; import { TraceMobx } from '../../../fields/util'; -import { emptyFunction, getWordAtPoint, OmitKeys, returnFalse, returnOne, setupMoveUpEvents, smoothScroll, Utils } from '../../../Utils'; +import { emptyFunction, getWordAtPoint, returnFalse, returnOne, 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,7 +23,6 @@ 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'; @@ -29,10 +30,10 @@ import { AnchorMenu } from '../pdf/AnchorMenu'; import { Annotation } from '../pdf/Annotation'; import { SidebarAnnos } from '../SidebarAnnos'; import { StyleProp } from '../StyleProvider'; -import { DocumentViewProps } from './DocumentView'; +import { 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'); const { CreateImage } = require('./WebBoxRenderer'); @@ -46,16 +47,20 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps public static openSidebarWidth = 250; public static sidebarResizerWidth = 5; 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(); private _outerRef: React.RefObject<HTMLDivElement> = React.createRef(); 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 _getAnchor: (savedAnnotations?: ObservableMap<number, HTMLDivElement[]>) => Opt<Doc> = () => undefined; private _sidebarRef = React.createRef<SidebarAnnos>(); private _searchRef = React.createRef<HTMLInputElement>(); private _searchString = ''; + + 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 _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; @@ -85,7 +90,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 ( @@ -140,6 +145,9 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps const nativeWidth = NumCast(this.layoutDoc.nativeWidth); const nativeHeight = (nativeWidth * this.props.PanelHeight()) / this.props.PanelWidth(); if ( + !this.props.isSelected(true) && + !Doc.IsBrushedDegree(this.rootDoc) && + !this.isAnyChildContentActive() && !this.rootDoc.thumbLockout && !this.props.dontRegisterView && this._iframe && @@ -154,7 +162,11 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps 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) => { - VideoBox.convertDataUri(data_url, this.layoutDoc[Id] + '-icon' + new Date().getTime(), true, this.layoutDoc[Id] + '-icon').then(returnedfilename => + 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; @@ -172,6 +184,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps }); } }; + _thumbTimer: any; 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) @@ -179,22 +192,24 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps 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"`; - DocUtils.AssignScripts(this.dataDoc, {}, reqdFuncs); + 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.rootDoc, {}, reqdFuncs); }); reaction( () => this.props.isSelected(true) || this.isAnyChildContentActive() || Doc.isBrushedHighlightedDegree(this.props.Document), async selected => { if (selected) { + this._thumbTimer && clearTimeout(this._thumbTimer); 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(); + this._thumbTimer && clearTimeout(this._thumbTimer); + this._thumbTimer = setTimeout(this.updateThumb, 2000); } }, { fireImmediately: this.props.isSelected(true) || this.isAnyChildContentActive() || (Doc.isBrushedHighlightedDegreeUnmemoized(this.props.Document) ? true : false) } @@ -235,17 +250,23 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps const durationMiliStr = viewTrans.match(/([0-9]*)ms/); const durationSecStr = viewTrans.match(/([0-9.]*)s/); const duration = durationMiliStr ? Number(durationMiliStr[1]) : durationSecStr ? Number(durationSecStr[1]) * 1000 : 0; - this.goTo(scrollTop, duration); + this.goTo(scrollTop, duration, 'ease'); }, { fireImmediately: true } ); } @action componentWillUnmount() { + this._iframetimeout && clearTimeout(this._iframetimeout); + this._iframetimeout = undefined; Object.values(this._disposers).forEach(disposer => disposer?.()); // this._iframe?.removeEventListener('wheel', this.iframeWheel, true); // 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) { @@ -265,6 +286,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 @@ -274,34 +297,59 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps menuControls = () => this.urlEditor; // controls to be added to the top bar when a document of this type is selected - scrollFocus = (doc: Doc, smooth: boolean) => { - if (StrCast(doc.webUrl) !== this._url) this.submitURL(StrCast(doc.webUrl), !smooth); - if (DocListCast(this.props.Document[this.fieldKey + '-sidebar']).includes(doc) && !this.SidebarShown) { - this.toggleSidebar(!smooth); - } - if (this._sidebarRef?.current?.makeDocUnfiltered(doc)) return 1; - if (doc !== this.rootDoc && this._outerRef.current) { + 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); + 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 = smooth ? 500 : 0; - this.goTo(scrollTo, focusSpeed); - 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._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 = () => { + getView = async (doc: Doc) => { + if (this.rootDoc.layoutKey === 'layout_icon') this.props.DocumentView?.().iconify(); + if (this._url && StrCast(doc.webUrl) !== this._url) this.submitURL(StrCast(doc.webUrl)); + 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(); + if (contents) { + ele = document.createElement('div'); + ele.append(contents); + } + } catch (e) {} const anchor = - this._getAnchor(this._savedAnnotations) ?? + 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, + annotationOn: this.rootDoc, }); + PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), scrollable: true, pannable: true } }, this.rootDoc); + anchor.text = ele?.textContent ?? ''; + anchor.textHtml = ele?.innerHTML; + //addAsAnnotation && this.addDocumentWrapper(anchor); return anchor; }; @@ -331,7 +379,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); - 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._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)), @@ -348,27 +396,52 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps // bcz: hack - iframe grabs all events which messes up how we handle contextMenus. So this super naively simulates the event stack to get the specific menu items and the doc view menu items. if (e.button === 2 || (e.button === 0 && e.altKey)) { e.preventDefault(); - e.stopPropagation(); + //e.stopPropagation(); ContextMenu.Instance.closeMenu(); 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') { + if (document) { + const style = document.createElement('style'); + style.type = styleType; + const sheets = document.head.appendChild(style); + return (sheets as any).sheet; + } + } + addStyleSheetRule(sheet: any, selector: any, css: any, selectorPrefix = '.') { + const propText = + typeof css === 'string' + ? css + : Object.keys(css) + .map(p => p + ':' + (p === 'content' ? "'" + css[p] + "'" : css[p])) + .join(';'); + return sheet?.insertRule(selectorPrefix + selector + '{' + propText + '}', sheet.cssRules.length); + } + + _iframetimeout: any = undefined; @action iframeLoaded = (e: any) => { const iframe = this._iframe; if (this._initialScroll !== undefined) { this.setScrollPos(this._initialScroll); } - let requrlraw = decodeURIComponent(iframe?.contentWindow?.location.href.replace(Utils.prepend('') + '/corsProxy/', '') ?? this._url.toString()); + + this.addStyleSheetRule(this.addStyleSheet(this._iframe?.contentDocument), '::selection', { color: 'white', background: 'orange' }, ''); + + let href: Opt<string>; + try { + href = iframe?.contentWindow?.location.href; + } catch (e) { + href = undefined; + } + let requrlraw = decodeURIComponent(href?.replace(Utils.prepend('') + '/corsProxy/', '') ?? this._url.toString()); if (requrlraw !== this._url.toString()) { if (requrlraw.match(/q=.*&/)?.length && this._url.toString().match(/q=.*&/)?.length) { const matches = requrlraw.match(/[^a-zA-z]q=[^&]*/g); @@ -386,16 +459,25 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps } this.submitURL(requrlraw, undefined, true); } - if (iframe?.contentDocument) { - iframe.contentDocument.addEventListener('pointerup', this.iframeUp); - iframe.contentDocument.addEventListener('pointerdown', this.iframeDown); - this._scrollHeight = Math.max(this._scrollHeight, iframe?.contentDocument.body.scrollHeight); - setTimeout( - action(() => (this._scrollHeight = Math.max(this._scrollHeight, iframe?.contentDocument?.body.scrollHeight || 0))), + const iframeContent = iframe?.contentDocument; + if (iframeContent) { + iframeContent.addEventListener('pointerup', this.iframeUp); + iframeContent.addEventListener('pointerdown', this.iframeDown); + const initHeights = () => { + this._scrollHeight = Math.max(this._scrollHeight, (iframeContent.body.children[0] as any)?.scrollHeight || 0); + if (this._scrollHeight) { + this.rootDoc.nativeHeight = Math.min(NumCast(this.rootDoc.nativeHeight), this._scrollHeight); + this.layoutDoc.height = Math.min(this.layoutDoc[HeightSym](), (this.layoutDoc[WidthSym]() * NumCast(this.rootDoc.nativeHeight)) / NumCast(this.rootDoc.nativeWidth)); + } + }; + initHeights(); + this._iframetimeout && clearTimeout(this._iframetimeout); + this._iframetimeout = setTimeout( + action(() => initHeights), 5000 ); iframe.setAttribute('enable-annotation', 'true'); - iframe.contentDocument.addEventListener( + iframeContent.addEventListener( 'click', undoBatch( action((e: MouseEvent) => { @@ -450,11 +532,11 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps ); }; - goTo = (scrollTop: number, duration: number) => { + goTo = (scrollTop: number, duration: number, easeFunc: 'linear' | 'ease' | undefined) => { if (this._outerRef.current) { const iframeHeight = Math.max(scrollTop, this._scrollHeight - this.panelHeight()); if (duration) { - smoothScroll(duration, [this._outerRef.current], scrollTop); + smoothScroll(duration, [this._outerRef.current], scrollTop, easeFunc); this.setDashScrollTop(scrollTop, duration); } else { this.setDashScrollTop(scrollTop); @@ -463,14 +545,14 @@ 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] = new WebField(new URL(future.pop()!)); + this.rootDoc[this.fieldKey + '-history'] = new List<string>([...history, this._url]); + this.rootDoc[this.fieldKey] = new WebField(new URL(future.pop()!)); if (this._webUrl === this._url) { this._webUrl = curUrl; setTimeout(action(() => (this._webUrl = this._url))); @@ -484,15 +566,15 @@ 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]); - this.dataDoc[this.fieldKey] = new WebField(new URL(history.pop()!)); + if (future === undefined) this.rootDoc[this.fieldKey + '-future'] = new List<string>([this._url]); + else this.rootDoc[this.fieldKey + '-future'] = new List<string>([...future, this._url]); + this.layoutDoc[this.fieldKey] = new WebField(new URL(history.pop()!)); if (this._webUrl === this._url) { this._webUrl = curUrl; setTimeout(action(() => (this._webUrl = this._url))); @@ -506,8 +588,9 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps }; static urlHash = (s: string) => { + const split = s.split(''); return Math.abs( - s.split('').reduce((a: any, b: any) => { + split.reduce((a: any, b: any) => { a = (a << 5) - a + b.charCodeAt(0); return a & a; }, 0) @@ -518,11 +601,11 @@ 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 future = Cast(this.rootDoc[this.fieldKey + '-future'], listSpec('string')); + const history = Cast(this.rootDoc[this.fieldKey + '-history'], listSpec('string')); const url = this.webField?.toString(); if (url && !preview) { - this.dataDoc[this.fieldKey + '-history'] = new List<string>([...(history || []), url]); + this.rootDoc[this.fieldKey + '-history'] = new List<string>([...(history || []), url]); this.layoutDoc._scrollTop = 0; if (this._webPageHasBeenRendered) { this.layoutDoc.thumb = undefined; @@ -533,7 +616,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps future && (future.length = 0); } if (!preview) { - this.dataDoc[this.fieldKey] = new WebField(new URL(newUrl)); + this.layoutDoc[this.fieldKey] = new WebField(new URL(newUrl)); !dontUpdateIframe && (this._webUrl = this._url); } } catch (e) { @@ -639,7 +722,6 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps } }; @action finishMarquee = (x?: number, y?: number, e?: PointerEvent) => { - this._getAnchor = AnchorMenu.Instance?.GetAnchor; this._marqueeing = undefined; this._isAnnotating = false; this._iframeClick = undefined; @@ -660,7 +742,7 @@ 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]; + const field = this.rootDoc[this.props.fieldKey]; let view; if (field instanceof HtmlField) { view = <span className="webBox-htmlSpan" contentEditable onPointerDown={e => e.stopPropagation()} dangerouslySetInnerHTML={{ __html: field.html }} />; @@ -767,7 +849,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps toggleSidebar = action((preview: boolean = false) => { 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']); @@ -789,12 +871,14 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps } } }); - sidebarWidth = () => - !this.SidebarShown ? 0 : WebBox.sidebarResizerWidth + (this._previewWidth ? WebBox.openSidebarWidth : ((NumCast(this.layoutDoc.nativeWidth) - Doc.NativeWidth(this.dataDoc)) * this.props.PanelWidth()) / NumCast(this.layoutDoc.nativeWidth)); - + 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); + }; @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} @@ -818,6 +902,62 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps return this._showSidebar || this.layoutDoc._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[]) => ( + <CollectionFreeFormView + {...this.props} + setContentView={emptyFunction} + NativeWidth={returnZero} + NativeHeight={returnZero} + 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} + focus={this.focus} + dropAction={'alias'} + docFilters={docFilters} + select={emptyFunction} + isAnyChildContentActive={returnFalse} + bringToFront={emptyFunction} + styleProvider={this.childStyleProvider} + whenChildContentsActiveChanged={this.whenChildContentsActiveChanged} + removeDocument={this.removeDocument} + moveDocument={this.moveDocument} + addDocument={this.addDocument} + childPointerEvents={this.props.isContentActive() ? 'all' : undefined} + pointerEvents={this.annotationPointerEvents} + /> + ); + return ( + <div + 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 + 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 }}> + {this.content} + {<div style={{ display: DragManager.docsBeingDragged.length ? 'none' : undefined, mixBlendMode: 'multiply' }}>{renderAnnotations(this.transparentFilter)}</div>} + {renderAnnotations(this.opaqueFilter)} + {this.annotationLayer} + </div> + </div> + ); + } + @computed get searchUI() { return ( <div className="webBox-ui" onPointerDown={e => e.stopPropagation()} style={{ display: this.props.isContentActive() ? 'flex' : 'none' }}> @@ -855,53 +995,30 @@ 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); + 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._scrollTop)); anchorMenuClick = () => this._sidebarRef.current?.anchorMenuClick; - basicFilter = () => [...this.props.docFilters(), Utils.PropUnsetFilter('textInlineAnnotations')]; transparentFilter = () => [...this.props.docFilters(), Utils.IsTransparentFilter()]; - opaqueFilter = () => [...this.props.docFilters(), Utils.IsOpaqueFilter()]; + 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'; } return this.props.styleProvider?.(doc, props, property); }; - pointerEvents = () => (!this._draggingSidebar && this.props.isContentActive() && this.props.pointerEvents?.() !== 'none' && !MarqueeOptionsMenu.Instance.isShown() ? 'all' : SnappingManager.GetIsDragging() ? undefined : 'none'); - annotationPointerEvents = () => (this._isAnnotating || SnappingManager.GetIsDragging() ? 'all' : 'none'); + pointerEvents = () => (!this._draggingSidebar && this.props.isContentActive() && this.props.pointerEvents?.() !== 'none' && !MarqueeOptionsMenu.Instance?.isShown() ? 'all' : SnappingManager.GetIsDragging() ? undefined : 'none'); + annotationPointerEvents = () => (this._isAnnotating || SnappingManager.GetIsDragging() || Doc.ActiveTool !== InkTool.None ? 'all' : 'none'); render() { - const pointerEvents = this.layoutDoc._lockedPosition ? 'none' : (this.props.pointerEvents?.() as any); + setTimeout(() => DocListCast(this.rootDoc[this.annotationKey]).forEach(doc => (doc.webUrl = this._url))); 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[]) => ( - <CollectionFreeFormView - {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight', 'setContentView']).omit} - renderDepth={this.props.renderDepth + 1} - isAnnotationOverlay={true} - fieldKey={this.annotationKey} - CollectionView={undefined} - setPreviewCursor={this.setPreviewCursor} - PanelWidth={this.panelWidth} - PanelHeight={this.panelHeight} - ScreenToLocalTransform={this.scrollXf} - NativeDimScaling={returnOne} - dropAction={'alias'} - docFilters={docFilters || this.basicFilter} - dontRenderDocuments={docFilters ? false : true} - select={emptyFunction} - bringToFront={emptyFunction} - whenChildContentsActiveChanged={this.whenChildContentsActiveChanged} - removeDocument={this.removeDocument} - moveDocument={this.moveDocument} - addDocument={this.addDocument} - styleProvider={this.childStyleProvider} - childPointerEvents={this.props.isContentActive() ? 'all' : undefined} - pointerEvents={this.annotationPointerEvents} - /> - ); return ( - <div className="webBox" ref={this._mainCont} style={{ pointerEvents: this.pointerEvents(), display: this.props.thumbShown?.() ? 'none' : undefined }}> + <div + className="webBox" + ref={this._mainCont} + style={{ pointerEvents: this.pointerEvents(), position: SnappingManager.GetIsDragging() ? 'absolute' : undefined, display: !SnappingManager.GetIsDragging() && this.props.thumbShown?.() ? 'none' : undefined }}> <div className="webBox-background" style={{ backgroundColor: this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor) }} /> <div className="webBox-container" @@ -911,27 +1028,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps pointerEvents, }} onContextMenu={this.specificContextMenu}> - <div - className={'webBox-outerContent'} - ref={this._outerRef} - style={{ - height: `${100 / scale}%`, - pointerEvents, - }} - onWheel={e => { - e.stopPropagation(); - e.preventDefault(); - }} // block wheel events from propagating since they're handled by the iframe - onScroll={e => this.setDashScrollTop(this._outerRef.current?.scrollTop || 0)} - onPointerDown={this.onMarqueeDown}> - <div className={'webBox-innerContent'} style={{ height: this._webPageHasBeenRendered ? NumCast(this.scrollHeight, 50) : '100%', pointerEvents }}> - {this.content} - <div style={{ mixBlendMode: 'multiply' }}>{renderAnnotations(this.transparentFilter)}</div> - {renderAnnotations(this.opaqueFilter)} - {SnappingManager.GetIsDragging() ? null : renderAnnotations()} - {this.annotationLayer} - </div> - </div> + {this.webpage} {!this._marqueeing || !this._mainCont.current || !this._annotationLayer.current ? null : ( <div style={{ transformOrigin: 'top left', transform: `scale(${1 / scale})` }}> <MarqueeAnnotator @@ -946,9 +1043,10 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps docView={this.props.docViewPath().lastElement()} finishMarquee={this.finishMarquee} savedAnnotations={this.savedAnnotationsCreator} + selectionText={this.selectionText} annotationLayer={this._annotationLayer.current} mainCont={this._mainCont.current} - />{' '} + /> </div> )} </div> @@ -961,7 +1059,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps }} onPointerDown={e => this.sidebarBtnDown(e, false)} /> - <div style={{ position: 'absolute', height: '100%', right: 0, top: 0, width: `calc(100 * ${this.sidebarWidth() / this.layoutDoc[WidthSym]()}%` }}> + <div style={{ position: 'absolute', height: '100%', right: 0, top: 0, width: `calc(100 * ${this.sidebarWidth() / this.props.PanelWidth()}%` }}> <SidebarAnnos ref={this._sidebarRef} {...this.props} @@ -985,5 +1083,5 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps } } ScriptingGlobals.add(function urlHash(url: string) { - return WebBox.urlHash(url); + return url ? WebBox.urlHash(url) : 0; }); |