diff options
Diffstat (limited to 'src/client/views/nodes')
| -rw-r--r-- | src/client/views/nodes/CollectionFreeFormDocumentView.tsx | 22 | ||||
| -rw-r--r-- | src/client/views/nodes/DocumentContentsView.tsx | 5 | ||||
| -rw-r--r-- | src/client/views/nodes/FieldView.tsx | 2 | ||||
| -rw-r--r-- | src/client/views/nodes/FormattedTextBox.tsx | 4 | ||||
| -rw-r--r-- | src/client/views/nodes/ImageBox.tsx | 62 | ||||
| -rw-r--r-- | src/client/views/nodes/KeyValuePair.tsx | 8 | ||||
| -rw-r--r-- | src/client/views/nodes/PDFBox.tsx | 95 | ||||
| -rw-r--r-- | src/client/views/nodes/VideoBox.tsx | 6 |
8 files changed, 146 insertions, 58 deletions
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index 5bff08280..aaaa6a9c5 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -1,4 +1,4 @@ -import { action, computed, IReactionDisposer, reaction } from "mobx"; +import { action, computed, IReactionDisposer, reaction, trace } from "mobx"; import { observer } from "mobx-react"; import { Doc, DocListCast, DocListCastAsync } from "../../../new_fields/Doc"; import { List } from "../../../new_fields/List"; @@ -177,6 +177,9 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF } else { this._lastTap = Date.now(); } + if (e.button === 0) { + e.preventDefault(); // prevents Firefox from dragging images (we want to do that ourself) + } } onPointerUp = (e: PointerEvent): void => { document.removeEventListener("pointerup", this.onPointerUp); @@ -189,6 +192,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF if (this._doubleTap) { this.props.addDocTab(this.props.Document, "inTab"); SelectionManager.DeselectAll(); + this.props.Document.libraryBrush = false; } let altKey = e.altKey; if (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && @@ -207,7 +211,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF expandedDocs = summarizedDocs ? [...summarizedDocs, ...expandedDocs] : expandedDocs; // let expandedDocs = [...(subBulletDocs ? subBulletDocs : []), ...(maximizedDocs ? maximizedDocs : []), ...(summarizedDocs ? summarizedDocs : []),]; if (expandedDocs.length) { // bcz: need a better way to associate behaviors with click events on widget-documents - let expandedProtoDocs = expandedDocs.map(doc => Doc.GetProto(doc)) + let expandedProtoDocs = expandedDocs.map(doc => Doc.GetProto(doc)); let maxLocation = StrCast(this.props.Document.maximizeLocation, "inPlace"); let getDispDoc = (target: Doc) => Object.getOwnPropertyNames(target).indexOf("isPrototype") === -1 ? target : Doc.MakeDelegate(target); if (altKey) { @@ -262,15 +266,15 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF let maximizedDoc = FieldValue(Cast(this.props.Document.maximizedDocs, listSpec(Doc))); let zoomFade = 1; //var zoom = doc.GetNumber(KeyStore.ZoomBasis, 1); - let transform = this.getTransform().scale(this.contentScaling()).inverse(); - var [sptX, sptY] = transform.transformPoint(0, 0); - let [bptX, bptY] = transform.transformPoint(this.props.PanelWidth(), this.props.PanelHeight()); - let w = bptX - sptX; + // let transform = this.getTransform().scale(this.contentScaling()).inverse(); + // var [sptX, sptY] = transform.transformPoint(0, 0); + // let [bptX, bptY] = transform.transformPoint(this.props.PanelWidth(), this.props.PanelHeight()); + // let w = bptX - sptX; //zoomFade = area < 100 || area > 800 ? Math.max(0, Math.min(1, 2 - 5 * (zoom < this.scale ? this.scale / zoom : zoom / this.scale))) : 1; const screenWidth = Math.min(50 * NumCast(this.props.Document.nativeWidth, 0), 1800); let fadeUp = .75 * screenWidth; let fadeDown = (maximizedDoc ? .0075 : .075) * screenWidth; - zoomFade = w < fadeDown /* || w > fadeUp */ ? Math.max(0.1, Math.min(1, 2 - (w < fadeDown ? Math.sqrt(Math.sqrt(fadeDown / w)) : w / fadeUp))) : 1; + // zoomFade = w < fadeDown /* || w > fadeUp */ ? Math.max(0.1, Math.min(1, 2 - (w < fadeDown ? Math.sqrt(Math.sqrt(fadeDown / w)) : w / fadeUp))) : 1; return ( <div className="collectionFreeFormDocumentView-container" ref={this._mainCont} @@ -282,7 +286,9 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF outlineStyle: "dashed", outlineWidth: BoolCast(this.props.Document.libraryBrush, false) || BoolCast(this.props.Document.protoBrush, false) ? - `${1 * this.getTransform().Scale}px` : "0px", + `${1 * this.getTransform().Scale}px` + // "2px" + : "0px", opacity: zoomFade, borderRadius: `${this.borderRounding()}px`, transformOrigin: "left top", diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index d2cb11586..52a9582d2 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -90,8 +90,9 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & { Math.min(NumCast(self.props.Document.width, 0), px * self.props.ScreenToLocalTransform().Scale))}px`; } - let nativizedTemplate = template.replace(/([0-9]+)px/g, convertConstantsToNative); - layout = nativizedTemplate.replace("{layout}", base); + // let nativizedTemplate = template.replace(/([0-9]+)px/g, convertConstantsToNative); + // layout = nativizedTemplate.replace("{layout}", base); + layout = template.replace("{layout}", base); base = layout; }); } diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx index 5c149af99..092ccb9b0 100644 --- a/src/client/views/nodes/FieldView.tsx +++ b/src/client/views/nodes/FieldView.tsx @@ -117,7 +117,7 @@ export class FieldView extends React.Component<FieldViewProps> { // return <WebBox {...this.props} /> // } else if (!(field instanceof Promise)) { - return <p>{JSON.stringify(field)}</p>; + return <p>{field.toString()}</p>; } else { return <p> {"Waiting for server..."} </p>; diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index d15813f9a..5afef221c 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -366,7 +366,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe style={{ pointerEvents: interactive ? "all" : "none", }} - onKeyDown={this.onKeyPress} + // onKeyDown={this.onKeyPress} onKeyPress={this.onKeyPress} onFocus={this.onFocused} onClick={this.onClick} @@ -378,7 +378,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe // tfs: do we need this event handler onWheel={this.onPointerWheel} > - <div className={`formattedTextBox-inner${rounded}`} style={{ pointerEvents: this.props.Document.isButton && !this.props.isSelected() ? "none" : "all" }} ref={this._proseRef} /> + <div className={`formattedTextBox-inner${rounded}`} style={{ whiteSpace: "pre-wrap", pointerEvents: this.props.Document.isButton && !this.props.isSelected() ? "none" : "all" }} ref={this._proseRef} /> </div> ); } diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 828ac9bc8..d9d964ee9 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -1,4 +1,4 @@ -import { action, observable } from 'mobx'; +import { action, observable, trace } from 'mobx'; import { observer } from "mobx-react"; import Lightbox from 'react-image-lightbox'; import 'react-image-lightbox/style.css'; // This only needs to be imported once in your app @@ -17,6 +17,12 @@ import { ImageField } from '../../../new_fields/URLField'; import { List } from '../../../new_fields/List'; import { InkingControl } from '../InkingControl'; import { Doc } from '../../../new_fields/Doc'; +import { faImage } from '@fortawesome/free-solid-svg-icons'; +import { library } from '@fortawesome/fontawesome-svg-core'; +var path = require('path'); + +library.add(faImage); + export const pageSchema = createSchema({ curPage: "number" @@ -41,7 +47,7 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD onLoad = (target: any) => { var h = this._imgRef.current!.naturalHeight; var w = this._imgRef.current!.naturalWidth; - if (this._photoIndex === 0 && (this.props as any).id !== "isExpander") { + if (this._photoIndex === 0 && (this.props as any).id !== "isExpander" && (!this.Document.nativeHeight || !this.Document.nativeWidth)) { Doc.SetOnPrototype(this.Document, "nativeHeight", FieldValue(this.Document.nativeWidth, 0) * h / w); this.Document.height = FieldValue(this.Document.width, 0) * h / w; } @@ -155,25 +161,61 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD ); } + choosePath(url: URL) { + if (url.protocol === "data" || url.href.indexOf(window.location.origin) === -1) + return url.href; + let ext = path.extname(url.href); + return url.href.replace(ext, this._curSuffix + ext); + } + + @observable _smallRetryCount = 1; + @observable _mediumRetryCount = 1; + @observable _largeRetryCount = 1; + @action retryPath = () => { + if (this._curSuffix === "_s") this._smallRetryCount++; + if (this._curSuffix === "_m") this._mediumRetryCount++; + if (this._curSuffix === "_l") this._largeRetryCount++; + } + @action onError = () => { + let timeout = this._curSuffix === "_s" ? this._smallRetryCount : this._curSuffix === "_m" ? this._mediumRetryCount : this._largeRetryCount; + if (timeout < 10) + setTimeout(this.retryPath, Math.min(10000, timeout * 5)); + } + _curSuffix = "_m"; render() { + // let transform = this.props.ScreenToLocalTransform().inverse(); + let pw = typeof this.props.PanelWidth === "function" ? this.props.PanelWidth() : typeof this.props.PanelWidth === "number" ? (this.props.PanelWidth as any) as number : 50; + // var [sptX, sptY] = transform.transformPoint(0, 0); + // let [bptX, bptY] = transform.transformPoint(pw, this.props.PanelHeight()); + // let w = bptX - sptX; + + let id = (this.props as any).id; // bcz: used to set id = "isExpander" in templates.tsx + let nativeWidth = FieldValue(this.Document.nativeWidth, pw); + let paths: string[] = ["http://www.cs.brown.edu/~bcz/noImage.png"]; + // this._curSuffix = ""; + // if (w > 20) { let field = this.Document[this.props.fieldKey]; - let paths: string[] = ["http://www.cs.brown.edu/~bcz/face.gif"]; - if (field instanceof ImageField) paths = [field.url.href]; - else if (field instanceof List) paths = field.filter(val => val instanceof ImageField).map(p => (p as ImageField).url.href); - let nativeWidth = FieldValue(this.Document.nativeWidth, (this.props.PanelWidth as any) as string ? Number((this.props.PanelWidth as any) as string) : 50); + // if (w < 100 && this._smallRetryCount < 10) this._curSuffix = "_s"; + // else if (w < 600 && this._mediumRetryCount < 10) this._curSuffix = "_m"; + // else if (this._largeRetryCount < 10) this._curSuffix = "_l"; + if (field instanceof ImageField) paths = [this.choosePath(field.url)]; + else if (field instanceof List) paths = field.filter(val => val instanceof ImageField).map(p => this.choosePath((p as ImageField).url)); + // } let interactive = InkingControl.Instance.selectedTool ? "" : "-interactive"; - let id = (this.props as any).id; // bcz: used to set id = "isExpander" in templates.tsx return ( <div id={id} className={`imageBox-cont${interactive}`} // onPointerDown={this.onPointerDown} onDrop={this.onDrop} ref={this.createDropTarget} onContextMenu={this.specificContextMenu}> - <img id={id} src={paths[Math.min(paths.length, this._photoIndex)]} - style={{ objectFit: (this._photoIndex === 0 ? undefined : "contain") }} + <img id={id} + key={this._smallRetryCount + (this._mediumRetryCount << 4) + (this._largeRetryCount << 8)} // force cache to update on retrys + src={paths[Math.min(paths.length, this._photoIndex)]} + // style={{ objectFit: (this._photoIndex === 0 ? undefined : "contain") }} width={nativeWidth} ref={this._imgRef} + onError={this.onError} onLoad={this.onLoad} /> {paths.length > 1 ? this.dots(paths) : (null)} - {this.lightbox(paths)} + {/* {this.lightbox(paths)} */} </div>); } }
\ No newline at end of file diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx index 7a88985c0..2363553df 100644 --- a/src/client/views/nodes/KeyValuePair.tsx +++ b/src/client/views/nodes/KeyValuePair.tsx @@ -60,10 +60,8 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> { <EditableView contents={contents} height={36} GetValue={() => { let field = FieldValue(props.Document[props.fieldKey]); - if (field) { - //TODO Types - return String(field); - // return field.ToScriptString(); + if (Field.IsField(field)) { + return Field.toScriptString(field); } return ""; }} @@ -75,7 +73,7 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> { let res = script.run(); if (!res.success) return false; const field = res.result; - if (Field.IsField(field)) { + if (Field.IsField(field, true)) { props.Document[props.fieldKey] = field; return true; } diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index e71ac4924..3bffdd02d 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -1,26 +1,28 @@ import * as htmlToImage from "html-to-image"; -import { action, computed, IReactionDisposer, observable, reaction, Reaction, trace, runInAction } from 'mobx'; +import { action, computed, IReactionDisposer, observable, reaction, runInAction, trace } from 'mobx'; import { observer } from "mobx-react"; import 'react-image-lightbox/style.css'; import Measure from "react-measure"; //@ts-ignore import { Document, Page } from "react-pdf"; import 'react-pdf/dist/Page/AnnotationLayer.css'; +import { Id } from "../../../new_fields/FieldSymbols"; +import { makeInterface } from "../../../new_fields/Schema"; +import { Cast, FieldValue, NumCast } from "../../../new_fields/Types"; +import { ImageField, PdfField } from "../../../new_fields/URLField"; import { RouteStore } from "../../../server/RouteStore"; import { Utils } from '../../../Utils'; +import { DocServer } from "../../DocServer"; +import { DocComponent } from "../DocComponent"; +import { InkingControl } from "../InkingControl"; +import { SearchBox } from "../SearchBox"; import { Annotation } from './Annotation'; +import { positionSchema } from "./DocumentView"; import { FieldView, FieldViewProps } from './FieldView'; +import { pageSchema } from "./ImageBox"; import "./PDFBox.scss"; +var path = require('path'); import React = require("react"); -import { SelectionManager } from "../../util/SelectionManager"; -import { Cast, FieldValue, NumCast } from "../../../new_fields/Types"; -import { Opt } from "../../../new_fields/Doc"; -import { DocComponent } from "../DocComponent"; -import { makeInterface } from "../../../new_fields/Schema"; -import { positionSchema } from "./DocumentView"; -import { pageSchema } from "./ImageBox"; -import { ImageField, PdfField } from "../../../new_fields/URLField"; -import { InkingControl } from "../InkingControl"; /** ALSO LOOK AT: Annotation.tsx, Sticky.tsx * This method renders PDF and puts all kinds of functionalities such as annotation, highlighting, @@ -71,11 +73,11 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen @computed private get thumbnailPage() { return NumCast(this.props.Document.thumbnailPage, -1); } componentDidMount() { - let wasSelected = false; + let wasSelected = this.props.isSelected(); this._reactionDisposer = reaction( - () => this.props.isSelected(), + () => [this.props.isSelected(), this.curPage], () => { - if (this.curPage > 0 && this.curPage !== this.thumbnailPage && wasSelected && !this.props.isSelected()) { + if (this.curPage > 0 && !this.props.isTopMost && this.curPage !== this.thumbnailPage && wasSelected && !this.props.isSelected()) { this.saveThumbnail(); } wasSelected = this._interactive = this.props.isSelected(); @@ -244,15 +246,20 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen @action saveThumbnail = () => { + this.props.Document.thumbnailPage = FieldValue(this.Document.curPage, -1); this._renderAsSvg = false; setTimeout(() => { let nwidth = FieldValue(this.Document.nativeWidth, 0); let nheight = FieldValue(this.Document.nativeHeight, 0); - htmlToImage.toPng(this._mainDiv.current!, { width: nwidth, height: nheight, quality: 1 }) + htmlToImage.toPng(this._mainDiv.current!, { width: nwidth, height: nheight, quality: 0.8 }) .then(action((dataUrl: string) => { - this.props.Document.thumbnail = new ImageField(new URL(dataUrl)); - this.props.Document.thumbnailPage = FieldValue(this.Document.curPage, -1); - this._renderAsSvg = true; + SearchBox.convertDataUri(dataUrl, "icon" + this.Document[Id] + "_" + this.curPage).then((returnedFilename) => { + if (returnedFilename) { + let url = DocServer.prepend(returnedFilename); + this.props.Document.thumbnail = new ImageField(new URL(url)); + } + runInAction(() => this._renderAsSvg = true); + }) })) .catch(function (error: any) { console.error('oops, something went wrong!', error); @@ -288,7 +295,6 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen } @computed get pdfContent() { - trace(); let pdfUrl = Cast(this.props.Document[this.props.fieldKey], PdfField); if (!pdfUrl) { return <p>No pdf url to render</p>; @@ -305,7 +311,7 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen </Measure>; let xf = (this.Document.nativeHeight || 0) / this.renderHeight; return <div className="pdfBox-contentContainer" key="container" style={{ transform: `scale(${xf}, ${xf})` }}> - <Document file={window.origin + RouteStore.corsProxy + `/${pdfUrl.url}`} renderMode={this._renderAsSvg ? "svg" : "canvas"}> + <Document file={window.origin + RouteStore.corsProxy + `/${pdfUrl.url}`} renderMode={this._renderAsSvg || this.props.isTopMost ? "svg" : "canvas"}> {body} </Document> </div >; @@ -313,33 +319,66 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen @computed get pdfRenderer() { - let proxy = this._loaded ? (null) : this.imageProxyRenderer; let pdfUrl = Cast(this.props.Document[this.props.fieldKey], PdfField); - if ((!this._interactive && proxy) || !pdfUrl) { + let proxy = this.imageProxyRenderer; + if ((!this._interactive && proxy && (!this.props.ContainingCollectionView || !this.props.ContainingCollectionView.props.isTopMost)) || !pdfUrl) { return proxy; } return [ this._pageInfo.area.filter(() => this._pageInfo.area).map((element: any) => element), this._currAnno.map((element: any) => element), - this.pdfContent, - proxy + this.pdfContent ]; } + choosePath(url: URL) { + if (url.protocol === "data" || url.href.indexOf(window.location.origin) === -1) + return url.href; + let ext = path.extname(url.href); + return url.href.replace(ext, this._curSuffix + ext); + } + @observable _smallRetryCount = 1; + @observable _mediumRetryCount = 1; + @observable _largeRetryCount = 1; + @action retryPath = () => { + if (this._curSuffix === "_s") this._smallRetryCount++; + if (this._curSuffix === "_m") this._mediumRetryCount++; + if (this._curSuffix === "_l") this._largeRetryCount++; + } + @action onError = () => { + let timeout = this._curSuffix === "_s" ? this._smallRetryCount : this._curSuffix === "_m" ? this._mediumRetryCount : this._largeRetryCount; + if (timeout < 10) + setTimeout(this.retryPath, Math.min(10000, timeout * 5)); + } + _curSuffix = "_m"; + @computed get imageProxyRenderer() { let thumbField = this.props.Document.thumbnail; - if (thumbField) { - let path = this.thumbnailPage !== this.curPage ? "https://image.flaticon.com/icons/svg/66/66163.svg" : - thumbField instanceof ImageField ? thumbField.url.href : "http://cs.brown.edu/people/bcz/prairie.jpg"; - return <img src={path} width="100%" />; + if (thumbField && this._renderAsSvg) { + + // let transform = this.props.ScreenToLocalTransform().inverse(); + let pw = typeof this.props.PanelWidth === "function" ? this.props.PanelWidth() : typeof this.props.PanelWidth === "number" ? (this.props.PanelWidth as any) as number : 50; + // var [sptX, sptY] = transform.transformPoint(0, 0); + // let [bptX, bptY] = transform.transformPoint(pw, this.props.PanelHeight()); + // let w = bptX - sptX; + + let path = thumbField instanceof ImageField ? thumbField.url.href : "http://cs.brown.edu/people/bcz/prairie.jpg"; + // this._curSuffix = ""; + // if (w > 20) { + let field = thumbField; + // if (w < 100 && this._smallRetryCount < 10) this._curSuffix = "_s"; + // else if (w < 400 && this._mediumRetryCount < 10) this._curSuffix = "_m"; + // else if (this._largeRetryCount < 10) this._curSuffix = "_l"; + if (field instanceof ImageField) path = this.choosePath(field.url); + // } + return <img key={path} src={path} width="100%" />; } return (null); } @action onKeyDown = (e: React.KeyboardEvent) => e.key === "Alt" && (this._alt = true); @action onKeyUp = (e: React.KeyboardEvent) => e.key === "Alt" && (this._alt = false); render() { - trace(); let classname = "pdfBox-cont" + (this.props.isSelected() && !InkingControl.Instance.selectedTool && !this._alt ? "-interactive" : ""); return ( <div className={classname} tabIndex={0} ref={this._mainDiv} onPointerDown={this.onPointerDown} onKeyDown={this.onKeyDown} onKeyUp={this.onKeyUp} > diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index 81c429a02..6ae55d151 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -52,7 +52,7 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD @action public Play() { this.Playing = true; if (this.player) this.player.play(); - if (!this._playTimer) this._playTimer = setInterval(this.updateTimecode, 1000); + if (!this._playTimer) this._playTimer = setInterval(this.updateTimecode, 500); } @action public Pause() { @@ -70,7 +70,9 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD } @action - updateTimecode = () => this.player && (this.props.Document.curPage = this.player.currentTime) + updateTimecode = () => { + this.player && (this.props.Document.curPage = this.player.currentTime); + } componentDidMount() { if (this.props.setVideoBox) this.props.setVideoBox(this); |
