import { library } from "@fortawesome/fontawesome-svg-core"; import { faStickyNote, faPen, faMousePointer } from '@fortawesome/free-solid-svg-icons'; import { action, computed, observable, trace, IReactionDisposer, reaction } from "mobx"; import { observer } from "mobx-react"; import { Doc, FieldResult } from "../../../new_fields/Doc"; import { documentSchema } from "../../../new_fields/documentSchemas"; import { HtmlField } from "../../../new_fields/HtmlField"; import { InkTool } from "../../../new_fields/InkField"; import { makeInterface } from "../../../new_fields/Schema"; import { Cast, NumCast, BoolCast, StrCast } from "../../../new_fields/Types"; import { WebField } from "../../../new_fields/URLField"; import { Utils, returnOne, emptyFunction, returnZero } from "../../../Utils"; import { Docs } from "../../documents/Documents"; import { DragManager } from "../../util/DragManager"; import { ImageUtils } from "../../util/Import & Export/ImageUtils"; import { ViewBoxAnnotatableComponent } from "../DocComponent"; import { DocumentDecorations } from "../DocumentDecorations"; import { InkingControl } from "../InkingControl"; import { FieldView, FieldViewProps } from './FieldView'; import "./WebBox.scss"; import React = require("react"); import * as WebRequest from 'web-request'; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView"; const htmlToText = require("html-to-text"); library.add(faStickyNote); type WebDocument = makeInterface<[typeof documentSchema]>; const WebDocument = makeInterface(documentSchema); @observer export class WebBox extends ViewBoxAnnotatableComponent(WebDocument) { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(WebBox, fieldKey); } get _collapsed() { return StrCast(this.layoutDoc._chromeStatus) === "disabled"; } set _collapsed(value) { this.layoutDoc._chromeStatus = !value ? "enabled" : "disabled"; } @observable private _url: string = "hello"; @observable private _pressX: number = 0; @observable private _pressY: number = 0; private _longPressSecondsHack?: NodeJS.Timeout; private _outerRef = React.createRef(); private _iframeRef = React.createRef(); private _iframeIndicatorRef = React.createRef(); private _iframeDragRef = React.createRef(); private _reactionDisposer?: IReactionDisposer; private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean) => void); iframeLoaded = action((e: any) => { if (this._iframeRef.current?.contentDocument) { this._iframeRef.current.contentDocument.addEventListener('pointerdown', this.iframedown, false); this._iframeRef.current.contentDocument.addEventListener('scroll', this.iframeScrolled, false); this.layoutDoc.scrollHeight = this._iframeRef.current.contentDocument.children?.[0].scrollHeight || 1000; this._iframeRef.current.contentDocument.children[0].scrollTop = NumCast(this.layoutDoc.scrollTop); } this._reactionDisposer?.(); this._reactionDisposer = reaction(() => this.layoutDoc.scrollY, (scrollY) => { if (scrollY !== undefined) { this._outerRef.current!.scrollTop = scrollY; this.layoutDoc.scrollY = undefined; } }, { fireImmediately: true } ); }); setPreviewCursor = (func?: (x: number, y: number, drag: boolean) => void) => this._setPreviewCursor = func; iframedown = (e: PointerEvent) => { this._setPreviewCursor?.(e.screenX, e.screenY, false); } iframeScrolled = (e: any) => { const scroll = e.target?.children?.[0].scrollTop; this.layoutDoc.scrollTop = this._outerRef.current!.scrollTop = scroll; } async componentDidMount() { this.setURL(); this._iframeRef.current!.setAttribute("enable-annotation", "true"); document.addEventListener("pointerup", this.onLongPressUp); document.addEventListener("pointermove", this.onLongPressMove); const field = Cast(this.rootDoc[this.props.fieldKey], WebField); if (field?.url.href.indexOf("youtube") !== -1) { const youtubeaspect = 400 / 315; const nativeWidth = NumCast(this.layoutDoc._nativeWidth); const nativeHeight = NumCast(this.layoutDoc._nativeHeight); if (!nativeWidth || !nativeHeight || Math.abs(nativeWidth / nativeHeight - youtubeaspect) > 0.05) { if (!nativeWidth) this.layoutDoc._nativeWidth = 600; this.layoutDoc._nativeHeight = NumCast(this.layoutDoc._nativeWidth) / youtubeaspect; this.layoutDoc._height = NumCast(this.layoutDoc._width) / youtubeaspect; } } else if (field?.url) { const result = await WebRequest.get(Utils.CorsProxy(field.url.href)); this.dataDoc.text = htmlToText.fromString(result.content); } } componentWillUnmount() { this._reactionDisposer?.(); document.removeEventListener("pointerup", this.onLongPressUp); document.removeEventListener("pointermove", this.onLongPressMove); this._iframeRef.current!.contentDocument?.removeEventListener('pointerdown', this.iframedown); this._iframeRef.current!.contentDocument?.removeEventListener('scroll', this.iframeScrolled); } @action onURLChange = (e: React.ChangeEvent) => { this._url = e.target.value; } @action submitURL = () => { this.dataDoc[this.props.fieldKey] = new WebField(new URL(this._url)); } @action setURL() { const urlField: FieldResult = Cast(this.dataDoc[this.props.fieldKey], WebField); if (urlField) this._url = urlField.url.toString(); else this._url = ""; } onValueKeyDown = async (e: React.KeyboardEvent) => { if (e.key === "Enter") { e.stopPropagation(); this.submitURL(); } } toggleAnnotationMode = () => { if (!this.layoutDoc.isAnnotating) { this.layoutDoc.lockedTransform = false; this.layoutDoc.isAnnotating = true; } else { this.layoutDoc.lockedTransform = true; this.layoutDoc.isAnnotating = false; } } urlEditor() { return (
); } @action toggleCollapse = () => { this._collapsed = !this._collapsed; } _ignore = 0; onPreWheel = (e: React.WheelEvent) => { this._ignore = e.timeStamp; } onPrePointer = (e: React.PointerEvent) => { this._ignore = e.timeStamp; } onPostPointer = (e: React.PointerEvent) => { if (this._ignore !== e.timeStamp) { e.stopPropagation(); } } onPostWheel = (e: React.WheelEvent) => { if (this._ignore !== e.timeStamp) { e.stopPropagation(); } } onLongPressDown = (e: React.PointerEvent) => { this._pressX = e.clientX; this._pressY = e.clientY; // find the pressed element in the iframe (currently only works if its an img) let pressedElement: HTMLElement | undefined; let pressedBound: ClientRect | undefined; let selectedText: string = ""; let pressedImg: boolean = false; if (this._iframeRef.current) { const B = this._iframeRef.current.getBoundingClientRect(); const iframeDoc = this._iframeRef.current.contentDocument; if (B && iframeDoc) { // TODO: this only works when scale = 1 as it is currently only inteded for mobile upload const element = iframeDoc.elementFromPoint(this._pressX - B.left, this._pressY - B.top); if (element && element.nodeName === "IMG") { pressedBound = element.getBoundingClientRect(); pressedElement = element.cloneNode(true) as HTMLElement; pressedImg = true; } else { // check if there is selected text const text = iframeDoc.getSelection(); if (text && text.toString().length > 0) { selectedText = text.toString(); // get html of the selected text const range = text.getRangeAt(0); const contents = range.cloneContents(); const div = document.createElement("div"); div.appendChild(contents); pressedElement = div; pressedBound = range.getBoundingClientRect(); } } } } // mark the pressed element if (pressedElement && pressedBound) { if (this._iframeIndicatorRef.current) { this._iframeIndicatorRef.current.style.top = pressedBound.top + "px"; this._iframeIndicatorRef.current.style.left = pressedBound.left + "px"; this._iframeIndicatorRef.current.style.width = pressedBound.width + "px"; this._iframeIndicatorRef.current.style.height = pressedBound.height + "px"; this._iframeIndicatorRef.current.classList.add("active"); } } // start dragging the pressed element if long pressed this._longPressSecondsHack = setTimeout(() => { if (pressedImg && pressedElement && pressedBound) { e.stopPropagation(); e.preventDefault(); if (pressedElement.nodeName === "IMG") { const src = pressedElement.getAttribute("src"); // TODO: may not always work if (src) { const doc = Docs.Create.ImageDocument(src); ImageUtils.ExtractExif(doc); // add clone to div so that dragging ghost is placed properly if (this._iframeDragRef.current) this._iframeDragRef.current.appendChild(pressedElement); const dragData = new DragManager.DocumentDragData([doc]); DragManager.StartDocumentDrag([pressedElement], dragData, this._pressX, this._pressY, { hideSource: true }); } } } else if (selectedText && pressedBound && pressedElement) { e.stopPropagation(); e.preventDefault(); // create doc with the selected text's html const doc = Docs.Create.HtmlDocument(pressedElement.innerHTML); // create dragging ghost with the selected text if (this._iframeDragRef.current) this._iframeDragRef.current.appendChild(pressedElement); // start the drag const dragData = new DragManager.DocumentDragData([doc]); DragManager.StartDocumentDrag([pressedElement], dragData, this._pressX - pressedBound.top, this._pressY - pressedBound.top, { hideSource: true }); } }, 1500); } onLongPressMove = (e: PointerEvent) => { // this._pressX = e.clientX; // this._pressY = e.clientY; } onLongPressUp = (e: PointerEvent) => { if (this._longPressSecondsHack) { clearTimeout(this._longPressSecondsHack); } if (this._iframeIndicatorRef.current) { this._iframeIndicatorRef.current.classList.remove("active"); } if (this._iframeDragRef.current) { while (this._iframeDragRef.current.firstChild) this._iframeDragRef.current.removeChild(this._iframeDragRef.current.firstChild); } } //const href = "https://brown365-my.sharepoint.com/personal/bcz_ad_brown_edu/_layouts/15/Doc.aspx?sourcedoc={31aa3178-4c21-4474-b367-877d0a7135e4}&action=embedview&wdStartOn=1"; @computed get urlContent() { const field = this.dataDoc[this.props.fieldKey]; let view; if (field instanceof HtmlField) { view = ; } else if (field instanceof WebField) { const url = this.layoutDoc.UseCors ? Utils.CorsProxy(field.url.href) : field.url.href; view =