diff options
Diffstat (limited to 'src/client/views/nodes/PDFBox.tsx')
-rw-r--r-- | src/client/views/nodes/PDFBox.tsx | 163 |
1 files changed, 146 insertions, 17 deletions
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 9807cee7c..4eb07fd8d 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -3,24 +3,30 @@ import { action, computed, IReactionDisposer, observable, reaction, runInAction import { observer } from "mobx-react"; import * as Pdfjs from "pdfjs-dist"; import "pdfjs-dist/web/pdf_viewer.css"; -import { Doc, DocListCast, Opt, WidthSym } from "../../../fields/Doc"; -import { Cast, NumCast, StrCast, ImageCast } from '../../../fields/Types'; -import { PdfField } from "../../../fields/URLField"; +import { Doc, DocListCast, HeightSym, Opt, WidthSym } from "../../../fields/Doc"; +import { Id } from '../../../fields/FieldSymbols'; +import { Cast, ImageCast, NumCast, StrCast } from '../../../fields/Types'; +import { ImageField, PdfField } from "../../../fields/URLField"; import { TraceMobx } from '../../../fields/util'; import { emptyFunction, returnOne, setupMoveUpEvents, Utils } from '../../../Utils'; -import { Docs } from '../../documents/Documents'; +import { Docs, DocUtils } from '../../documents/Documents'; +import { DocumentType } from '../../documents/DocumentTypes'; import { KeyCodes } from '../../util/KeyCodes'; -import { undoBatch } from '../../util/UndoManager'; +import { undoBatch, UndoManager } from '../../util/UndoManager'; import { ContextMenu } from '../ContextMenu'; import { ContextMenuProps } from '../ContextMenuItem'; import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from "../DocComponent"; import { Colors } from '../global/globalEnums'; +import { CreateImage } from "../nodes/WebBoxRenderer"; import { AnchorMenu } from '../pdf/AnchorMenu'; import { PDFViewer } from "../pdf/PDFViewer"; import { SidebarAnnos } from '../SidebarAnnos'; import { FieldView, FieldViewProps } from './FieldView'; +import { ImageBox } from './ImageBox'; import "./PDFBox.scss"; +import { VideoBox } from './VideoBox'; import React = require("react"); +import { CollectionFreeFormView } from '../collections/collectionFreeForm'; @observer export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps>() { @@ -52,6 +58,116 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps } } + replaceCanvases = (oldDiv: HTMLElement, newDiv: HTMLElement) => { + if (oldDiv.childNodes) { + for (let i = 0; i < oldDiv.childNodes.length; i++) { + this.replaceCanvases(oldDiv.childNodes[i] as HTMLElement, newDiv.childNodes[i] as HTMLElement); + } + } + + if (oldDiv.className === "pdfBox-ui" || + oldDiv.className === "pdfViewerDash-overlay-inking") { + newDiv.style.display = "none"; + } + if (newDiv && newDiv.style) newDiv.style.overflow = "hidden"; + if (oldDiv instanceof HTMLCanvasElement) { + const canvas = oldDiv; + const img = document.createElement('img'); // create a Image Element + img.src = canvas.toDataURL(); //image sourcez + img.style.width = canvas.style.width; + img.style.height = canvas.style.height; + const newCan = newDiv as HTMLCanvasElement; + const parEle = newCan.parentElement as HTMLElement; + parEle.removeChild(newCan); + parEle.appendChild(img); + } + } + + crop = (region: Doc | undefined, addCrop?: boolean) => { + if (!region) return; + const cropping = Doc.MakeCopy(region, true); + Doc.GetProto(region).lockedPosition = true; + Doc.GetProto(region).title = "region:" + this.rootDoc.title; + Doc.GetProto(region).isPushpin = true; + this.addDocument(region); + + const docViewContent = this.props.docViewPath().lastElement().ContentDiv!; + const newDiv = docViewContent.cloneNode(true) as HTMLDivElement; + newDiv.style.width = (this.layoutDoc[WidthSym]()).toString(); + newDiv.style.height = (this.layoutDoc[HeightSym]()).toString(); + this.replaceCanvases(docViewContent, newDiv); + const htmlString = this._pdfViewer?._mainCont.current && new XMLSerializer().serializeToString(newDiv); + + const anchx = NumCast(cropping.x); + const anchy = NumCast(cropping.y); + const anchw = cropping[WidthSym]() * (this.props.scaling?.() || 1); + const anchh = cropping[HeightSym]() * (this.props.scaling?.() || 1); + const viewScale = 1; + cropping.title = "crop: " + this.rootDoc.title; + cropping.x = NumCast(this.rootDoc.x) + NumCast(this.rootDoc._width); + cropping.y = NumCast(this.rootDoc.y); + cropping._width = anchw; + cropping._height = anchh; + cropping.isLinkButton = undefined; + const croppingProto = Doc.GetProto(cropping); + croppingProto.annotationOn = undefined; + croppingProto.isPrototype = true; + croppingProto.proto = Cast(this.rootDoc.proto, Doc, null)?.proto; // set proto of cropping's data doc to be IMAGE_PROTO + croppingProto.type = DocumentType.IMG; + croppingProto.layout = ImageBox.LayoutString("data"); + croppingProto.data = new ImageField(Utils.CorsProxy("http://www.cs.brown.edu/~bcz/noImage.png")); + croppingProto["data-nativeWidth"] = anchw; + croppingProto["data-nativeHeight"] = anchh; + if (addCrop) { + DocUtils.MakeLink({ doc: region }, { doc: cropping }, "cropped image", ""); + } + this.props.bringToFront(cropping); + + CreateImage( + "", + document.styleSheets, + htmlString, + anchw, + anchh, + NumCast(region.y) * this.props.PanelWidth() / NumCast(this.rootDoc[this.fieldKey + "-nativeWidth"]), + NumCast(region.x) * this.props.PanelWidth() / NumCast(this.rootDoc[this.fieldKey + "-nativeWidth"]), + 4 + ).then + ((data_url: any) => { + VideoBox.convertDataUri(data_url, region[Id]).then( + returnedfilename => setTimeout(action(() => { + croppingProto.data = new ImageField(returnedfilename); + }), 500)); + }) + .catch(function (error: any) { + console.error('oops, something went wrong!', error); + }); + + + return cropping; + } + + updateIcon = () => { + // currently we render pdf icons as text labels + const docViewContent = this.props.docViewPath().lastElement().ContentDiv!; + const filename = this.layoutDoc[Id] + "-icon" + (new Date()).getTime(); + this._pdfViewer?._mainCont.current && CollectionFreeFormView.UpdateIcon( + filename, docViewContent, + this.layoutDoc[WidthSym](), this.layoutDoc[HeightSym](), + this.props.PanelWidth(), this.props.PanelHeight(), + NumCast(this.layoutDoc._scrollTop), + NumCast(this.rootDoc[this.fieldKey + "-nativeHeight"], 1), + true, + this.layoutDoc[Id] + "-icon", + (iconFile:string, nativeWidth:number, nativeHeight:number) => { + setTimeout(() => { + this.dataDoc.icon = new ImageField(iconFile); + this.dataDoc["icon-nativeWidth"] = nativeWidth; + this.dataDoc["icon-nativeHeight"] = nativeHeight; + }, 500); + }); + } + componentWillUnmount() { this._selectReactionDisposer?.(); } componentDidMount() { this.props.setContentView?.(this); @@ -72,7 +188,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps } getAnchor = () => { const anchor = - AnchorMenu.Instance?.GetAnchor() ?? + this._pdfViewer?._getAnchor(this._pdfViewer.savedAnnotations()) ?? Docs.Create.TextanchorDocument({ title: StrCast(this.rootDoc.title + "@" + NumCast(this.layoutDoc._scrollTop)?.toFixed(0)), y: NumCast(this.layoutDoc._scrollTop), @@ -104,8 +220,14 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps }); public prevAnnotation = () => this._pdfViewer?.prevAnnotation(); public nextAnnotation = () => this._pdfViewer?.nextAnnotation(); - public backPage = () => { this.Document._curPage = (NumCast(this.Document._curPage) || 1) - 1; return true; }; - public forwardPage = () => { this.Document._curPage = (NumCast(this.Document._curPage) || 1) + 1; return true; }; + public backPage = () => { + this.Document._curPage = Math.max(1, (NumCast(this.Document._curPage) || 1) - 1); + return true; + } + public forwardPage = () => { + this.Document._curPage = Math.min(NumCast(this.dataDoc[this.props.fieldKey + "-numPages"]), (NumCast(this.Document._curPage) || 1) + 1); + return true; + } public gotoPage = (p: number) => this.Document._curPage = p; @undoBatch @@ -137,6 +259,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps return this.addDocument(doc, sidebarKey); } sidebarBtnDown = (e: React.PointerEvent, onButton: boolean) => { // onButton determines whether the width of the pdf box changes, or just the ratio of the sidebar to the pdf + const batch = UndoManager.StartBatch("sidebar"); setupMoveUpEvents(this, e, (e, down, delta) => { const localDelta = this.props.ScreenToLocalTransform().scale(this.props.scaling?.() || 1).transformDirection(delta[0], delta[1]); const nativeWidth = NumCast(this.layoutDoc[this.fieldKey + "-nativeWidth"]); @@ -148,7 +271,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps this.layoutDoc._showSidebar = nativeWidth !== this.layoutDoc._nativeWidth; } return false; - }, emptyFunction, () => this.toggleSidebar()); + }, () => batch.end(), () => this.toggleSidebar()); } @observable _previewNativeWidth: Opt<number> = undefined; @observable _previewWidth: Opt<number> = undefined; @@ -181,13 +304,16 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps </>; const searchTitle = `${!this._searching ? "Open" : "Close"} Search Bar`; const curPage = NumCast(this.Document._curPage) || 1; - return !this.props.isContentActive() ? (null) : + return !this.props.isContentActive() || this._pdfViewer?.isAnnotating ? (null) : <div className="pdfBox-ui" onKeyDown={e => [KeyCodes.BACKSPACE, KeyCodes.DELETE].includes(e.keyCode) ? e.stopPropagation() : true} onPointerDown={e => e.stopPropagation()} style={{ display: this.props.isContentActive() ? "flex" : "none" }}> <div className="pdfBox-overlayCont" onPointerDown={(e) => e.stopPropagation()} style={{ left: `${this._searching ? 0 : 100}%` }}> <button className="pdfBox-overlayButton" title={searchTitle} /> <input className="pdfBox-searchBar" placeholder="Search" ref={this._searchRef} onChange={this.searchStringChanged} - onKeyDown={e => e.keyCode === KeyCodes.ENTER && this.search(this._searchString, e.shiftKey)} /> + onKeyDown={e => { + e.stopPropagation(); + e.keyCode === KeyCodes.ENTER && this.search(this._searchString, e.shiftKey); + }} /> <button className="pdfBox-search" title="Search" onClick={e => this.search(this._searchString, e.shiftKey)}> <FontAwesomeIcon icon="search" size="sm" /> </button> @@ -209,6 +335,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps <div className="pdfBox-pageNums"> <input value={curPage} style={{ width: `${curPage > 99 ? 4 : 3}ch`, pointerEvents: "all" }} onChange={e => this.Document._curPage = Number(e.currentTarget.value)} + onKeyDown={e => e.stopPropagation()} onClick={action(() => this._pageControls = !this._pageControls)} /> {this._pageControls ? pageBtns : (null)} </div> @@ -224,12 +351,13 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps </div>; } sidebarWidth = () => !this.SidebarShown ? 0 : - this._previewWidth ? PDFBox.openSidebarWidth : - (NumCast(this.layoutDoc.nativeWidth) - Doc.NativeWidth(this.dataDoc)) * this.props.PanelWidth() / NumCast(this.layoutDoc.nativeWidth) + PDFBox.sidebarResizerWidth + (this._previewWidth ? PDFBox.openSidebarWidth : + (NumCast(this.layoutDoc.nativeWidth) - Doc.NativeWidth(this.dataDoc)) * this.props.PanelWidth() / NumCast(this.layoutDoc.nativeWidth)) specificContextMenu = (e: React.MouseEvent): void => { const funcs: ContextMenuProps[] = []; funcs.push({ description: "Copy path", event: () => this.pdfUrl && Utils.CopyText(Utils.prepend("") + this.pdfUrl.url.pathname), icon: "expand-arrows-alt" }); + funcs.push({ description: "update icon", event: () => this.pdfUrl && this.updateIcon(), icon: "expand-arrows-alt" }); //funcs.push({ description: "Toggle Sidebar ", event: () => this.toggleSidebar(), icon: "expand-arrows-alt" }); ContextMenu.Instance.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" }); } @@ -261,12 +389,12 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps }}> <div className="pdfBox-background" onPointerDown={e => this.sidebarBtnDown(e, false)} /> <div style={{ - width: `calc(${100 / scale}% - ${(this.sidebarWidth() + PDFBox.sidebarResizerWidth) / scale * (this._previewWidth ? scale : 1)}px)`, + width: `calc(${100 / scale}% - ${this.sidebarWidth() / scale * (this._previewWidth ? scale : 1)}px)`, height: `${100 / scale}%`, transform: `scale(${scale})`, position: "absolute", transformOrigin: "top left", - top: 0 + top: 0, }}> <PDFViewer {...this.props} rootDoc={this.rootDoc} @@ -282,7 +410,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps moveDocument={this.moveDocument} removeDocument={this.removeDocument} whenChildContentsActiveChanged={this.whenChildContentsActiveChanged} - startupLive={true} + crop={this.crop} ContentScaling={returnOne} /> </div> @@ -324,4 +452,5 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps } return this.renderTitleBox; } -}
\ No newline at end of file +} + |