import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, override, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import * as WebRequest from 'web-request';
import { Doc, DocListCast, Field, Opt } from '../../../fields/Doc';
import { Id } from '../../../fields/FieldSymbols';
import { HtmlField } from '../../../fields/HtmlField';
import { InkTool } from '../../../fields/InkField';
import { List } from '../../../fields/List';
import { RefField } from '../../../fields/RefField';
import { listSpec } from '../../../fields/Schema';
import { Cast, NumCast, StrCast, WebCast } from '../../../fields/Types';
import { ImageField, WebField } from '../../../fields/URLField';
import { TraceMobx } from '../../../fields/util';
import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, copyProps, emptyFunction, getWordAtPoint, lightOrDark, returnFalse, returnOne, returnZero, setupMoveUpEvents, smoothScroll, Utils } from '../../../Utils';
import { Docs, DocUtils } from '../../documents/Documents';
import { DocumentManager } from '../../util/DocumentManager';
import { ScriptingGlobals } from '../../util/ScriptingGlobals';
import { SnappingManager } from '../../util/SnappingManager';
import { undoBatch, UndoManager } from '../../util/UndoManager';
import { MarqueeOptionsMenu } from '../collections/collectionFreeForm';
import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
import { ContextMenu } from '../ContextMenu';
import { ContextMenuProps } from '../ContextMenuItem';
import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent';
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 { DocComponentView, DocFocusOptions, DocumentView, DocumentViewProps, OpenWhere } from './DocumentView';
import { FieldView, FieldViewProps } from './FieldView';
import { LinkInfo } from './LinkDocPreview';
import { PinProps, PresBox } from './trails';
import './WebBox.scss';
const { CreateImage } = require('./WebBoxRenderer');
const _global = (window /* browser */ || global) /* node */ as any;
const htmlToText = require('html-to-text');
@observer
export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps>() {
    public static LayoutString(fieldKey: string) {
        return FieldView.LayoutString(WebBox, fieldKey);
    }
    public static openSidebarWidth = 250;
    public static sidebarResizerWidth = 5;
    static webStyleSheet = addStyleSheet();
    private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean, hide: boolean, doc: Opt<Doc>) => void);
    private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
    private _outerRef: React.RefObject<HTMLDivElement> = React.createRef();
    private _marqueeref = React.createRef<MarqueeAnnotator>();
    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.layout_scrollTop));
    private _sidebarRef = React.createRef<SidebarAnnos>();
    private _searchRef = React.createRef<HTMLInputElement>();
    private _searchString = '';
    private _scrollTimer: any;
    private _getAnchor: (savedAnnotations: Opt<ObservableMap<number, HTMLDivElement[]>>, addAsAnnotation: boolean) => Opt<Doc> = () => undefined;

    @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 _webPageHasBeenRendered = false;
    @observable private _marqueeing: number[] | undefined = undefined;
    get marqueeing() {
        return this._marqueeing;
    }
    set marqueeing(val) {
        val && this._marqueeref.current?.onInitiateSelection(val);
        !val && this._marqueeref.current?.onTerminateSelection();
        this._marqueeing = val;
    }
    @observable private _iframe: HTMLIFrameElement | null = null;
    @observable private _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>();
    @observable private _scrollHeight = NumCast(this.layoutDoc.scrollHeight);
    @computed get _url() {
        return this.webField?.toString() || '';
    }
    @computed get _urlHash() {
        return this._url ? WebBox.urlHash(this._url) + '' : '';
    }
    @computed get scrollHeight() {
        return Math.max(NumCast(this.layoutDoc._height), this._scrollHeight);
    }
    @computed get allAnnotations() {
        return DocListCast(this.dataDoc[this.annotationKey]);
    }
    @computed get inlineTextAnnotations() {
        return this.allAnnotations.filter(a => a.text_inlineAnnotations);
    }
    @computed get webField() {
        return Cast(this.Document[this._props.fieldKey], WebField)?.url;
    }

    _prevProps: ViewBoxAnnotatableProps & FieldViewProps;
    @override _props: ViewBoxAnnotatableProps & FieldViewProps;
    constructor(props: ViewBoxAnnotatableProps & FieldViewProps) {
        super(props);
        this._props = this._prevProps = props;
        makeObservable(this);
        this._webUrl = this._url; // setting the weburl will change the src parameter of the embedded iframe and force a navigation to it.
    }

    componentDidUpdate() {
        copyProps(this);
    }

    @action
    search = (searchString: string, bwd?: boolean, clear: boolean = false) => {
        if (!this._searching && !clear) {
            this._searching = true;
            setTimeout(() => {
                this._searchRef.current?.focus();
                this._searchRef.current?.select();
                this._searchRef.current?.setRangeText(searchString);
            });
        }
        try {
            if (clear) {
                this._iframe?.contentWindow?.getSelection()?.empty();
            }
            if (searchString) {
                (this._iframe?.contentWindow as any)?.find(searchString, false, bwd, true);
            }
        } catch (e) {
            console.log('WebBox search error', e);
        }
        return true;
    };
    @action
    setScrollPos = (pos: number) => {
        if (!this._outerRef.current || this._outerRef.current.scrollHeight < pos) {
            if (this._webPageHasBeenRendered) setTimeout(() => this.setScrollPos(pos), 250);
        } else {
            this._outerRef.current.scrollTop = pos;
            this._initialScroll = undefined;
        }
    };

    updateThumb = async () => {
        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();
        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.Document.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.Document.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);
            });
    };

    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 ? 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)
            if (this._url) {
                const reqdFuncs: { [key: string]: string } = {};
                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);
            }
        });
        this._disposers.urlchange = reaction(
            () => WebCast(this.dataDoc.data),
            url => this.submitURL(false, false)
        );
        this._disposers.titling = reaction(
            () => StrCast(this.Document.title),
            url => {
                url.startsWith('www') && this.setData('http://' + url);
                url.startsWith('http') && this.setData(url);
            }
        );

        this._disposers.layout_autoHeight = reaction(
            () => this.layoutDoc._layout_autoHeight,
            layout_autoHeight => {
                if (layout_autoHeight) {
                    this.layoutDoc._nativeHeight = NumCast(this.Document[this._props.fieldKey + '_nativeHeight']);
                    this._props.setHeight?.(NumCast(this.Document[this._props.fieldKey + '_nativeHeight']) * (this._props.NativeDimScaling?.() || 1));
                }
            }
        );

        if (this.webField?.href.indexOf('youtube') !== -1) {
            const youtubeaspect = 400 / 315;
            const nativeWidth = Doc.NativeWidth(this.layoutDoc);
            const nativeHeight = Doc.NativeHeight(this.layoutDoc);
            if (this.webField) {
                if (!nativeWidth || !nativeHeight || Math.abs(nativeWidth / nativeHeight - youtubeaspect) > 0.05) {
                    if (!nativeWidth) Doc.SetNativeWidth(this.layoutDoc, 600);
                    Doc.SetNativeHeight(this.layoutDoc, (nativeWidth || 600) / youtubeaspect);
                    this.layoutDoc._height = NumCast(this.layoutDoc._width) / youtubeaspect;
                }
            } // else it's an HTMLfield
        } else if (this.webField && !this.dataDoc.text) {
            WebRequest.get(Utils.CorsProxy(this.webField.href)) //
                .then(result => result && (this.dataDoc.text = htmlToText.convert(result.content)));
        }

        this._disposers.scrollReaction = reaction(
            () => NumCast(this.layoutDoc._layout_scrollTop),
            scrollTop => {
                const viewTrans = StrCast(this.Document._viewTransition);
                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, 'ease');
            },
            { fireImmediately: true }
        );
    }
    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) {
            if (this.dataDoc[this._props.fieldKey] instanceof HtmlField) this._mainCont.current.style.transform = `rotate(${NumCast(this._props.DocumentView!().screenToLocalTransform().RotateDeg)}deg)`;
            const clientRects = selRange.getClientRects();
            for (let i = 0; i < clientRects.length; i++) {
                const rect = clientRects.item(i);
                const mainrect = this._url ? { translateX: 0, translateY: 0, scale: 1 } : Utils.GetScreenTransform(this._mainCont.current);
                if (rect && rect.width !== this._mainCont.current.clientWidth) {
                    const annoBox = document.createElement('div');
                    annoBox.className = 'marqueeAnnotator-annotationBox';
                    const scale = this._url ? 1 : this._props.ScreenToLocalTransform().Scale;
                    // transforms the positions from screen onto the pdf div
                    annoBox.style.top = ((rect.top - mainrect.translateY) * scale + (this._url ? this._mainCont.current.scrollTop : NumCast(this.layoutDoc.layout_scrollTop))).toString();
                    annoBox.style.left = ((rect.left - mainrect.translateX) * scale).toString();
                    annoBox.style.width = (rect.width * scale).toString();
                    annoBox.style.height = (rect.height * scale).toString();
                    this._annotationLayer.current && MarqueeAnnotator.previewNewAnnotation(this._savedAnnotations, this._annotationLayer.current, annoBox, 1);
                }
            }
            this._mainCont.current.style.transform = '';
        }
        this._selectionContent = selRange?.cloneContents();
        this._selectionText = this._selectionContent?.textContent || '';

        // clear selection
        this._textAnnotationCreator = undefined;
        if (sel.empty) sel.empty(); // Chrome
        else if (sel.removeAllRanges) sel.removeAllRanges(); // Firefox
        return this._savedAnnotations;
    };

    focus = (anchor: Doc, options: DocFocusOptions) => {
        if (anchor !== this.Document && this._outerRef.current) {
            const windowHeight = this._props.PanelHeight() / (this._props.NativeDimScaling?.() || 1);
            const scrollTo = Utils.scrollIntoView(NumCast(anchor.y), NumCast(anchor._height), NumCast(this.layoutDoc._layout_scrollTop), windowHeight, windowHeight * 0.1, Math.max(NumCast(anchor.y) + NumCast(anchor._height), 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;
                }
            }
        }
    };

    @action
    getView = (doc: Doc) => {
        if (Doc.AreProtosEqual(doc, this.Document)) return new Promise<Opt<DocumentView>>(res => res(this._props.DocumentView?.()));
        if (this.Document.layout_fieldKey === 'layout_icon') this._props.DocumentView?.().iconify();
        const webUrl = WebCast(doc.config_data)?.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.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 visibleAnchor = this._getAnchor(this._savedAnnotations, true);
        const anchor =
            visibleAnchor ??
            Docs.Create.ConfigDocument({
                title: StrCast(this.Document.title + ' ' + this.layoutDoc._layout_scrollTop),
                y: NumCast(this.layoutDoc._layout_scrollTop),
                annotationOn: this.Document,
            });
        PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), scrollable: pinProps?.pinData ? true : false, pannable: true } }, this.Document);
        anchor.text = ele?.textContent ?? '';
        anchor.text_html = ele?.innerHTML ?? this._selectionText;
        addAsAnnotation && this.addDocumentWrapper(anchor);
        return anchor;
    };

    _textAnnotationCreator: (() => ObservableMap<number, HTMLDivElement[]>) | undefined;
    savedAnnotationsCreator: () => ObservableMap<number, HTMLDivElement[]> = () => this._textAnnotationCreator?.() || this._savedAnnotations;

    @action
    iframeUp = (e: PointerEvent) => {
        this._getAnchor = AnchorMenu.Instance?.GetAnchor; // need to save AnchorMenu's getAnchor since a subsequent selection on another doc will overwrite this value
        this._textAnnotationCreator = undefined;
        this._props.docViewPath().lastElement()?.docView?.cleanupPointerEvents(); // pointerup events aren't generated on containing document view, so we have to invoke it here.
        if (this._iframe?.contentWindow && this._iframe.contentDocument && !this._iframe.contentWindow.getSelection()?.isCollapsed) {
            const mainContBounds = Utils.GetScreenTransform(this._mainCont.current!);
            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._layout_scrollTop) * scale);
                // Changing which document to add the annotation to (the currently selected WebBox)
                GPTPopup.Instance.setSidebarId(`${this._props.fieldKey}_${this._urlHash ? this._urlHash + '_' : ''}sidebar`);
                GPTPopup.Instance.addDoc = this.sidebarAddDocument;
            }
        }
    };
    @action
    webClipDown = (e: React.PointerEvent) => {
        e.stopPropagation();
        const sel = window.getSelection();
        this._textAnnotationCreator = undefined;
        if (sel?.empty) sel.empty(); // Chrome
        else if (sel?.removeAllRanges) sel.removeAllRanges(); // Firefox
        // bcz: NEED TO unrotate e.clientX and e.clientY
        const word = getWordAtPoint(e.target, e.clientX, e.clientY);
        this._setPreviewCursor?.(e.clientX, e.clientY, false, true, this.Document);
        MarqueeAnnotator.clearAnnotations(this._savedAnnotations);

        if (!word && !(e.target as any)?.className?.includes('rangeslider') && !(e.target as any)?.onclick && !(e.target as any)?.parentNode?.onclick) {
            if (e.button !== 2) this.marqueeing = [e.clientX, e.clientY];
            e.preventDefault();
        }
        document.addEventListener('pointerup', this.webClipUp);
    };
    @action
    webClipUp = (e: PointerEvent) => {
        if (window.getSelection()?.isCollapsed && this._marqueeref.current?.isEmpty) {
            this.marqueeing = undefined;
        }
        document.removeEventListener('pointerup', this.webClipUp);
        this._getAnchor = AnchorMenu.Instance?.GetAnchor; // need to save AnchorMenu's getAnchor since a subsequent selection on another doc will overwrite this value
        const sel = window.getSelection();
        if (sel && !sel.isCollapsed) {
            const selRange = sel.getRangeAt(0);
            this._selectionText = sel.toString();
            AnchorMenu.Instance.setSelectedText(sel.toString());
            this._textAnnotationCreator = () => this.createTextAnnotation(sel, selRange);
            (!sel.isCollapsed || this.marqueeing) && AnchorMenu.Instance.jumpTo(e.clientX, e.clientY);
            // Changing which document to add the annotation to (the currently selected WebBox)
            GPTPopup.Instance.setSidebarId(`${this._props.fieldKey}_${this._urlHash ? this._urlHash + '_' : ''}sidebar`);
            GPTPopup.Instance.addDoc = this.sidebarAddDocument;
        }
    };
    @action
    iframeDown = (e: PointerEvent) => {
        this._props.select(false);
        const locpt = { 
            x: (e.clientX / NumCast(this.Document.nativeWidth)) * this._props.PanelWidth(), 
            y: ((e.clientY - NumCast(this.layoutDoc.layout_scrollTop))/ NumCast(this.Document.nativeHeight)) * this._props.PanelHeight() }; // prettier-ignore
        const scrclick = this._props.DocumentView?.()._props.ScreenToLocalTransform().inverse().transformPoint(locpt.x, locpt.y)!;
        const scrcent = this._props
            .DocumentView?.()
            ._props.ScreenToLocalTransform()
            .inverse()
            .transformPoint(NumCast(this.Document.width) / 2, NumCast(this.Document.height) / 2)!;
        const theclickoff = Utils.rotPt(scrclick[0] - scrcent[0], scrclick[1] - scrcent[1], -this._props.ScreenToLocalTransform().Rotate);
        const theclick = [theclickoff.x + scrcent[0], theclickoff.y + scrcent[1]];
        MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
        const word = getWordAtPoint(e.target, e.clientX, e.clientY);
        if (!word && !(e.target as any)?.className?.includes('rangeslider') && !(e.target as any)?.onclick && !(e.target as any)?.parentNode?.onclick) {
            this.marqueeing = theclick;
            e.preventDefault();
        }
    };
    isFirefox = () => 'InstallTrigger' in window; // navigator.userAgent.indexOf("Chrome") !== -1;

    addWebStyleSheet(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;
        }
    }
    addWebStyleSheetRule(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;
    @observable _warning = 0;
    @action
    iframeLoaded = (e: any) => {
        const iframe = this._iframe;
        if (this._initialScroll !== undefined) {
            this.setScrollPos(this._initialScroll);
        }
        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 {
            href = iframe?.contentWindow?.location.href;
        } catch (e) {
            runInAction(() => this._warning++);
            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);
                const newsearch = matches?.lastElement()!;
                if (matches) {
                    requrlraw = requrlraw.substring(0, requrlraw.indexOf(newsearch));
                    for (let i = 1; i < Array.from(matches)?.length; i++) {
                        requrlraw = requrlraw.replace(matches[i], '');
                    }
                }
                requrlraw = requrlraw
                    .replace(/q=[^&]*/, newsearch.substring(1))
                    .replace('search&', 'search?')
                    .replace('?gbv=1', '');
            }
            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.scrollHeight || 0);
                if (this._scrollHeight) {
                    this.Document.nativeHeight = Math.min(NumCast(this.Document.nativeHeight), this._scrollHeight);
                    this.layoutDoc.height = Math.min(NumCast(this.layoutDoc._height), (NumCast(this.layoutDoc._width) * NumCast(this.Document.nativeHeight)) / NumCast(this.Document.nativeWidth));
                }
            };
            const swidth = Math.max(NumCast(this.layoutDoc.nativeWidth), iframeContent.body.scrollWidth || 0);
            if (swidth) {
                const aspectResize = swidth / NumCast(this.Document.nativeWidth);
                this.Document.nativeWidth = swidth;
                this.Document.nativeHeight = NumCast(this.Document.nativeHeight) * aspectResize;
                this.layoutDoc.height = NumCast(this.layoutDoc._height) * aspectResize;
            }
            initHeights();
            this._iframetimeout && clearTimeout(this._iframetimeout);
            this._iframetimeout = setTimeout(
                action(() => initHeights),
                5000
            );
            iframeContent.addEventListener(
                'click',
                undoBatch(
                    action((e: MouseEvent) => {
                        let href = '';
                        for (let ele = e.target as any; ele; ele = ele.parentElement) {
                            href = (typeof ele.href === 'string' ? ele.href : ele.href?.baseVal) || ele.parentElement?.href || href;
                        }
                        const origin = this.webField?.origin;
                        if (href && origin) {
                            const batch = UndoManager.StartBatch('webclick');
                            e.stopPropagation();
                            setTimeout(() => {
                                this.setData(href.replace(Utils.prepend(''), origin));
                                batch.end();
                            });
                            if (this._outerRef.current) {
                                this._outerRef.current.scrollTop = NumCast(this.layoutDoc._layout_scrollTop);
                                this._outerRef.current.scrollLeft = 0;
                            }
                        }
                    })
                )
            );
            iframe.contentDocument.addEventListener('wheel', this.iframeWheel, { passive: false });
        }
    };

    @action
    iframeWheel = (e: WheelEvent) => {
        if (!this._scrollTimer) {
            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());
        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 (!LinkInfo.Instance?.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) => {
        if (this._outerRef.current) {
            if (duration) {
                smoothScroll(duration, [this._outerRef.current], scrollTop, easeFunc);
                this.setDashScrollTop(scrollTop, duration);
            } else {
                this.setDashScrollTop(scrollTop);
            }
        } else this._initialScroll = scrollTop;
    };

    forward = (checkAvailable?: boolean) => {
        const future = Cast(this.dataDoc[this.fieldKey + '_future'], listSpec('string'), []);
        const history = Cast(this.dataDoc[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._scrollHeight = 0;
                if (this._webUrl === this._url) {
                    this._webUrl = curUrl;
                    setTimeout(action(() => (this._webUrl = this._url)));
                } else {
                    this._webUrl = this._url;
                }
                return true;
            }
        });
        return false;
    };

    back = (checkAvailable?: boolean) => {
        const future = Cast(this.dataDoc[this.fieldKey + '_future'], listSpec('string'));
        const history = Cast(this.dataDoc[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()!));
                this._scrollHeight = 0;
                if (this._webUrl === this._url) {
                    this._webUrl = curUrl;
                    setTimeout(action(() => (this._webUrl = this._url)));
                } else {
                    this._webUrl = this._url;
                }
                return true;
            }
        });
        return false;
    };

    static urlHash = (s: string) => {
        const split = s.split('');
        return Math.abs(
            split.reduce((a: any, b: any) => {
                a = (a << 5) - a + b.charCodeAt(0);
                return a & a;
            }, 0)
        );
    };
    @action
    submitURL = (preview?: boolean, dontUpdateIframe?: boolean) => {
        try {
            if (!preview) {
                if (this._webPageHasBeenRendered) {
                    this.layoutDoc.thumb = undefined;
                    this.layoutDoc.thumbScrollTop = undefined;
                    this.layoutDoc.thumbNativeWidth = undefined;
                    this.layoutDoc.thumbNativeHeight = undefined;
                }
            }
            if (!preview) {
                if (!dontUpdateIframe) {
                    this._webUrl = this._url;
                }
            }
        } catch (e) {
            console.log('WebBox URL error:' + this._url);
        }
        return true;
    };

    onWebUrlDrop = (e: React.DragEvent) => {
        const { dataTransfer } = e;
        const html = dataTransfer.getData('text/html');
        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.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.dataDoc[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) => {
        if (e.key === 'Enter') this.setData(this._keyInput.current!.value);
        e.stopPropagation();
    };

    specificContextMenu = (e: React.MouseEvent | PointerEvent): void => {
        const cm = ContextMenu.Instance;
        const funcs: ContextMenuProps[] = [];
        if (!cm.findByDescription('Options...')) {
            !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.dataDoc[this.fieldKey + '_allowScripts'] ? 'Prevent' : 'Allow') + ' Scripts',
                event: () => {
                    this.dataDoc[this.fieldKey + '_allowScripts'] = !this.dataDoc[this.fieldKey + '_allowScripts'];
                    if (this._iframe) {
                        runInAction(() => (this._hackHide = true));
                        setTimeout(action(() => (this._hackHide = false)));
                    }
                },
                icon: 'snowflake',
            });
            funcs.push({
                description: (!this.layoutDoc.layout_reflowHorizontal ? 'Force' : 'Prevent') + ' Reflow',
                event: () => {
                    const nw = !this.layoutDoc.layout_reflowHorizontal ? undefined : Doc.NativeWidth(this.layoutDoc) - this.sidebarWidth() / (this._props.NativeDimScaling?.() || 1);
                    this.layoutDoc.layout_reflowHorizontal = !nw;
                    if (nw) {
                        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' });
        }
    };

    /**
     * This gets called when some other child of the webbox is selected and a pointer down occurs on the webbox.
     * it's also called for html clippings when you click outside the bounds of the clipping
     * @param e
     */
    @action
    onMarqueeDown = (e: React.PointerEvent) => {
        const sel = this._url ? this._iframe?.contentDocument?.getSelection() : window.document.getSelection();
        this._textAnnotationCreator = undefined;
        if (sel?.empty) sel.empty(); // Chrome
        else if (sel?.removeAllRanges) sel.removeAllRanges(); // Firefox
        this.marqueeing = [e.clientX, e.clientY];
        if (!e.altKey && e.button === 0 && this._props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
            setupMoveUpEvents(
                this,
                e,
                action(e => {
                    MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
                    return true;
                }),
                returnFalse,
                action(() => {
                    this.marqueeing = undefined;
                    MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
                }),
                false
            );
        } else {
            this.marqueeing = undefined;
        }
    };
    @action finishMarquee = (x?: number, y?: number, e?: PointerEvent) => {
        this._getAnchor = AnchorMenu.Instance?.GetAnchor;
        this.marqueeing = undefined;
        this._textAnnotationCreator = undefined;
        const sel = this._url ? this._iframe?.contentDocument?.getSelection() : window.document.getSelection();
        if (sel?.empty) sel.empty(); // Chrome
        else if (sel?.removeAllRanges) sel.removeAllRanges(); // Firefox
        this._setPreviewCursor?.(x ?? 0, y ?? 0, false, !this._marqueeref.current?.isEmpty, this.Document);
        if (x !== undefined && y !== undefined) {
            ContextMenu.Instance.closeMenu();
            ContextMenu.Instance.setIgnoreEvents(false);
            if (e?.button === 2 || e?.altKey) {
                e?.preventDefault();
                e?.stopPropagation();
                setTimeout(() => {
                    // if menu comes up right away, the down event can still be active causing a menu item to be selected
                    this.specificContextMenu(undefined as any);
                    this._props.docViewPath().lastElement().docView?.onContextMenu(undefined, x, y);
                });
            }
        }
    };

    @observable lighttext = false;

    @computed get urlContent() {
        if (this._props.ScreenToLocalTransform().Scale > 25) return <div></div>;
        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.dataDoc[this._props.fieldKey];
        if (field instanceof HtmlField) {
            return (
                <span
                    className="webBox-htmlSpan"
                    ref={action((r: any) => {
                        if (r) {
                            this._scrollHeight = Number(getComputedStyle(r).height.replace('px', ''));
                            this.lighttext = Array.from(r.children).some((c: any) => c instanceof HTMLElement && lightOrDark(getComputedStyle(c).color) !== Colors.WHITE);
                        }
                    })}
                    contentEditable
                    onPointerDown={this.webClipDown}
                    dangerouslySetInnerHTML={{ __html: field.html }}
                />
            );
        }
        if (field instanceof WebField) {
            const url = this.layoutDoc[this.fieldKey + '_useCors'] ? Utils.CorsProxy(this._webUrl) : this._webUrl;
            const scripts = this.dataDoc[this.fieldKey + '_allowScripts'] || this._webUrl.includes('wikipedia.org') || this._webUrl.includes('google.com') || this._webUrl.startsWith('https://bing');
            //if (!scripts) console.log('No scripts for: ' + url);
            return (
                <iframe
                    key={this._warning}
                    className="webBox-iframe"
                    ref={action((r: HTMLIFrameElement | null) => (this._iframe = r))}
                    style={{ pointerEvents: DocumentView.Interacting ? 'none' : undefined }}
                    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={`${scripts ? 'allow-scripts' : ''} allow-forms allow-modals allow-orientation-lock allow-pointer-lock allow-popups allow-popups-to-escape-sandbox allow-presentation allow-same-origin`}
                />
            );
        }
        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) => {
        this._url && (doc instanceof Doc ? [doc] : doc).forEach(doc => (doc.config_data = new WebField(this._url)));
        return this.addDocument(doc, annotationKey);
    };

    sidebarAddDocument = (doc: Doc | Doc[], sidebarKey?: string) => {
        if (!this.layoutDoc._layout_showSidebar) this.toggleSidebar();
        return this.addDocumentWrapper(doc, sidebarKey);
    };
    @observable _draggingSidebar = false;
    sidebarBtnDown = (e: React.PointerEvent, onButton: boolean) => {
        const batch = UndoManager.StartBatch('sidebar');
        // onButton determines whether the width of the pdf box changes, or just the ratio of the sidebar to the pdf
        setupMoveUpEvents(
            this,
            e,
            action((e, down, delta) => {
                this._draggingSidebar = true;
                const localDelta = this._props
                    .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 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 = NumCast(this.layoutDoc._width) + localDelta[0]);
                    this.layoutDoc._layout_showSidebar = nativeWidth !== this.layoutDoc._nativeWidth;
                }
                return false;
            }),
            action((e, movement, isClick) => {
                this._draggingSidebar = false;
                !isClick && batch.end();
            }),
            () => {
                this.toggleSidebar();
                batch.end();
            }
        );
    };
    @computed get sidebarHandle() {
        return (
            <div
                className="webBox-overlayButton-sidebar"
                key="sidebar"
                title="Toggle Sidebar"
                style={{
                    top: StrCast(this.layoutDoc._layout_showTitle) === 'title' ? 20 : 5,
                    backgroundColor: this.SidebarShown ? Colors.MEDIUM_BLUE : Colors.BLACK,
                }}
                onPointerDown={e => this.sidebarBtnDown(e, true)}>
                <FontAwesomeIcon style={{ color: Colors.WHITE }} icon={'comment-alt'} size="sm" />
            </div>
        );
    }
    @observable _previewNativeWidth: Opt<number> = undefined;
    @observable _previewWidth: Opt<number> = undefined;
    toggleSidebar = action((preview: boolean = false) => {
        var nativeWidth = NumCast(this.layoutDoc[this.fieldKey + '_nativeWidth']);
        if (!nativeWidth) {
            const defaultNativeWidth = NumCast(this.Document.nativeWidth, this.dataDoc[this.fieldKey] instanceof WebField ? 850 : NumCast(this.Document._width));
            Doc.SetNativeWidth(this.dataDoc, Doc.NativeWidth(this.dataDoc) || defaultNativeWidth);
            Doc.SetNativeHeight(this.dataDoc, Doc.NativeHeight(this.dataDoc) || (NumCast(this.Document._height) / NumCast(this.Document._width)) * defaultNativeWidth);
            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) + NumCast(this.layoutDoc.width)) / NumCast(this.layoutDoc.width);
        const curNativeWidth = NumCast(this.layoutDoc.nativeWidth, nativeWidth);
        if (preview) {
            this._previewNativeWidth = nativeWidth * sideratio;
            this._previewWidth = (NumCast(this.layoutDoc._width) * nativeWidth * sideratio) / curNativeWidth;
            this._showSidebar = true;
        } else {
            this.layoutDoc._layout_showSidebar = !this.layoutDoc._layout_showSidebar;
            this.layoutDoc._width = (NumCast(this.layoutDoc._width) * nativeWidth * pdfratio) / curNativeWidth;
            if (!this.layoutDoc._layout_showSidebar && !(this.dataDoc[this.fieldKey] instanceof WebField)) {
                this.layoutDoc.nativeWidth = this.dataDoc[this.fieldKey + '_nativeWidth'] = undefined;
            } else {
                !this.layoutDoc._layout_showSidebar && (this.dataDoc[this.fieldKey + '_nativeWidth'] = this.dataDoc[this.fieldKey + '_nativeHeight'] = undefined);
                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.isContentActive() && this._props.pointerEvents?.() !== 'none' && Doc.ActiveTool === InkTool.None;
        return (
            <div
                className={'webBox-cont' + (interactive ? '-interactive' : '')}
                onKeyDown={e => e.stopPropagation()}
                style={{
                    width: !this.layoutDoc.layout_reflowHorizontal ? 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 ? null : this.urlContent}
            </div>
        );
    }

    @computed get annotationLayer() {
        TraceMobx();
        return (
            <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,
                    mixBlendMode: this._url || !this.lighttext ? 'multiply' : 'hard-light',
                }}
                ref={this._annotationLayer}>
                {this.inlineTextAnnotations
                    .sort((a, b) => NumCast(a.y) - NumCast(b.y))
                    .map(anno => (
                        <Annotation {...this._props} fieldKey={this.annotationKey} pointerEvents={this.pointerEvents} dataDoc={this.dataDoc} anno={anno} key={`${anno[Id]}-annotation`} />
                    ))}
            </div>
        );
    }
    @computed get SidebarShown() {
        return this._showSidebar || this.layoutDoc._layout_showSidebar ? true : false;
    }
    renderAnnotations = (childFilters: () => string[]) => (
        <CollectionFreeFormView
            {...this._props}
            setContentView={this.setInnerContent}
            NativeWidth={returnZero}
            NativeHeight={returnZero}
            originTopLeft={false}
            isAnnotationOverlayScrollable={true}
            renderDepth={this._props.renderDepth + 1}
            isAnnotationOverlay={true}
            fieldKey={this.annotationKey}
            setPreviewCursor={this.setPreviewCursor}
            PanelWidth={this.panelWidth}
            PanelHeight={this.panelHeight}
            ScreenToLocalTransform={this.scrollXf}
            NativeDimScaling={returnOne}
            focus={this.focus}
            childFilters={childFilters}
            select={emptyFunction}
            isAnyChildContentActive={returnFalse}
            bringToFront={emptyFunction}
            styleProvider={this.childStyleProvider}
            whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
            removeDocument={this.removeDocument}
            moveDocument={this.moveDocument}
            addDocument={this.addDocumentWrapper}
            childPointerEvents={this.childPointerEvents}
            pointerEvents={this.annotationPointerEvents}
        />
    );

    @computed get renderOpaqueAnnotations() {
        return this.renderAnnotations(this.opaqueFilter);
    }
    @computed get renderTransparentAnnotations() {
        return this.renderAnnotations(this.transparentFilter);
    }
    childPointerEvents = () => (this._props.isContentActive() ? 'all' : undefined);
    @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);
        return (
            <div
                className="webBox-outerContent"
                ref={this._outerRef}
                style={{
                    height: `${100 / scale}%`,
                    pointerEvents,
                }}
                // 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._props.PanelHeight() && this._scrollHeight) || '100%', pointerEvents }}>
                    {this.content}
                    <div style={{ display: SnappingManager.GetCanEmbed() ? 'none' : undefined, mixBlendMode: 'multiply' }}>{this.renderTransparentAnnotations}</div>
                    {this.renderOpaqueAnnotations}
                    {this.annotationLayer}
                </div>
            </div>
        );
    }

    @computed get searchUI() {
        return (
            <div className="webBox-ui" onPointerDown={e => e.stopPropagation()} style={{ display: this._props.isContentActive() ? 'flex' : 'none' }}>
                <div className="webBox-overlayCont" onPointerDown={e => e.stopPropagation()} style={{ left: `${this._searching ? 0 : 100}%` }}>
                    <button className="webBox-overlayButton" title={'search'} />
                    <input
                        className="webBox-searchBar"
                        placeholder="Search"
                        ref={this._searchRef}
                        onChange={this.searchStringChanged}
                        onKeyDown={e => {
                            e.key === 'Enter' && this.search(this._searchString, e.shiftKey);
                            e.stopPropagation();
                        }}
                    />
                    <button className="webBox-search" title="Search" onClick={e => this.search(this._searchString, e.shiftKey)}>
                        <FontAwesomeIcon icon="search" size="sm" />
                    </button>
                </div>
                <button
                    className="webBox-overlayButton"
                    title={'search'}
                    onClick={action(() => {
                        this._searching = !this._searching;
                        this.search('', false, true);
                    })}>
                    <div className="webBox-overlayButton-arrow" onPointerDown={e => e.stopPropagation()} />
                    <div className="webBox-overlayButton-iconCont" onPointerDown={e => e.stopPropagation()}>
                        <FontAwesomeIcon icon={this._searching ? 'times' : 'search'} size="lg" />
                    </div>
                </button>
            </div>
        );
    }
    searchStringChanged = (e: React.ChangeEvent<HTMLInputElement>) => (this._searchString = e.currentTarget.value);
    setPreviewCursor = (func?: (x: number, y: number, drag: boolean, hide: boolean, doc: Opt<Doc>) => void) => (this._setPreviewCursor = func);
    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.childFilters(), Utils.TransparentBackgroundFilter];
    opaqueFilter = () => [...this._props.childFilters(), Utils.noDragDocsFilter, ...(SnappingManager.GetCanEmbed() ? [] : [Utils.OpaqueBackgroundFilter])];
    childStyleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string): any => {
        if (doc instanceof Doc && property === StyleProp.PointerEvents) {
            if (this.inlineTextAnnotations.includes(doc)) return 'none';
        }
        return this._props.styleProvider?.(doc, props, property);
    };
    pointerEvents = () =>
        !this._draggingSidebar && this._props.isContentActive() && !MarqueeOptionsMenu.Instance?.isShown()
            ? 'all' //
            : 'none';
    annotationPointerEvents = () => (this._props.isContentActive() && (SnappingManager.GetIsDragging() || Doc.ActiveTool !== InkTool.None) ? 'all' : 'none');
    render() {
        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);
        return (
            <div
                className="webBox"
                ref={this._mainCont}
                style={{
                    pointerEvents: this.pointerEvents(), //
                    position: SnappingManager.GetIsDragging() ? 'absolute' : undefined,
                }}>
                <div className="webBox-background" style={{ backgroundColor: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor) }} />
                <div
                    className="webBox-container"
                    style={{
                        width: `calc(${100 / scale}% - ${!this.SidebarShown ? 0 : ((this.sidebarWidth() - WebBox.sidebarResizerWidth) / scale) * (this._previewWidth ? scale : 1)}px)`,
                        transform: `scale(${scale})`,
                        pointerEvents,
                    }}
                    onContextMenu={this.specificContextMenu}>
                    {this.webpage}
                </div>
                {!this._mainCont.current || !this._annotationLayer.current ? null : (
                    <div style={{ position: 'absolute', height: '100%', width: '100%', pointerEvents: this.marqueeing ? 'all' : 'none' }}>
                        <MarqueeAnnotator
                            ref={this._marqueeref}
                            Document={this.Document}
                            anchorMenuClick={this.anchorMenuClick}
                            scrollTop={NumCast(this.layoutDoc.layout_scrollTop)}
                            annotationLayerScrollTop={0}
                            scaling={this._props.NativeDimScaling}
                            addDocument={this.addDocumentWrapper}
                            docView={this._props.DocumentView!}
                            finishMarquee={this.finishMarquee}
                            savedAnnotations={this.savedAnnotationsCreator}
                            selectionText={this.selectionText}
                            annotationLayer={this._annotationLayer.current}
                            mainCont={this._mainCont.current}
                        />
                    </div>
                )}
                <div
                    className="webBox-sideResizer"
                    style={{
                        display: this.SidebarShown ? undefined : 'none',
                        width: WebBox.sidebarResizerWidth,
                        left: `calc(100% - ${this.sidebarWidth() - WebBox.sidebarResizerWidth}px)`,
                    }}
                    onPointerDown={e => this.sidebarBtnDown(e, false)}
                />
                <div style={{ position: 'absolute', height: '100%', right: 0, top: 0, width: `calc(100 * ${this.sidebarWidth() / this._props.PanelWidth()}%` }}>
                    <SidebarAnnos
                        ref={this._sidebarRef}
                        {...this._props}
                        whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
                        fieldKey={this.fieldKey + '_' + this._urlHash}
                        Document={this.Document}
                        layoutDoc={this.layoutDoc}
                        dataDoc={this.dataDoc}
                        setHeight={emptyFunction}
                        nativeWidth={this._previewNativeWidth ?? NumCast(this.layoutDoc._nativeWidth) - WebBox.sidebarResizerWidth / (this._props.NativeDimScaling?.() || 1)}
                        showSidebar={this.SidebarShown}
                        sidebarAddDocument={this.sidebarAddDocument}
                        moveDocument={this.moveDocument}
                        removeDocument={this.removeDocument}
                    />
                </div>
                {!this._props.isContentActive() || SnappingManager.GetIsDragging() ? null : this.sidebarHandle}
                {!this._props.isContentActive() || SnappingManager.GetIsDragging() ? null : this.searchUI}
            </div>
        );
    }
}
ScriptingGlobals.add(function urlHash(url: string) {
    return url ? WebBox.urlHash(url) : 0;
});
