diff options
author | bobzel <zzzman@gmail.com> | 2021-03-15 03:36:35 -0400 |
---|---|---|
committer | bobzel <zzzman@gmail.com> | 2021-03-15 03:36:35 -0400 |
commit | ded69655dabf97c76f97271e7da8e77e3f33ec25 (patch) | |
tree | 9cc9cce9713365735491b7f0dcd6b6928db739af /src/client/views/nodes/WebBox.tsx | |
parent | f8e0f7b7712375341d95a68bc78d6369e9b1deb5 (diff) |
getting webBox's to work like PdfBox's - text selection & marquee.
Diffstat (limited to 'src/client/views/nodes/WebBox.tsx')
-rw-r--r-- | src/client/views/nodes/WebBox.tsx | 158 |
1 files changed, 131 insertions, 27 deletions
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index 6127f82e3..9e6e94248 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -37,6 +37,7 @@ import { CollectionStackingView } from "../collections/CollectionStackingView"; import { StyleProp } from "../StyleProvider"; import { FormattedTextBox } from "./formattedText/FormattedTextBox"; import { CollectionViewType } from "../collections/CollectionView"; +import { AnchorMenu } from "../pdf/AnchorMenu"; const htmlToText = require("html-to-text"); type WebDocument = makeInterface<[typeof documentSchema]>; @@ -64,6 +65,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum @observable private _savedAnnotations: Dictionary<number, HTMLDivElement[]> = new Dictionary<number, HTMLDivElement[]>(); get scrollHeight() { return this.webpage?.scrollHeight || 1000; } get webpage() { return this._iframe?.contentDocument?.children[0]; } + @computed get inlineTextAnnotations() { return this.allAnnotations.filter(a => a.textInlineAnnotations); } constructor(props: any) { super(props); @@ -71,12 +73,114 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum Doc.SetNativeWidth(this.dataDoc, Doc.NativeWidth(this.dataDoc) || 850); Doc.SetNativeHeight(this.dataDoc, Doc.NativeHeight(this.dataDoc) || this.Document[HeightSym]() / this.Document[WidthSym]() * 850); } - this._annotationKey = this._annotationKey + "-" + this.urlHash(this._url); + if (this.layoutDoc[this.fieldKey + "-contentWidth"] === undefined) { + this.layoutDoc[this.fieldKey + "-contentWidth"] = Doc.NativeWidth(this.layoutDoc); + } + this._annotationKey = "annotations-" + this.urlHash(this._url); + } + + @action + createTextAnnotation = (sel: Selection, selRange: Range) => { + if (this._mainCont.current) { + const clientRects = selRange.getClientRects(); + for (let i = 0; i < clientRects.length; i++) { + const rect = clientRects.item(i); + if (rect && rect.width !== this._mainCont.current.clientWidth) { + const annoBox = document.createElement("div"); + annoBox.className = "marqueeAnnotator-annotationBox"; + // transforms the positions from screen onto the pdf div + annoBox.style.top = (rect.top + this._mainCont.current.scrollTop).toString(); + annoBox.style.left = (rect.left).toString(); + annoBox.style.width = (rect.width).toString(); + annoBox.style.height = (rect.height).toString(); + this._annotationLayer.current && MarqueeAnnotator.previewNewAnnotation(this._savedAnnotations, this._annotationLayer.current, annoBox, 1); + } + } + } + //this._selectionText = selRange.cloneContents().textContent || ""; + + // clear selection + if (sel.empty) { // Chrome + sel.empty(); + } else if (sel.removeAllRanges) { // Firefox + sel.removeAllRanges(); + } + } + + @action + ptrUp = (e: PointerEvent) => { + if (this._iframe?.contentWindow && this._iframe.contentDocument && !this._iframe.contentWindow.getSelection()?.isCollapsed) { + this._iframe.contentDocument.addEventListener("pointerup", this.ptrUp); + const mainContBounds = Utils.GetScreenTransform(this._mainCont.current!); + const scale = (this.props.scaling?.() || 1) * mainContBounds.scale; + const sel = this._iframe.contentWindow.getSelection(); + if (sel) { + this.createTextAnnotation(sel, sel.getRangeAt(0)) + AnchorMenu.Instance.jumpTo(e.clientX * scale + mainContBounds.translateX, + e.clientY * scale + mainContBounds.translateY - NumCast(this.layoutDoc._scrollTop) * scale); + } + } else AnchorMenu.Instance.fadeOut(true); + } + + getWordAtPoint = (elem: any, x: number, y: number): Opt<string> => { + if (elem.nodeType == elem.TEXT_NODE) { + var range = elem.ownerDocument.createRange(); + range.selectNodeContents(elem); + var currentPos = 0; + var endPos = range.endOffset; + while (currentPos + 1 < endPos) { + range.setStart(elem, currentPos); + range.setEnd(elem, currentPos + 1); + const rangeRect = range.getBoundingClientRect(); + if (rangeRect.left <= x && rangeRect.right >= x && + rangeRect.top <= y && rangeRect.bottom >= y) { + range.expand("word"); + var ret = range.toString(); + range.detach(); + return (ret); + } + currentPos += 1; + } + } else { + for (var i = 0; i < elem.childNodes.length; i++) { + var range = elem.childNodes[i].ownerDocument.createRange(); + range.selectNodeContents(elem.childNodes[i]); + const rangeRect = range.getBoundingClientRect(); + if (rangeRect.left <= x && rangeRect.right >= x && + rangeRect.top <= y && rangeRect.bottom >= y) { + range.detach(); + const word = this.getWordAtPoint(elem.childNodes[i], x, y); + if (word) return word; + } else { + range.detach(); + } + } + } + return undefined; + } + @action + ptrDown = (e: PointerEvent) => { + const mainContBounds = Utils.GetScreenTransform(this._mainCont.current!); + const scale = (this.props.scaling?.() || 1) * mainContBounds.scale; + const word = this.getWordAtPoint(e.target, e.clientX, e.clientY); + this._marqueeing = [e.clientX * scale + mainContBounds.translateX, + e.clientY * scale + mainContBounds.translateY - NumCast(this.layoutDoc._scrollTop) * scale]; + if (word) { + console.log(word); + this._iframe?.contentDocument?.addEventListener("pointerup", this.ptrUp); + setTimeout(action(() => this._marqueeing = undefined), 100); // bcz: hack .. anchor menu is setup within MarqueeAnnotator so we need to at least create the marqueeAnnotator even though we aren't using it. + } else { + this.isAnnotating = true; + this.props.select(false); + e.stopPropagation(); + e.preventDefault(); + } } iframeLoaded = (e: any) => { const iframe = this._iframe; if (iframe?.contentDocument) { + iframe?.contentDocument.addEventListener("pointerdown", this.ptrDown); if (this._initialScroll !== undefined && this._outerRef.current && this.webpage) { this.webpage.scrollTop = this._initialScroll; this._outerRef.current.scrollTop = this._initialScroll; @@ -134,6 +238,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum onScroll = (e: any) => { if (!this._ignoreScroll.includes("iframe") && this.webpage) { this.webpage.scrollTop = this._outerRef.current?.scrollTop || 0; + } + if (this._ignoreScroll !== "iframe|outer") { this.layoutDoc._scrollTop = this._outerRef.current?.scrollTop; } } @@ -258,7 +364,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum if (future.length) { history.push(this._url); this.dataDoc[this.fieldKey] = new WebField(new URL(this._url = future.pop()!)); - this._annotationKey = this.fieldKey + "-annotations-" + this.urlHash(this._url); + this._annotationKey = "annotations-" + this.urlHash(this._url); return true; } return false; @@ -272,7 +378,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum if (future === undefined) this.dataDoc[this.fieldKey + "-future"] = new List<string>([this._url]); else future.push(this._url); this.dataDoc[this.fieldKey] = new WebField(new URL(this._url = history.pop()!)); - this._annotationKey = this.fieldKey + "-annotations-" + this.urlHash(this._url); + this._annotationKey = "annotations-" + this.urlHash(this._url); return true; } return false; @@ -299,7 +405,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum future && (future.length = 0); } this._url = newUrl; - this._annotationKey = this.fieldKey + "-annotations-" + this.urlHash(this._url); + this._annotationKey = "annotations-" + this.urlHash(this._url); this.dataDoc[this.fieldKey] = new WebField(new URL(newUrl)); } catch (e) { console.log("WebBox URL error:" + this._url); @@ -353,16 +459,6 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum ); } - editToggleBtn() { - return <Tooltip title={<div className="dash-tooltip" >{`${this.props.Document.isAnnotating ? "Exit" : "Enter"} annotation mode`}</div>}> - <div className="webBox-annotationToggle" - style={{ color: this.props.Document.isAnnotating ? "black" : "white", backgroundColor: this.props.Document.isAnnotating ? "white" : "black" }} - onClick={action(() => this.layoutDoc.isAnnotating = !this.layoutDoc.isAnnotating)}> - <FontAwesomeIcon icon="edit" size="sm" /> - </div> - </Tooltip>; - } - _ignore = 0; onPreWheel = (e: React.WheelEvent) => this._ignore = e.timeStamp; onPrePointer = (e: React.PointerEvent) => this._ignore = e.timeStamp; @@ -583,7 +679,6 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum style={{ width: NumCast(this.layoutDoc[this.fieldKey + "-contentWidth"]) || `${100 / scale}%`, height: `${100 / scale}%`, - transform: `scale(${scale})` }} onWheel={this.onPostWheel} onPointerDown={this.onPostPointer} onPointerMove={this.onPostPointer} onPointerUp={this.onPostPointer}> {this.urlContent} @@ -599,13 +694,16 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum </>); } - @computed get allAnnotations() { return DocListCast(this.dataDoc[this.props.fieldKey + "-annotations"]); } - @computed get nonDocAnnotations() { return this.allAnnotations.filter(a => a.annotations); } + showInfo = action((anno: Opt<Doc>) => this._overlayAnnoInfo = anno); + @observable private _overlayAnnoInfo: Opt<Doc>; + @computed get allAnnotations() { + return DocListCast(this.dataDoc[this.annotationKey]); + } @computed get annotationLayer() { TraceMobx(); return <div className="webBox-annotationLayer" style={{ height: Doc.NativeHeight(this.Document) || undefined }} ref={this._annotationLayer}> - {this.nonDocAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y)).map(anno => - <Annotation {...this.props} showInfo={emptyFunction} dataDoc={this.dataDoc} fieldKey={this.props.fieldKey} anno={anno} key={`${anno[Id]}-annotation`} />) + {this.inlineTextAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y)).map(anno => + <Annotation {...this.props} fieldKey={this.annotationKey} showInfo={this.showInfo} dataDoc={this.dataDoc} anno={anno} key={`${anno[Id]}-annotation`} />) } </div>; } @@ -617,9 +715,13 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum this.props.select(false); } } + @observable isAnnotating = false; @action - finishMarquee = () => this._marqueeing = undefined; + finishMarquee = () => { + this._marqueeing = undefined; + this.isAnnotating = false; + } panelWidth = () => this.props.PanelWidth() / (this.props.scaling?.() || 1) - this.sidebarWidth(); // (this.Document.scrollHeight || Doc.NativeHeight(this.Document) || 0); panelHeight = () => this.props.PanelHeight() / (this.props.scaling?.() || 1); // () => this._pageSizes.length && this._pageSizes[0] ? this._pageSizes[0].width : Doc.NativeWidth(this.Document); @@ -634,11 +736,12 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum onWheel={this.onWebWheel} onContextMenu={this.specificContextMenu}> <base target="_blank" /> - {this.content} <div className={"webBox-outerContent"} ref={this._outerRef} style={{ - width: `calc(${100 / scale}% - ${this.sidebarWidth()}px)`, height: `${100 / scale}%`, transform: `scale(${scale})`, - pointerEvents: !this.layoutDoc.isAnnotating || inactiveLayer ? "none" : "all" + width: `calc(${100 / scale}% - ${this.sidebarWidth()}px)`, + height: `${100 / scale}%`, + transform: `scale(${scale})`, + pointerEvents: inactiveLayer ? "none" : "all" }} onWheel={this.onWheel} onPointerDown={this.onMarqueeDown} @@ -648,12 +751,14 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum height: NumCast(this.scrollHeight, 50), pointerEvents: inactiveLayer ? "none" : undefined }}> + {this.content} <CollectionFreeFormView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight", "setContentView"]).omit} renderDepth={this.props.renderDepth + 1} CollectionView={undefined} fieldKey={this.annotationKey} isAnnotationOverlay={true} scaling={returnOne} + pointerEvents={this.isAnnotating ? "all" : "none"} PanelWidth={this.panelWidth} PanelHeight={this.panelHeight} ScreenToLocalTransform={this.scrollXf} @@ -663,14 +768,14 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum select={emptyFunction} active={this.active} whenActiveChanged={this.whenActiveChanged} /> + {this.annotationLayer} </div> </div> - {this.annotationLayer} {!this._marqueeing || !this._mainCont.current || !this._annotationLayer.current ? (null) : <MarqueeAnnotator rootDoc={this.rootDoc} anchorMenuClick={this.anchorMenuClick} - scrollTop={NumCast(this.rootDoc._scrollTop)} - down={this._marqueeing} scaling={this.props.scaling} + scrollTop={0} + down={this._marqueeing} scaling={returnOne} addDocument={this.addDocument} finishMarquee={this.finishMarquee} savedAnnotations={this._savedAnnotations} @@ -682,7 +787,6 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum <FontAwesomeIcon style={{ color: "white" }} icon={"chevron-left"} size="sm" /> </button> {this.sidebarOverlay} - {this.props.isSelected() ? this.editToggleBtn() : null} </div>); } }
\ No newline at end of file |