diff options
Diffstat (limited to 'src/client/views/nodes')
26 files changed, 1216 insertions, 647 deletions
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx index 95c765e8a..62a479b2a 100644 --- a/src/client/views/nodes/AudioBox.tsx +++ b/src/client/views/nodes/AudioBox.tsx @@ -68,7 +68,7 @@ export class AudioBox extends DocExtendableComponent<FieldViewProps, AudioDocume this.Document.playOnSelect && sel && !Doc.AreProtosEqual(sel, this.props.Document) && this.playFrom(DateCast(sel.creationTime).date.getTime()); }); this._scrubbingDisposer = reaction(() => AudioBox._scrubTime, timeInMillisecondsFrom1970 => { - const start = this.extensionDoc && DateCast(this.extensionDoc.recordingStart); + const start = DateCast(this.dataDoc[this.props.fieldKey + "-recordingStart"]); start && this.playFrom((timeInMillisecondsFrom1970 - start.date.getTime()) / 1000); }); } @@ -128,18 +128,17 @@ export class AudioBox extends DocExtendableComponent<FieldViewProps, AudioDocume recordAudioAnnotation = () => { let gumStream: any; const self = this; - const extensionDoc = this.extensionDoc; - extensionDoc && navigator.mediaDevices.getUserMedia({ + navigator.mediaDevices.getUserMedia({ audio: true }).then(function (stream) { gumStream = stream; self._recorder = new MediaRecorder(stream); - extensionDoc.recordingStart = new DateField(new Date()); + self.dataDoc[self.props.fieldKey + "-recordingStart"] = new DateField(new Date()); AudioBox.ActiveRecordings.push(self.props.Document); self._recorder.ondataavailable = async function (e: any) { const formData = new FormData(); formData.append("file", e.data); - const res = await fetch(Utils.prepend("/upload"), { + const res = await fetch(Utils.prepend("/uploadFormData"), { method: 'POST', body: formData }); @@ -213,55 +212,53 @@ export class AudioBox extends DocExtendableComponent<FieldViewProps, AudioDocume render() { const interactive = this.active() ? "-interactive" : ""; - return (!this.extensionDoc ? (null) : - <div className={`audiobox-container`} onContextMenu={this.specificContextMenu} - onClick={!this.path ? this.recordClick : undefined}> - <div className="audiobox-handle"></div> - {!this.path ? - <button className={`audiobox-record${interactive}`} style={{ backgroundColor: this._audioState === "recording" ? "red" : "black" }}> - {this._audioState === "recording" ? "STOP" : "RECORD"} - </button> : - <div className="audiobox-controls"> - <div className="audiobox-player" onClick={this.onPlay}> - <div className="audiobox-playhead"> <FontAwesomeIcon style={{ width: "100%" }} icon={this._playing ? "pause" : "play"} size={this.props.PanelHeight() < 36 ? "1x" : "2x"} /></div> - <div className="audiobox-playhead" onClick={this.onStop}><FontAwesomeIcon style={{ width: "100%" }} icon="stop" size={this.props.PanelHeight() < 36 ? "1x" : "2x"} /></div> - <div className="audiobox-timeline" onClick={e => e.stopPropagation()} - onPointerDown={e => { - if (e.button === 0 && !e.ctrlKey) { - const rect = (e.target as any).getBoundingClientRect(); - this._ele!.currentTime = this.Document.currentTimecode = (e.clientX - rect.x) / rect.width * NumCast(this.dataDoc.duration); - this.pause(); - e.stopPropagation(); - } - }} > - {DocListCast(this.dataDoc.links).map((l, i) => { - let la1 = l.anchor1 as Doc; - let la2 = l.anchor2 as Doc; - let linkTime = NumCast(l.anchor2Timecode); - if (Doc.AreProtosEqual(la1, this.dataDoc)) { - la1 = l.anchor2 as Doc; - la2 = l.anchor1 as Doc; - linkTime = NumCast(l.anchor1Timecode); - } - return !linkTime ? (null) : - <div className={this.props.PanelHeight() < 32 ? "audiobox-marker-minicontainer" : "audiobox-marker-container"} key={l[Id]} style={{ left: `${linkTime / NumCast(this.dataDoc.duration, 1) * 100}%` }}> - <div className={this.props.PanelHeight() < 32 ? "audioBox-linker-mini" : "audioBox-linker"} key={"linker" + i}> - <DocumentView {...this.props} Document={l} layoutKey={Doc.LinkEndpoint(l, la2)} - parentActive={returnTrue} bringToFront={emptyFunction} zoomToScale={emptyFunction} getScale={returnOne} - backgroundColor={returnTransparent} /> - </div> - <div key={i} className="audiobox-marker" onPointerEnter={() => Doc.linkFollowHighlight(la1)} - onPointerDown={e => { if (e.button === 0 && !e.ctrlKey) { this.playFrom(linkTime); e.stopPropagation(); } }} - onClick={e => { if (e.button === 0 && !e.ctrlKey) { this.pause(); e.stopPropagation(); } }} /> - </div>; - })} - <div className="audiobox-current" style={{ left: `${NumCast(this.Document.currentTimecode) / NumCast(this.dataDoc.duration, 1) * 100}%` }} /> - {this.audio} - </div> + return <div className={`audiobox-container`} onContextMenu={this.specificContextMenu} + onClick={!this.path ? this.recordClick : undefined}> + <div className="audiobox-handle"></div> + {!this.path ? + <button className={`audiobox-record${interactive}`} style={{ backgroundColor: this._audioState === "recording" ? "red" : "black" }}> + {this._audioState === "recording" ? "STOP" : "RECORD"} + </button> : + <div className="audiobox-controls"> + <div className="audiobox-player" onClick={this.onPlay}> + <div className="audiobox-playhead"> <FontAwesomeIcon style={{ width: "100%" }} icon={this._playing ? "pause" : "play"} size={this.props.PanelHeight() < 36 ? "1x" : "2x"} /></div> + <div className="audiobox-playhead" onClick={this.onStop}><FontAwesomeIcon style={{ width: "100%" }} icon="stop" size={this.props.PanelHeight() < 36 ? "1x" : "2x"} /></div> + <div className="audiobox-timeline" onClick={e => e.stopPropagation()} + onPointerDown={e => { + if (e.button === 0 && !e.ctrlKey) { + const rect = (e.target as any).getBoundingClientRect(); + this._ele!.currentTime = this.Document.currentTimecode = (e.clientX - rect.x) / rect.width * NumCast(this.dataDoc.duration); + this.pause(); + e.stopPropagation(); + } + }} > + {DocListCast(this.dataDoc.links).map((l, i) => { + let la1 = l.anchor1 as Doc; + let la2 = l.anchor2 as Doc; + let linkTime = NumCast(l.anchor2Timecode); + if (Doc.AreProtosEqual(la1, this.dataDoc)) { + la1 = l.anchor2 as Doc; + la2 = l.anchor1 as Doc; + linkTime = NumCast(l.anchor1Timecode); + } + return !linkTime ? (null) : + <div className={this.props.PanelHeight() < 32 ? "audiobox-marker-minicontainer" : "audiobox-marker-container"} key={l[Id]} style={{ left: `${linkTime / NumCast(this.dataDoc.duration, 1) * 100}%` }}> + <div className={this.props.PanelHeight() < 32 ? "audioBox-linker-mini" : "audioBox-linker"} key={"linker" + i}> + <DocumentView {...this.props} Document={l} layoutKey={Doc.LinkEndpoint(l, la2)} + parentActive={returnTrue} bringToFront={emptyFunction} zoomToScale={emptyFunction} getScale={returnOne} + backgroundColor={returnTransparent} /> + </div> + <div key={i} className="audiobox-marker" onPointerEnter={() => Doc.linkFollowHighlight(la1)} + onPointerDown={e => { if (e.button === 0 && !e.ctrlKey) { this.playFrom(linkTime); e.stopPropagation(); } }} + onClick={e => { if (e.button === 0 && !e.ctrlKey) { this.pause(); e.stopPropagation(); } }} /> + </div>; + })} + <div className="audiobox-current" style={{ left: `${NumCast(this.Document.currentTimecode) / NumCast(this.dataDoc.duration, 1) * 100}%` }} /> + {this.audio} </div> </div> - } - </div> - ); + </div> + } + </div>; } }
\ No newline at end of file diff --git a/src/client/views/nodes/ButtonBox.tsx b/src/client/views/nodes/ButtonBox.tsx index d1272c266..ee48b47b7 100644 --- a/src/client/views/nodes/ButtonBox.tsx +++ b/src/client/views/nodes/ButtonBox.tsx @@ -36,7 +36,7 @@ export class ButtonBox extends DocComponent<FieldViewProps, ButtonDocument>(Butt @computed get dataDoc() { return this.props.DataDoc && - (this.Document.isTemplateField || BoolCast(this.props.DataDoc.isTemplateField) || + (this.Document.isTemplateForField || BoolCast(this.props.DataDoc.isTemplateForField) || this.props.DataDoc.layout === this.props.Document) ? this.props.DataDoc : Doc.GetProto(this.props.Document); } @@ -80,7 +80,10 @@ export class ButtonBox extends DocComponent<FieldViewProps, ButtonDocument>(Butt return ( <div className="buttonBox-outerDiv" ref={this.createDropTarget} onContextMenu={this.specificContextMenu} style={{ boxShadow: this.Document.opacity === 0 ? undefined : StrCast(this.Document.boxShadow, "") }}> - <div className="buttonBox-mainButton" style={{ background: this.Document.backgroundColor || "", color: this.Document.color || "black", fontSize: this.Document.fontSize }} > + <div className="buttonBox-mainButton" style={{ + background: this.Document.backgroundColor, color: this.Document.color || "black", + fontSize: this.Document.fontSize, letterSpacing: this.Document.letterSpacing || "", textTransform: this.Document.textTransform || "" + }} > <div className="buttonBox-mainButtonCenter"> {(this.Document.text || this.Document.title)} </div> diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index 614a68e7a..3bceec45f 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -2,7 +2,6 @@ import anime from "animejs"; import { computed, IReactionDisposer, observable, reaction, trace } from "mobx"; import { observer } from "mobx-react"; import { Doc, HeightSym, WidthSym } from "../../../new_fields/Doc"; -import { listSpec } from "../../../new_fields/Schema"; import { Cast, NumCast, StrCast } from "../../../new_fields/Types"; import { Transform } from "../../util/Transform"; import { DocComponent } from "../DocComponent"; @@ -15,9 +14,12 @@ import { returnFalse } from "../../../Utils"; import { ContentFittingDocumentView } from "./ContentFittingDocumentView"; export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps { - dataProvider?: (doc: Doc) => { x: number, y: number, width: number, height: number, z: number, transition?: string } | undefined; + dataProvider?: (doc: Doc) => { x: number, y: number, zIndex?: number, highlight?: boolean, width: number, height: number, z: number, transition?: string } | undefined; x?: number; y?: number; + z?: number; + zIndex?: number; + highlight?: boolean; width?: number; height?: number; jitterRotation: number; @@ -27,68 +29,48 @@ export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps { @observer export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeFormDocumentViewProps, PositionDocument>(PositionDocument) { - _disposer: IReactionDisposer | undefined = undefined; + @observable _animPos: number[] | undefined = undefined; get displayName() { return "CollectionFreeFormDocumentView(" + this.props.Document.title + ")"; } // this makes mobx trace() statements more descriptive get transform() { return `scale(${this.props.ContentScaling()}) translate(${this.X}px, ${this.Y}px) rotate(${anime.random(-1, 1) * this.props.jitterRotation}deg)`; } - get X() { return this._animPos !== undefined ? this._animPos[0] : this.renderScriptDim ? this.renderScriptDim.x : this.props.x !== undefined ? this.props.x : this.dataProvider ? this.dataProvider.x : (this.Document.x || 0); } - get Y() { return this._animPos !== undefined ? this._animPos[1] : this.renderScriptDim ? this.renderScriptDim.y : this.props.y !== undefined ? this.props.y : this.dataProvider ? this.dataProvider.y : (this.Document.y || 0); } + get X() { return this.renderScriptDim ? this.renderScriptDim.x : this.props.x !== undefined ? this.props.x : this.dataProvider ? this.dataProvider.x : (this.Document.x || 0); } + get Y() { return this.renderScriptDim ? this.renderScriptDim.y : this.props.y !== undefined ? this.props.y : this.dataProvider ? this.dataProvider.y : (this.Document.y || 0); } + get ZInd() { return this.dataProvider ? this.dataProvider.zIndex : (this.Document.zIndex || 0); } + get Highlight() { return this.dataProvider?.highlight; } get width() { return this.renderScriptDim ? this.renderScriptDim.width : this.props.width !== undefined ? this.props.width : this.props.dataProvider && this.dataProvider ? this.dataProvider.width : this.layoutDoc[WidthSym](); } get height() { const hgt = this.renderScriptDim ? this.renderScriptDim.height : this.props.height !== undefined ? this.props.height : this.props.dataProvider && this.dataProvider ? this.dataProvider.height : this.layoutDoc[HeightSym](); return (hgt === undefined && this.nativeWidth && this.nativeHeight) ? this.width * this.nativeHeight / this.nativeWidth : hgt; } @computed get dataProvider() { return this.props.dataProvider && this.props.dataProvider(this.props.Document) ? this.props.dataProvider(this.props.Document) : undefined; } - @computed get nativeWidth() { return NumCast(this.layoutDoc.nativeWidth); } - @computed get nativeHeight() { return NumCast(this.layoutDoc.nativeHeight); } + @computed get nativeWidth() { return NumCast(this.layoutDoc._nativeWidth); } + @computed get nativeHeight() { return NumCast(this.layoutDoc._nativeHeight); } @computed get renderScriptDim() { if (this.Document.renderScript) { const someView = Cast(this.props.Document.someView, Doc); const minimap = Cast(this.props.Document.minimap, Doc); if (someView instanceof Doc && minimap instanceof Doc) { - const x = (NumCast(someView.panX) - NumCast(someView.width) / 2 / NumCast(someView.scale) - (NumCast(minimap.fitX) - NumCast(minimap.fitW) / 2)) / NumCast(minimap.fitW) * NumCast(minimap.width) - NumCast(minimap.width) / 2; - const y = (NumCast(someView.panY) - NumCast(someView.height) / 2 / NumCast(someView.scale) - (NumCast(minimap.fitY) - NumCast(minimap.fitH) / 2)) / NumCast(minimap.fitH) * NumCast(minimap.height) - NumCast(minimap.height) / 2; - const w = NumCast(someView.width) / NumCast(someView.scale) / NumCast(minimap.fitW) * NumCast(minimap.width); - const h = NumCast(someView.height) / NumCast(someView.scale) / NumCast(minimap.fitH) * NumCast(minimap.height); + const x = (NumCast(someView._panX) - NumCast(someView._width) / 2 / NumCast(someView.scale) - (NumCast(minimap.fitX) - NumCast(minimap.fitW) / 2)) / NumCast(minimap.fitW) * NumCast(minimap._width) - NumCast(minimap._width) / 2; + const y = (NumCast(someView._panY) - NumCast(someView._height) / 2 / NumCast(someView.scale) - (NumCast(minimap.fitY) - NumCast(minimap.fitH) / 2)) / NumCast(minimap.fitH) * NumCast(minimap._height) - NumCast(minimap._height) / 2; + const w = NumCast(someView._width) / NumCast(someView.scale) / NumCast(minimap.fitW) * NumCast(minimap.width); + const h = NumCast(someView._height) / NumCast(someView.scale) / NumCast(minimap.fitH) * NumCast(minimap.height); return { x: x, y: y, width: w, height: h }; } } return undefined; } - componentWillUnmount() { - this._disposer && this._disposer(); - } - componentDidMount() { - this._disposer = reaction(() => this.props.Document.animateToPos ? Array.from(Cast(this.props.Document.animateToPos, listSpec("number"))!) : undefined, - target => this._animPos = !target ? undefined : target[2] ? [NumCast(this.layoutDoc.x), NumCast(this.layoutDoc.y)] : this.props.ScreenToLocalTransform().transformPoint(target[0], target[1]), - { fireImmediately: true }); - } - - contentScaling = () => this.nativeWidth > 0 && !this.props.Document.ignoreAspect ? this.width / this.nativeWidth : 1; - panelWidth = () => this.props.PanelWidth(); - panelHeight = () => this.props.PanelHeight(); + contentScaling = () => this.nativeWidth > 0 && !this.props.Document.ignoreAspect && !this.props.fitToBox ? this.width / this.nativeWidth : 1; + clusterColorFunc = (doc: Doc) => this.clusterColor; + panelWidth = () => (this.dataProvider?.width || this.props.PanelWidth()); + panelHeight = () => (this.dataProvider?.height || this.props.PanelHeight()); getTransform = (): Transform => this.props.ScreenToLocalTransform() .translate(-this.X, -this.Y) .scale(1 / this.contentScaling()) - borderRounding = () => { - const ruleRounding = this.props.ruleProvider ? StrCast(this.props.ruleProvider["ruleRounding_" + this.Document.heading]) : undefined; - const ld = this.layoutDoc[StrCast(this.layoutDoc.layoutKey, "layout")] instanceof Doc ? this.layoutDoc[StrCast(this.layoutDoc.layoutKey, "layout")] as Doc : undefined; - const br = StrCast((ld || this.props.Document).borderRounding); - return !br && ruleRounding ? ruleRounding : br; - } - @computed get clusterColor() { return this.props.backgroundColor(this.props.Document); } - - clusterColorFunc = (doc: Doc) => this.clusterColor; - - @observable _animPos: number[] | undefined = undefined; - - finalPanelWidth = () => (this.dataProvider ? this.dataProvider.width : this.panelWidth()); - finalPanelHeight = () => (this.dataProvider ? this.dataProvider.height : this.panelHeight()); - + focusDoc = (doc: Doc) => this.props.focus(doc, false); render() { TraceMobx(); return <div className="collectionFreeFormDocumentView-container" @@ -99,29 +81,32 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF this.clusterColor ? (`${this.clusterColor} ${StrCast(this.layoutDoc.boxShadow, `0vw 0vw ${(this.layoutDoc.isBackground ? 100 : 50) / this.props.ContentScaling()}px`)}`) : // if it's just in a cluster, make the shadown roughly match the cluster border extent this.layoutDoc.isBackground ? undefined : // if it's a background & has a cluster color, make the shadow spread really big StrCast(this.layoutDoc.boxShadow, ""), - borderRadius: this.borderRounding(), + borderRadius: StrCast(Doc.Layout(this.layoutDoc).borderRounding), + outline: this.Highlight ? "orange solid 2px" : "", transform: this.transform, transition: this.Document.isAnimating ? ".5s ease-in" : this.props.transition ? this.props.transition : this.dataProvider ? this.dataProvider.transition : StrCast(this.layoutDoc.transition), width: this.width, height: this.height, - zIndex: this.Document.zIndex || 0, + zIndex: this.ZInd, + display: this.ZInd === -99 ? "none" : undefined, + pointerEvents: this.props.Document.isBackground ? "none" : undefined }} > - {!this.props.fitToBox ? <DocumentView {...this.props} dragDivName={"collectionFreeFormDocumentView-container"} ContentScaling={this.contentScaling} ScreenToLocalTransform={this.getTransform} backgroundColor={this.clusterColorFunc} - PanelWidth={this.finalPanelWidth} - PanelHeight={this.finalPanelHeight} + PanelWidth={this.panelWidth} + PanelHeight={this.panelHeight} /> : <ContentFittingDocumentView {...this.props} + CollectionDoc={this.props.ContainingCollectionDoc} DataDocument={this.props.DataDoc} getTransform={this.getTransform} active={returnFalse} - focus={(doc: Doc) => this.props.focus(doc, false)} - PanelWidth={this.finalPanelWidth} - PanelHeight={this.finalPanelHeight} + focus={this.focusDoc} + PanelWidth={this.panelWidth} + PanelHeight={this.panelHeight} />} </div>; } diff --git a/src/client/views/nodes/ContentFittingDocumentView.scss b/src/client/views/nodes/ContentFittingDocumentView.scss index 2801af441..eb2d93b9a 100644 --- a/src/client/views/nodes/ContentFittingDocumentView.scss +++ b/src/client/views/nodes/ContentFittingDocumentView.scss @@ -19,6 +19,6 @@ .documentView-node:first-child { position: relative; - background: $light-color; + background: "#B59B66"; //$light-color; } }
\ No newline at end of file diff --git a/src/client/views/nodes/ContentFittingDocumentView.tsx b/src/client/views/nodes/ContentFittingDocumentView.tsx index e97445f27..bd1b6166f 100644 --- a/src/client/views/nodes/ContentFittingDocumentView.tsx +++ b/src/client/views/nodes/ContentFittingDocumentView.tsx @@ -24,9 +24,7 @@ interface ContentFittingDocumentViewProps { fitToBox?: boolean; PanelWidth: () => number; PanelHeight: () => number; - ruleProvider: Doc | undefined; focus?: (doc: Doc) => void; - showOverlays?: (doc: Doc) => { title?: string, caption?: string }; CollectionView?: CollectionView; CollectionDoc?: Doc; onClick?: ScriptField; @@ -45,15 +43,16 @@ interface ContentFittingDocumentViewProps { export class ContentFittingDocumentView extends React.Component<ContentFittingDocumentViewProps>{ public get displayName() { return "DocumentView(" + this.props.Document?.title + ")"; } // this makes mobx trace() statements more descriptive private get layoutDoc() { return this.props.Document && Doc.Layout(this.props.Document); } - private get nativeWidth() { return NumCast(this.layoutDoc?.nativeWidth, this.props.PanelWidth()); } - private get nativeHeight() { return NumCast(this.layoutDoc?.nativeHeight, this.props.PanelHeight()); } - private contentScaling = () => { + private get nativeWidth() { return NumCast(this.layoutDoc?._nativeWidth, this.props.PanelWidth()); } + private get nativeHeight() { return NumCast(this.layoutDoc?._nativeHeight, this.props.PanelHeight()); } + @computed get scaling() { const wscale = this.props.PanelWidth() / (this.nativeWidth || this.props.PanelWidth() || 1); if (wscale * this.nativeHeight > this.props.PanelHeight()) { return (this.props.PanelHeight() / (this.nativeHeight || this.props.PanelHeight() || 1)) || 1; } return wscale || 1; } + private contentScaling = () => this.scaling; @undoBatch @action @@ -63,16 +62,20 @@ export class ContentFittingDocumentView extends React.Component<ContentFittingDo this.props.childDocs && this.props.childDocs.map(otherdoc => { const target = Doc.GetProto(otherdoc); target.layout = ComputedField.MakeFunction("this.image_data[0]"); - target.layoutCustom = Doc.MakeDelegate(docDragData.draggedDocuments[0]); + target.layout_custom = Doc.MakeDelegate(docDragData.draggedDocuments[0]); }); e.stopPropagation(); } return true; } - private PanelWidth = () => this.nativeWidth && (!this.props.Document || !this.props.Document.fitWidth) ? this.nativeWidth * this.contentScaling() : this.props.PanelWidth(); - private PanelHeight = () => this.nativeHeight && (!this.props.Document || !this.props.Document.fitWidth) ? this.nativeHeight * this.contentScaling() : this.props.PanelHeight(); + private PanelWidth = () => this.panelWidth; + private PanelHeight = () => this.panelHeight;; + + @computed get panelWidth() { return this.nativeWidth && (!this.props.Document || !this.props.Document._fitWidth) ? this.nativeWidth * this.contentScaling() : this.props.PanelWidth(); } + @computed get panelHeight() { return this.nativeHeight && (!this.props.Document || !this.props.Document._fitWidth) ? this.nativeHeight * this.contentScaling() : this.props.PanelHeight(); } + private getTransform = () => this.props.getTransform().translate(-this.centeringOffset, -this.centeringYOffset).scale(1 / this.contentScaling()); - private get centeringOffset() { return this.nativeWidth && (!this.props.Document || !this.props.Document.fitWidth) ? (this.props.PanelWidth() - this.nativeWidth * this.contentScaling()) / 2 : 0; } + private get centeringOffset() { return this.nativeWidth && (!this.props.Document || !this.props.Document._fitWidth) ? (this.props.PanelWidth() - this.nativeWidth * this.contentScaling()) / 2 : 0; } private get centeringYOffset() { return Math.abs(this.centeringOffset) < 0.001 ? (this.props.PanelHeight() - this.nativeHeight * this.contentScaling()) / 2 : 0; } @computed get borderRounding() { return StrCast(this.props.Document?.borderRounding); } @@ -97,8 +100,6 @@ export class ContentFittingDocumentView extends React.Component<ContentFittingDo LibraryPath={this.props.LibraryPath} fitToBox={this.props.fitToBox} onClick={this.props.onClick} - ruleProvider={this.props.ruleProvider} - showOverlays={this.props.showOverlays} addDocument={this.props.addDocument} removeDocument={this.props.removeDocument} moveDocument={this.props.moveDocument} diff --git a/src/client/views/nodes/DocuLinkBox.tsx b/src/client/views/nodes/DocuLinkBox.tsx index 0d4d50c59..a4a9a62aa 100644 --- a/src/client/views/nodes/DocuLinkBox.tsx +++ b/src/client/views/nodes/DocuLinkBox.tsx @@ -61,10 +61,12 @@ export class DocuLinkBox extends DocComponent<FieldViewProps, DocLinkSchema>(Doc } } onClick = (e: React.MouseEvent) => { - if (Math.abs(e.clientX - this._downx) < 3 && Math.abs(e.clientY - this._downy) < 3 && (e.button !== 2 && !e.ctrlKey && this.props.Document.isButton)) { - DocumentManager.Instance.FollowLink(this.props.Document, this.props.Document[this.props.fieldKey] as Doc, document => this.props.addDocTab(document, undefined, "inTab"), false); + if (!this.props.Document.onClick) { + if (Math.abs(e.clientX - this._downx) < 3 && Math.abs(e.clientY - this._downy) < 3 && (e.button !== 2 && !e.ctrlKey && this.props.Document.isButton)) { + DocumentManager.Instance.FollowLink(this.props.Document, this.props.Document[this.props.fieldKey] as Doc, document => this.props.addDocTab(document, undefined, "inTab"), false); + } + e.stopPropagation(); } - e.stopPropagation(); } render() { diff --git a/src/client/views/nodes/DocumentBox.tsx b/src/client/views/nodes/DocumentBox.tsx index 863ea748b..6b7b652c6 100644 --- a/src/client/views/nodes/DocumentBox.tsx +++ b/src/client/views/nodes/DocumentBox.tsx @@ -96,7 +96,6 @@ export class DocumentBox extends DocComponent<FieldViewProps, DocBoxSchema>(DocB addDocument={this.props.addDocument} moveDocument={this.props.moveDocument} removeDocument={this.props.removeDocument} - ruleProvider={this.props.ruleProvider} addDocTab={this.props.addDocTab} pinToPres={this.props.pinToPres} getTransform={this.getTransform} diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index aa553afb7..ac80e2ec1 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -56,14 +56,12 @@ const ObserverJsxParser: typeof JsxParser = ObserverJsxParser1 as any; export class DocumentContentsView extends React.Component<DocumentViewProps & { isSelected: (outsideReaction: boolean) => boolean, select: (ctrl: boolean) => void, - onClick?: ScriptField, layoutKey: string, - hideOnLeave?: boolean }> { @computed get layout(): string { TraceMobx(); if (!this.layoutDoc) return "<p>awaiting layout</p>"; - const layout = Cast(this.layoutDoc[this.props.layoutKey], "string"); + const layout = Cast(this.layoutDoc[StrCast(this.layoutDoc.layoutKey, this.layoutDoc === this.props.Document ? this.props.layoutKey : "layout")], "string"); if (layout === undefined) { return this.props.Document.data ? "<FieldView {...props} fieldKey='data' />" : @@ -79,12 +77,18 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & { if (this.props.DataDoc === undefined && typeof Doc.LayoutField(this.props.Document) !== "string") { // if there is no dataDoc (ie, we're not rendering a template layout), but this document has a layout document (not a layout string), // then we render the layout document as a template and use this document as the data context for the template layout. - return this.props.Document; + const proto = Doc.GetProto(this.props.Document); + return proto instanceof Promise ? undefined : proto; } - return this.props.DataDoc; + return this.props.DataDoc instanceof Promise ? undefined : this.props.DataDoc; } get layoutDoc() { - return Doc.expandTemplateLayout(Doc.Layout(this.props.Document), this.props.Document); + if (this.props.DataDoc === undefined && typeof Doc.LayoutField(this.props.Document) !== "string") { + // if there is no dataDoc (ie, we're not rendering a template layout), but this document has a layout document (not a layout string), + // then we render the layout document as a template and use this document as the data context for the template layout. + return Doc.expandTemplateLayout(Doc.Layout(this.props.Document), this.props.Document); + } + return Doc.Layout(this.props.Document); } CreateBindings(): JsxBindings { @@ -98,7 +102,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & { render() { TraceMobx(); - return (this.props.renderDepth > 7 || !this.layout) ? (null) : + return (this.props.renderDepth > 7 || !this.layout || !this.layoutDoc) ? (null) : <ObserverJsxParser blacklistedAttrs={[]} components={{ diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 60dc253f7..dcdce5fce 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -9,12 +9,12 @@ import { Id } from '../../../new_fields/FieldSymbols'; import { listSpec } from "../../../new_fields/Schema"; import { ScriptField } from '../../../new_fields/ScriptField'; import { BoolCast, Cast, NumCast, StrCast } from "../../../new_fields/Types"; -import { ImageField } from '../../../new_fields/URLField'; +import { ImageField, PdfField, VideoField, AudioField } from '../../../new_fields/URLField'; import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils"; import { emptyFunction, returnTransparent, returnTrue, Utils, returnOne } from "../../../Utils"; import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils'; import { DocServer } from "../../DocServer"; -import { Docs, DocUtils } from "../../documents/Documents"; +import { Docs, DocUtils, DocumentOptions } from "../../documents/Documents"; import { DocumentType } from '../../documents/DocumentTypes'; import { ClientUtils } from '../../util/ClientUtils'; import { DocumentManager } from "../../util/DocumentManager"; @@ -44,7 +44,13 @@ import { InkTool } from '../../../new_fields/InkField'; import { TraceMobx } from '../../../new_fields/util'; import { List } from '../../../new_fields/List'; import { FormattedTextBoxComment } from './FormattedTextBoxComment'; +import { GestureUtils } from '../../../pen-gestures/GestureUtils'; +import { RadialMenu } from './RadialMenu'; +import { RadialMenuProps } from './RadialMenuItem'; + import { CollectionStackingView } from '../collections/CollectionStackingView'; +import { RichTextField } from '../../../new_fields/RichTextField'; +import { HistoryUtil } from '../../util/History'; library.add(fa.faEdit, fa.faTrash, fa.faShare, fa.faDownload, fa.faExpandArrowsAlt, fa.faCompressArrowsAlt, fa.faLayerGroup, fa.faExternalLinkAlt, fa.faAlignCenter, fa.faCaretSquareRight, fa.faSquare, fa.faConciergeBell, fa.faWindowRestore, fa.faFolder, fa.faMapPin, fa.faLink, fa.faFingerprint, fa.faCrosshairs, fa.faDesktop, fa.faUnlock, fa.faLock, fa.faLaptopCode, fa.faMale, @@ -58,15 +64,15 @@ export interface DocumentViewProps { LibraryPath: Doc[]; fitToBox?: boolean; onClick?: ScriptField; + onPointerDown?: ScriptField; + onPointerUp?: ScriptField; dragDivName?: string; addDocument?: (doc: Doc) => boolean; removeDocument?: (doc: Doc) => boolean; moveDocument?: (doc: Doc, targetCollection: Doc | undefined, addDocument: (document: Doc) => boolean) => boolean; ScreenToLocalTransform: () => Transform; renderDepth: number; - showOverlays?: (doc: Doc) => { title?: string, titleHover?: string, caption?: string }; ContentScaling: () => number; - ruleProvider: Doc | undefined; PanelWidth: () => number; PanelHeight: () => number; focus: (doc: Doc, willZoom: boolean, scale?: number, afterFocus?: () => boolean) => void; @@ -82,9 +88,9 @@ export interface DocumentViewProps { ChromeHeight?: () => number; dontRegisterView?: boolean; layoutKey?: string; + radialMenu?: String[]; } - @observer export class DocumentView extends DocComponent<DocumentViewProps, Document>(Document) { private _downX: number = 0; @@ -94,19 +100,86 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu private _hitTemplateDrag = false; private _mainCont = React.createRef<HTMLDivElement>(); private _dropDisposer?: DragManager.DragDropDisposer; + private _gestureEventDisposer?: GestureUtils.GestureEventDisposer; private _titleRef = React.createRef<EditableView>(); + protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer; + public get displayName() { return "DocumentView(" + this.props.Document.title + ")"; } // this makes mobx trace() statements more descriptive public get ContentDiv() { return this._mainCont.current; } @computed get active() { return SelectionManager.IsSelected(this, true) || this.props.parentActive(true); } @computed get topMost() { return this.props.renderDepth === 0; } - @computed get nativeWidth() { return this.layoutDoc.nativeWidth || 0; } - @computed get nativeHeight() { return this.layoutDoc.nativeHeight || 0; } - @computed get onClickHandler() { return this.props.onClick ? this.props.onClick : this.Document.onClick; } + @computed get nativeWidth() { return this.layoutDoc._nativeWidth || 0; } + @computed get nativeHeight() { return this.layoutDoc._nativeHeight || 0; } + @computed get onClickHandler() { return this.props.onClick || this.layoutDoc.onClick || this.Document.onClick; } + @computed get onPointerDownHandler() { return this.props.onPointerDown ? this.props.onPointerDown : this.Document.onPointerDown; } + @computed get onPointerUpHandler() { return this.props.onPointerUp ? this.props.onPointerUp : this.Document.onPointerUp; } + + private _firstX: number = 0; + private _firstY: number = 0; + + + // handle1PointerHoldStart = (e: React.TouchEvent): any => { + // this.onRadialMenu(e); + // const pt = InteractionUtils.GetMyTargetTouches(e, this.prevPoints, true)[0]; + // this._firstX = pt.pageX; + // this._firstY = pt.pageY; + // e.stopPropagation(); + // e.preventDefault(); + + // document.removeEventListener("touchmove", this.onTouch); + // document.removeEventListener("touchmove", this.handle1PointerHoldMove); + // document.addEventListener("touchmove", this.handle1PointerHoldMove); + // document.removeEventListener("touchend", this.handle1PointerHoldEnd); + // document.addEventListener("touchend", this.handle1PointerHoldEnd); + // } + + // handle1PointerHoldMove = (e: TouchEvent): void => { + // const pt = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true)[0]; + // if (Math.abs(pt.pageX - this._firstX) > 150 || Math.abs(pt.pageY - this._firstY) > 150) { + // this.handleRelease(); + // } + // document.removeEventListener("touchmove", this.handle1PointerHoldMove); + // document.addEventListener("touchmove", this.handle1PointerHoldMove); + // document.removeEventListener("touchend", this.handle1PointerHoldEnd); + // document.addEventListener("touchend", this.handle1PointerHoldEnd); + // } + + // handleRelease() { + // RadialMenu.Instance.closeMenu(); + // document.removeEventListener("touchmove", this.handle1PointerHoldMove); + // document.removeEventListener("touchend", this.handle1PointerHoldEnd); + // } + + // handle1PointerHoldEnd = (e: TouchEvent): void => { + // RadialMenu.Instance.closeMenu(); + // document.removeEventListener("touchmove", this.handle1PointerHoldMove); + // document.removeEventListener("touchend", this.handle1PointerHoldEnd); + // } + + // @action + // onRadialMenu = (e: React.TouchEvent): void => { + // const pt = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true)[0]; + + // RadialMenu.Instance.openMenu(); + + // RadialMenu.Instance.addItem({ description: "Open Fields", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { width: 300, height: 300 }), undefined, "onRight"), icon: "layer-group", selected: -1 }); + // RadialMenu.Instance.addItem({ description: "Delete this document", event: () => this.props.ContainingCollectionView?.removeDocument(this.props.Document), icon: "trash", selected: -1 }); + // RadialMenu.Instance.addItem({ description: "Open in a new tab", event: () => this.props.addDocTab(this.props.Document, undefined, "onRight"), icon: "folder", selected: -1 }); + // RadialMenu.Instance.addItem({ description: "Pin to Presentation", event: () => this.props.pinToPres(this.props.Document), icon: "map-pin", selected: -1 }); + + // RadialMenu.Instance.displayMenu(pt.pageX - 15, pt.pageY - 15); + // if (!SelectionManager.IsSelected(this, true)) { + // SelectionManager.SelectDoc(this, false); + // } + // e.stopPropagation(); + // } @action componentDidMount() { this._mainCont.current && (this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, this.drop.bind(this))); + this._mainCont.current && (this._gestureEventDisposer = GestureUtils.MakeGestureTarget(this._mainCont.current, this.onGesture.bind(this))); + this._mainCont.current && (this.multiTouchDisposer = InteractionUtils.MakeMultiTouchTarget(this._mainCont.current, this.onTouchStart.bind(this))); !this.props.dontRegisterView && DocumentManager.Instance.DocumentViews.push(this); } @@ -114,7 +187,11 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu @action componentDidUpdate() { this._dropDisposer && this._dropDisposer(); + this._gestureEventDisposer && this._gestureEventDisposer(); + this.multiTouchDisposer && this.multiTouchDisposer(); this._mainCont.current && (this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, this.drop.bind(this))); + this._mainCont.current && (this._gestureEventDisposer = GestureUtils.MakeGestureTarget(this._mainCont.current, this.onGesture.bind(this))); + this._mainCont.current && (this.multiTouchDisposer = InteractionUtils.MakeMultiTouchTarget(this._mainCont.current, this.onTouchStart.bind(this))); } @action @@ -133,6 +210,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu dragData.moveDocument = this.props.moveDocument;// this.Document.onDragStart ? undefined : this.props.moveDocument; dragData.applyAsTemplate = applyAsTemplate; dragData.dragDivName = this.props.dragDivName; + this.props.Document.sourceContext = this.props.ContainingCollectionDoc; // bcz: !! shouldn't need this ... use search find the document's context dynamically DragManager.StartDocumentDrag([this._mainCont.current], dragData, x, y, { hideSource: !dropAction && !this.Document.onDragStart }); } } @@ -177,21 +255,21 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu } } - onClick = async (e: React.MouseEvent) => { + onClick = undoBatch((e: React.MouseEvent | React.PointerEvent) => { if (!e.nativeEvent.cancelBubble && !this.Document.ignoreClick && CurrentUserUtils.MainDocId !== this.props.Document[Id] && (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD)) { e.stopPropagation(); let preventDefault = true; if (this._doubleTap && this.props.renderDepth && !this.onClickHandler?.script) { // disable double-click to show full screen for things that have an on click behavior since clicking them twice can be misinterpreted as a double click const fullScreenAlias = Doc.MakeAlias(this.props.Document); - if (StrCast(fullScreenAlias.layoutKey) !== "layoutCustom" && fullScreenAlias.layoutCustom !== undefined) { - fullScreenAlias.layoutKey = "layoutCustom"; + if (StrCast(fullScreenAlias.layoutKey) !== "layout_custom" && fullScreenAlias.layout_custom !== undefined) { + fullScreenAlias.layoutKey = "layout_custom"; } this.props.addDocTab(fullScreenAlias, undefined, "inTab"); SelectionManager.DeselectAll(); Doc.UnBrushDoc(this.props.Document); } else if (this.onClickHandler && this.onClickHandler.script) { - this.onClickHandler.script.run({ this: this.Document.isTemplateField && this.props.DataDoc ? this.props.DataDoc : this.props.Document }, console.log); + this.onClickHandler.script.run({ this: this.Document.isTemplateForField && this.props.DataDoc ? this.props.DataDoc : this.props.Document, containingCollection: this.props.ContainingCollectionDoc, shiftKey: e.shiftKey }, console.log); } else if (this.Document.type === DocumentType.BUTTON) { ScriptBox.EditButtonScript("On Button Clicked ...", this.props.Document, "onClick", e.clientX, e.clientY); } else if (this.props.Document.isButton === "Selector") { // this should be moved to an OnClick script @@ -206,7 +284,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu } preventDefault && e.preventDefault(); } - } + }) buttonClick = async (altKey: boolean, ctrlKey: boolean) => { const maximizedDocs = await DocListCastAsync(this.Document.maximizedDocs); @@ -236,37 +314,40 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu } } - handle1PointerDown = (e: React.TouchEvent) => { - if (!e.nativeEvent.cancelBubble) { - const touch = InteractionUtils.GetMyTargetTouches(e, this.prevPoints)[0]; + handle1PointerDown = (e: React.TouchEvent, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>) => { + if (this.Document.onPointerDown) return; + const touch = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true)[0]; + console.log("down"); + if (touch) { this._downX = touch.clientX; this._downY = touch.clientY; - this._hitTemplateDrag = false; - for (let element = (e.target as any); element && !this._hitTemplateDrag; element = element.parentElement) { - if (element.className && element.className.toString() === "collectionViewBaseChrome-collapse") { - this._hitTemplateDrag = true; + if (!e.nativeEvent.cancelBubble) { + this._hitTemplateDrag = false; + for (let element = (e.target as any); element && !this._hitTemplateDrag; element = element.parentElement) { + if (element.className && element.className.toString() === "collectionViewBaseChrome-collapse") { + this._hitTemplateDrag = true; + } } + if ((this.active || this.Document.onDragStart || this.Document.onClick) && !e.ctrlKey && !this.Document.lockedPosition && !this.Document.inOverlay) e.stopPropagation(); + this.removeMoveListeners(); + this.addMoveListeners(); + this.removeEndListeners(); + this.addEndListeners(); + e.stopPropagation(); } - if ((this.active || this.Document.onDragStart || this.Document.onClick) && !e.ctrlKey && !this.Document.lockedPosition && !this.Document.inOverlay) e.stopPropagation(); - document.removeEventListener("touchmove", this.onTouch); - document.addEventListener("touchmove", this.onTouch); - document.removeEventListener("touchend", this.onTouchEnd); - document.addEventListener("touchend", this.onTouchEnd); - if ((e.nativeEvent as any).formattedHandled) e.stopPropagation(); } } - handle1PointerMove = (e: TouchEvent) => { + handle1PointerMove = (e: TouchEvent, me: InteractionUtils.MultiTouchEvent<TouchEvent>) => { if ((e as any).formattedHandled) { e.stopPropagation; return; } if (e.cancelBubble && this.active) { - document.removeEventListener("touchmove", this.onTouch); + this.removeMoveListeners(); } else if (!e.cancelBubble && (SelectionManager.IsSelected(this, true) || this.props.parentActive(true) || this.Document.onDragStart || this.Document.onClick) && !this.Document.lockedPosition && !this.Document.inOverlay) { - const touch = InteractionUtils.GetMyTargetTouches(e, this.prevPoints)[0]; + const touch = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true)[0]; if (Math.abs(this._downX - touch.clientX) > 3 || Math.abs(this._downY - touch.clientY) > 3) { if (!e.altKey && (!this.topMost || this.Document.onDragStart || this.Document.onClick)) { - document.removeEventListener("touchmove", this.onTouch); - document.removeEventListener("touchend", this.onTouchEnd); + this.cleanUpInteractions(); this.startDragging(this._downX, this._downY, this.Document.dropAction ? this.Document.dropAction as any : e.ctrlKey || e.altKey ? "alias" : undefined, this._hitTemplateDrag); } } @@ -276,21 +357,21 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu } } - handle2PointersDown = (e: React.TouchEvent) => { + handle2PointersDown = (e: React.TouchEvent, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>) => { if (!e.nativeEvent.cancelBubble && !this.isSelected()) { e.stopPropagation(); e.preventDefault(); - document.removeEventListener("touchmove", this.onTouch); - document.addEventListener("touchmove", this.onTouch); - document.removeEventListener("touchend", this.onTouchEnd); - document.addEventListener("touchend", this.onTouchEnd); + this.removeMoveListeners(); + this.addMoveListeners(); + this.removeEndListeners(); + this.addEndListeners(); } } @action - handle2PointersMove = (e: TouchEvent) => { - const myTouches = InteractionUtils.GetMyTargetTouches(e, this.prevPoints); + handle2PointersMove = (e: TouchEvent, me: InteractionUtils.MultiTouchEvent<TouchEvent>) => { + const myTouches = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true); const pt1 = myTouches[0]; const pt2 = myTouches[1]; const oldPoint1 = this.prevPoints.get(pt1.identifier); @@ -311,10 +392,10 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu if (dX !== 0 || dY !== 0 || dW !== 0 || dH !== 0) { const doc = PositionDocument(this.props.Document); const layoutDoc = PositionDocument(Doc.Layout(this.props.Document)); - let nwidth = layoutDoc.nativeWidth || 0; - let nheight = layoutDoc.nativeHeight || 0; - const width = (layoutDoc.width || 0); - const height = (layoutDoc.height || (nheight / nwidth * width)); + let nwidth = layoutDoc._nativeWidth || 0; + let nheight = layoutDoc._nativeHeight || 0; + const width = (layoutDoc._width || 0); + const height = (layoutDoc._height || (nheight / nwidth * width)); const scale = this.props.ScreenToLocalTransform().Scale * this.props.ContentScaling(); const actualdW = Math.max(width + (dW * scale), 20); const actualdH = Math.max(height + (dH * scale), 20); @@ -323,62 +404,59 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu const fixedAspect = e.ctrlKey || (!layoutDoc.ignoreAspect && nwidth && nheight); if (fixedAspect && e.ctrlKey && layoutDoc.ignoreAspect) { layoutDoc.ignoreAspect = false; - layoutDoc.nativeWidth = nwidth = layoutDoc.width || 0; - layoutDoc.nativeHeight = nheight = layoutDoc.height || 0; + + layoutDoc._nativeWidth = nwidth = layoutDoc._width || 0; + layoutDoc._nativeHeight = nheight = layoutDoc._height || 0; } if (fixedAspect && (!nwidth || !nheight)) { - layoutDoc.nativeWidth = nwidth = layoutDoc.width || 0; - layoutDoc.nativeHeight = nheight = layoutDoc.height || 0; + layoutDoc._nativeWidth = nwidth = layoutDoc._width || 0; + layoutDoc._nativeHeight = nheight = layoutDoc._height || 0; } if (nwidth > 0 && nheight > 0 && !layoutDoc.ignoreAspect) { if (Math.abs(dW) > Math.abs(dH)) { if (!fixedAspect) { - layoutDoc.nativeWidth = actualdW / (layoutDoc.width || 1) * (layoutDoc.nativeWidth || 0); + layoutDoc._nativeWidth = actualdW / (layoutDoc._width || 1) * (layoutDoc._nativeWidth || 0); } - layoutDoc.width = actualdW; - if (fixedAspect && !layoutDoc.fitWidth) layoutDoc.height = nheight / nwidth * layoutDoc.width; - else layoutDoc.height = actualdH; + layoutDoc._width = actualdW; + if (fixedAspect && !layoutDoc._fitWidth) layoutDoc._height = nheight / nwidth * layoutDoc._width; + else layoutDoc._height = actualdH; } else { if (!fixedAspect) { - layoutDoc.nativeHeight = actualdH / (layoutDoc.height || 1) * (doc.nativeHeight || 0); + layoutDoc._nativeHeight = actualdH / (layoutDoc._height || 1) * (doc._nativeHeight || 0); } - layoutDoc.height = actualdH; - if (fixedAspect && !layoutDoc.fitWidth) layoutDoc.width = nwidth / nheight * layoutDoc.height; - else layoutDoc.width = actualdW; + layoutDoc._height = actualdH; + if (fixedAspect && !layoutDoc._fitWidth) layoutDoc._width = nwidth / nheight * layoutDoc._height; + else layoutDoc._width = actualdW; } } else { - dW && (layoutDoc.width = actualdW); - dH && (layoutDoc.height = actualdH); - dH && layoutDoc.autoHeight && (layoutDoc.autoHeight = false); + dW && (layoutDoc._width = actualdW); + dH && (layoutDoc._height = actualdH); + dH && layoutDoc._autoHeight && (layoutDoc._autoHeight = false); } } - // let newWidth = Math.max(Math.abs(oldPoint1!.clientX - oldPoint2!.clientX), Math.abs(pt1.clientX - pt2.clientX)) - // this.props.Document.width = newWidth; e.stopPropagation(); e.preventDefault(); } } onPointerDown = (e: React.PointerEvent): void => { + if (this.onPointerDownHandler && this.onPointerDownHandler.script) { + this.onPointerDownHandler.script.run({ this: this.Document.isTemplateForField && this.props.DataDoc ? this.props.DataDoc : this.props.Document }, console.log); + document.removeEventListener("pointerup", this.onPointerUp); + document.addEventListener("pointerup", this.onPointerUp); + return; + } // console.log(e.button) // console.log(e.nativeEvent) // continue if the event hasn't been canceled AND we are using a moues or this is has an onClick or onDragStart function (meaning it is a button document) - if (!InteractionUtils.IsType(e, InteractionUtils.MOUSETYPE)) { + if (!(InteractionUtils.IsType(e, InteractionUtils.MOUSETYPE) || InkingControl.Instance.selectedTool === InkTool.Highlighter || InkingControl.Instance.selectedTool === InkTool.Pen)) { if (!InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) { e.stopPropagation(); } return; } - if ((!e.nativeEvent.cancelBubble || this.Document.onClick || this.Document.onDragStart)) { - // if ((e.nativeEvent.cancelBubble && (e.button === 0 || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE))) - // // return if we're inking, and not selecting a button document - // || (InkingControl.Instance.selectedTool !== InkTool.None && !this.Document.onClick) - // // return if using pen or eraser - // || InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || InteractionUtils.IsType(e, InteractionUtils.ERASERTYPE)) { - // return; - // } - + if (!e.nativeEvent.cancelBubble || this.onClickHandler || this.Document.onDragStart) { this._downX = e.clientX; this._downY = e.clientY; this._hitTemplateDrag = false; @@ -389,26 +467,35 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu this._hitTemplateDrag = true; } } - if ((this.active || this.Document.onDragStart || this.Document.onClick) && !e.ctrlKey && (e.button === 0 || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) && !this.Document.lockedPosition && !this.Document.inOverlay) e.stopPropagation(); // events stop at the lowest document that is active. if right dragging, we let it go through though to allow for context menu clicks. PointerMove callbacks should remove themselves if the move event gets stopPropagated by a lower-level handler (e.g, marquee drag); + if ((this.active || this.Document.onDragStart || this.onClickHandler) && + !e.ctrlKey && + (e.button === 0 || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) && + !this.Document.lockedPosition && + !this.Document.inOverlay) { + e.stopPropagation(); // events stop at the lowest document that is active. if right dragging, we let it go through though to allow for context menu clicks. PointerMove callbacks should remove themselves if the move event gets stopPropagated by a lower-level handler (e.g, marquee drag); + } document.removeEventListener("pointermove", this.onPointerMove); document.removeEventListener("pointerup", this.onPointerUp); document.addEventListener("pointermove", this.onPointerMove); document.addEventListener("pointerup", this.onPointerUp); + if ((e.nativeEvent as any).formattedHandled) { e.stopPropagation(); } } } onPointerMove = (e: PointerEvent): void => { + if ((e as any).formattedHandled) { e.stopPropagation(); return; } + if ((InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || InkingControl.Instance.selectedTool === InkTool.Highlighter || InkingControl.Instance.selectedTool === InkTool.Pen)) return; if (e.cancelBubble && this.active) { document.removeEventListener("pointermove", this.onPointerMove); // stop listening to pointerMove if something else has stopPropagated it (e.g., the MarqueeView) } - else if (!e.cancelBubble && (SelectionManager.IsSelected(this, true) || this.props.parentActive(true) || this.Document.onDragStart || this.Document.onClick) && !this.Document.lockedPosition && !this.Document.inOverlay) { + else if (!e.cancelBubble && (SelectionManager.IsSelected(this, true) || this.props.parentActive(true) || this.Document.onDragStart || this.onClickHandler) && !this.Document.lockedPosition && !this.Document.inOverlay) { if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3) { - if (!e.altKey && (!this.topMost || this.Document.onDragStart || this.Document.onClick) && (e.buttons === 1 || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE))) { + if (!e.altKey && (!this.topMost || this.Document.onDragStart || this.onClickHandler) && (e.buttons === 1 || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE))) { document.removeEventListener("pointermove", this.onPointerMove); document.removeEventListener("pointerup", this.onPointerUp); - this.startDragging(this._downX, this._downY, this.Document.dropAction ? this.Document.dropAction as any : e.ctrlKey || e.altKey ? "alias" : undefined, this._hitTemplateDrag); + this.startDragging(this._downX, this._downY, this.props.ContainingCollectionDoc?.childDropAction ? this.props.ContainingCollectionDoc?.childDropAction : this.Document.dropAction ? this.Document.dropAction as any : e.ctrlKey || e.altKey ? "alias" : undefined, this._hitTemplateDrag); } } e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document' however it does mark the event as cancelBubble=true which we test for in the move event handlers @@ -417,54 +504,68 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu } onPointerUp = (e: PointerEvent): void => { + if (this.onPointerUpHandler && this.onPointerUpHandler.script && !InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) { + this.onPointerUpHandler.script.run({ this: this.Document.isTemplateForField && this.props.DataDoc ? this.props.DataDoc : this.props.Document }, console.log); + document.removeEventListener("pointerup", this.onPointerUp); + return; + } document.removeEventListener("pointermove", this.onPointerMove); document.removeEventListener("pointerup", this.onPointerUp); this._doubleTap = (Date.now() - this._lastTap < 300 && e.button === 0 && Math.abs(e.clientX - this._downX) < 2 && Math.abs(e.clientY - this._downY) < 2); this._lastTap = Date.now(); } + onGesture = (e: Event, ge: GestureUtils.GestureEvent) => { + switch (ge.gesture) { + case GestureUtils.Gestures.Line: + ge.callbackFn && ge.callbackFn(this.props.Document); + e.stopPropagation(); + break; + } + } + @undoBatch - deleteClicked = (): void => { SelectionManager.DeselectAll(); this.props.removeDocument && this.props.removeDocument(this.props.Document); } + deleteClicked = (): void => { SelectionManager.DeselectAll(); this.props.removeDocument?.(this.props.Document); } static makeNativeViewClicked = (doc: Doc) => { - undoBatch(() => doc.layoutKey = "layout")(); + undoBatch(() => Doc.setNativeView(doc))(); } - static makeCustomViewClicked = (doc: Doc, dataDoc: Opt<Doc>) => { + static makeCustomViewClicked = (doc: Doc, dataDoc: Opt<Doc>, creator: (documents: Array<Doc>, options: DocumentOptions, id?: string) => Doc, name: string = "custom", docLayoutTemplate?: Doc) => { const batch = UndoManager.StartBatch("CustomViewClicked"); - if (doc.layoutCustom === undefined) { - const width = NumCast(doc.width); - const height = NumCast(doc.height); - const options = { title: "data", width, x: -width / 2, y: - height / 2, }; - - let fieldTemplate: Doc; - switch (doc.type) { - case DocumentType.TEXT: - fieldTemplate = Docs.Create.TextDocument(options); - break; - case DocumentType.PDF: - fieldTemplate = Docs.Create.PdfDocument("http://www.msn.com", options); - break; - case DocumentType.VID: - fieldTemplate = Docs.Create.VideoDocument("http://www.cs.brown.edu", options); - break; - case DocumentType.AUDIO: - fieldTemplate = Docs.Create.AudioDocument("http://www.cs.brown.edu", options); - break; - default: - fieldTemplate = Docs.Create.ImageDocument("http://www.cs.brown.edu", options); + const customName = "layout_" + name; + if (!StrCast(doc.title).endsWith(name)) doc.title = doc.title + "_" + name; + if (doc[customName] === undefined) { + const _width = NumCast(doc._width); + const _height = NumCast(doc._height); + const options = { title: "data", _width, x: -_width / 2, y: - _height / 2, _showSidebar: false }; + + const field = doc.data; + let fieldTemplate: Opt<Doc>; + if (field instanceof RichTextField || typeof (field) === "string") { + fieldTemplate = Docs.Create.TextDocument("", options); + } else if (field instanceof PdfField) { + fieldTemplate = Docs.Create.PdfDocument("http://www.msn.com", options); + } else if (field instanceof VideoField) { + fieldTemplate = Docs.Create.VideoDocument("http://www.cs.brown.edu", options); + } else if (field instanceof AudioField) { + fieldTemplate = Docs.Create.AudioDocument("http://www.cs.brown.edu", options); + } else if (field instanceof ImageField) { + fieldTemplate = Docs.Create.ImageDocument("http://www.cs.brown.edu", options); } - fieldTemplate.backgroundColor = doc.backgroundColor; - fieldTemplate.heading = 1; - fieldTemplate.autoHeight = true; + if (fieldTemplate) { + fieldTemplate.backgroundColor = doc.backgroundColor; + fieldTemplate.heading = 1; + fieldTemplate._autoHeight = true; + } - const docTemplate = Docs.Create.FreeformDocument([fieldTemplate], { title: doc.title + "_layout", width: width + 20, height: Math.max(100, height + 45) }); + const docTemplate = docLayoutTemplate || creator(fieldTemplate ? [fieldTemplate] : [], { title: customName + "(" + doc.title + ")", isTemplateDoc: true, _width: _width + 20, _height: Math.max(100, _height + 45) }); - Doc.MakeMetadataFieldTemplate(fieldTemplate, Doc.GetProto(docTemplate), true); - Doc.ApplyTemplateTo(docTemplate, dataDoc || doc, "layoutCustom", undefined); + fieldTemplate && Doc.MakeMetadataFieldTemplate(fieldTemplate, Doc.GetProto(docTemplate)); + Doc.ApplyTemplateTo(docTemplate, dataDoc || doc, customName, undefined); } else { - doc.layoutKey = "layoutCustom"; + doc.layoutKey = customName; } batch.end(); } @@ -503,7 +604,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu `Link from ${StrCast(de.complete.annoDragData.annotationDocument.title)}`); } if (de.complete.docDragData && de.complete.docDragData.applyAsTemplate) { - Doc.ApplyTemplateTo(de.complete.docDragData.draggedDocuments[0], this.props.Document, "layoutCustom"); + Doc.ApplyTemplateTo(de.complete.docDragData.draggedDocuments[0], this.props.Document, "layout_custom", undefined); e.stopPropagation(); } if (de.complete.linkDragData) { @@ -515,26 +616,14 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu } } - @action - onDrop = (e: React.DragEvent) => { - const text = e.dataTransfer.getData("text/plain"); - if (!e.isDefaultPrevented() && text && text.startsWith("<div")) { - const oldLayout = this.Document.layout || ""; - const layout = text.replace("{layout}", oldLayout); - this.Document.layout = layout; - e.stopPropagation(); - e.preventDefault(); - } - } - @undoBatch @action freezeNativeDimensions = (): void => { - this.layoutDoc.autoHeight = this.layoutDoc.autoHeight = false; + this.layoutDoc._autoHeight = false; this.layoutDoc.ignoreAspect = !this.layoutDoc.ignoreAspect; - if (!this.layoutDoc.ignoreAspect && !this.layoutDoc.nativeWidth) { - this.layoutDoc.nativeWidth = this.props.PanelWidth(); - this.layoutDoc.nativeHeight = this.props.PanelHeight(); + if (!this.layoutDoc.ignoreAspect && !this.layoutDoc._nativeWidth) { + this.layoutDoc._nativeWidth = this.props.PanelWidth(); + this.layoutDoc._nativeHeight = this.props.PanelHeight(); } } @@ -545,7 +634,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu if (!anchors.find(anchor2 => anchor2 && anchor2.title === this.Document.title + ".portal" ? true : false)) { const portalID = (this.Document.title + ".portal").replace(/^-/, "").replace(/\([0-9]*\)$/, ""); DocServer.GetRefField(portalID).then(existingPortal => { - const portal = existingPortal instanceof Doc ? existingPortal : Docs.Create.FreeformDocument([], { width: (this.layoutDoc.width || 0) + 10, height: this.layoutDoc.height || 0, title: portalID }); + const portal = existingPortal instanceof Doc ? existingPortal : Docs.Create.FreeformDocument([], { _width: (this.layoutDoc._width || 0) + 10, _height: this.layoutDoc._height || 0, title: portalID }); DocUtils.MakeLink({ doc: this.props.Document, ctx: this.props.ContainingCollectionDoc }, { doc: portal }, portalID, "portal link"); this.Document.isButton = true; }); @@ -554,27 +643,27 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu @undoBatch @action - setNarrativeView = (custom: boolean): void => { - if (custom) { - this.props.Document.layout_narrative = CollectionView.LayoutString("narrative"); - this.props.Document.nativeWidth = this.props.Document.nativeHeight = undefined; - !this.props.Document.narrative && (Doc.GetProto(this.props.Document).narrative = new List<Doc>([])); - this.props.Document.viewType = CollectionViewType.Stacking; - this.props.Document.layoutKey = "layout_narrative"; - } else { - DocumentView.makeNativeViewClicked(this.props.Document) - } - } - - @undoBatch - @action - setCustomView = (custom: boolean): void => { - if (this.props.ContainingCollectionView?.props.DataDoc || this.props.ContainingCollectionView?.props.Document.isTemplateDoc) { - Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.ContainingCollectionView.props.Document); - } else { - custom ? DocumentView.makeCustomViewClicked(this.props.Document, this.props.DataDoc) : DocumentView.makeNativeViewClicked(this.props.Document); + setCustomView = + (custom: boolean, layout: string): void => { + // if (this.props.ContainingCollectionView?.props.DataDoc || this.props.ContainingCollectionView?.props.Document.isTemplateDoc) { + // Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.ContainingCollectionView.props.Document); + // } else + if (custom) { + DocumentView.makeNativeViewClicked(this.props.Document); + + let foundLayout: Opt<Doc>; + DocListCast(Cast(Doc.UserDoc().expandingButtons, Doc, null)?.data)?.concat([Cast(Doc.UserDoc().iconView, Doc, null)]). + map(btnDoc => (btnDoc.dragFactory as Doc) || btnDoc).filter(doc => doc.isTemplateDoc).forEach(tempDoc => { + if (StrCast(tempDoc.title) === layout) { + foundLayout = tempDoc; + } + }) + DocumentView. + makeCustomViewClicked(this.props.Document, this.props.DataDoc, Docs.Create.StackingDocument, layout, foundLayout); + } else { + DocumentView.makeNativeViewClicked(this.props.Document); + } } - } @undoBatch @action @@ -618,7 +707,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu subitems.push({ description: "Open Right ", event: () => this.props.addDocTab(this.props.Document, this.props.DataDoc, "onRight", this.props.LibraryPath), icon: "caret-square-right" }); subitems.push({ description: "Open Alias Tab ", event: () => this.props.addDocTab(Doc.MakeAlias(this.props.Document), this.props.DataDoc, "inTab"), icon: "folder" }); subitems.push({ description: "Open Alias Right", event: () => this.props.addDocTab(Doc.MakeAlias(this.props.Document), this.props.DataDoc, "onRight"), icon: "caret-square-right" }); - subitems.push({ description: "Open Fields ", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { width: 300, height: 300 }), undefined, "onRight"), icon: "layer-group" }); + subitems.push({ description: "Open Fields ", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), undefined, "onRight"), icon: "layer-group" }); + subitems.push({ description: "Open Repl", icon: "laptop-code", event: () => OverlayView.Instance.addWindow(<ScriptingRepl />, { x: 300, y: 100, width: 200, height: 200, title: "Scripting REPL" }) }); cm.addItem({ description: "Open...", subitems: subitems, icon: "external-link-alt" }); @@ -643,21 +733,15 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu const existing = ContextMenu.Instance.findByDescription("Layout..."); const layoutItems: ContextMenuProps[] = existing && "subitems" in existing ? existing.subitems : []; layoutItems.push({ description: this.Document.isBackground ? "As Foreground" : "As Background", event: this.makeBackground, icon: this.Document.lockedPosition ? "unlock" : "lock" }); - if (this.props.DataDoc) { - layoutItems.push({ description: "Make View of Metadata Field", event: () => Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.DataDoc!), icon: "concierge-bell" }); - } - layoutItems.push({ description: `${this.Document.chromeStatus !== "disabled" ? "Hide" : "Show"} Chrome`, event: () => this.Document.chromeStatus = (this.Document.chromeStatus !== "disabled" ? "disabled" : "enabled"), icon: "project-diagram" }); - layoutItems.push({ description: `${this.Document.autoHeight ? "Variable Height" : "Auto Height"}`, event: () => this.layoutDoc.autoHeight = !this.layoutDoc.autoHeight, icon: "plus" }); - layoutItems.push({ description: this.Document.ignoreAspect || !this.Document.nativeWidth || !this.Document.nativeHeight ? "Freeze" : "Unfreeze", event: this.freezeNativeDimensions, icon: "snowflake" }); + layoutItems.push({ description: "Make View of Metadata Field", event: () => Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.DataDoc), icon: "concierge-bell" }); + + layoutItems.push({ description: `${this.Document._chromeStatus !== "disabled" ? "Hide" : "Show"} Chrome`, event: () => this.Document._chromeStatus = (this.Document._chromeStatus !== "disabled" ? "disabled" : "enabled"), icon: "project-diagram" }); + layoutItems.push({ description: `${this.Document._autoHeight ? "Variable Height" : "Auto Height"}`, event: () => this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight, icon: "plus" }); + layoutItems.push({ description: this.Document.ignoreAspect || !this.Document._nativeWidth || !this.Document._nativeHeight ? "Freeze" : "Unfreeze", event: this.freezeNativeDimensions, icon: "snowflake" }); layoutItems.push({ description: this.Document.lockedPosition ? "Unlock Position" : "Lock Position", event: this.toggleLockPosition, icon: BoolCast(this.Document.lockedPosition) ? "unlock" : "lock" }); layoutItems.push({ description: this.Document.lockedTransform ? "Unlock Transform" : "Lock Transform", event: this.toggleLockTransform, icon: BoolCast(this.Document.lockedTransform) ? "unlock" : "lock" }); layoutItems.push({ description: "Center View", event: () => this.props.focus(this.props.Document, false), icon: "crosshairs" }); layoutItems.push({ description: "Zoom to Document", event: () => this.props.focus(this.props.Document, true), icon: "search" }); - if (this.Document.type !== DocumentType.COL && this.Document.type !== DocumentType.TEMPLATE) { - layoutItems.push({ description: "Use Custom Layout", event: () => DocumentView.makeCustomViewClicked(this.props.Document, this.props.DataDoc), icon: "concierge-bell" }); - } else { - layoutItems.push({ description: "Use Native Layout", event: () => DocumentView.makeNativeViewClicked(this.props.Document), icon: "concierge-bell" }); - } !existing && cm.addItem({ description: "Layout...", subitems: layoutItems, icon: "compass" }); const more = ContextMenu.Instance.findByDescription("More..."); @@ -676,8 +760,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu moreItems.push({ description: "Tag Child Images via Google Photos", event: () => GooglePhotos.Query.TagChildImages(this.props.Document), icon: "caret-square-right" }); moreItems.push({ description: "Write Back Link to Album", event: () => GooglePhotos.Transactions.AddTextEnrichment(this.props.Document), icon: "caret-square-right" }); } - moreItems.push({ description: "Pin to Presentation", event: () => this.props.pinToPres(this.props.Document), icon: "map-pin" }); //I think this should work... and it does! A miracle! - moreItems.push({ description: "Add Repl", icon: "laptop-code", event: () => OverlayView.Instance.addWindow(<ScriptingRepl />, { x: 300, y: 100, width: 200, height: 200, title: "Scripting REPL" }) }); moreItems.push({ description: "Download document", icon: "download", event: async () => console.log(JSON.parse(await rp.get(Utils.CorsProxy("http://localhost:8983/solr/dash/select"), { @@ -702,13 +784,13 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu const mode2 = mode === DocServer.WriteMode.Default ? mode : DocServer.WriteMode.Playground; DocServer.setFieldWriteMode("x", mode1); DocServer.setFieldWriteMode("y", mode1); - DocServer.setFieldWriteMode("width", mode1); - DocServer.setFieldWriteMode("height", mode1); + DocServer.setFieldWriteMode("_width", mode1); + DocServer.setFieldWriteMode("_height", mode1); - DocServer.setFieldWriteMode("panX", mode2); - DocServer.setFieldWriteMode("panY", mode2); + DocServer.setFieldWriteMode("_panX", mode2); + DocServer.setFieldWriteMode("_panY", mode2); DocServer.setFieldWriteMode("scale", mode2); - DocServer.setFieldWriteMode("viewType", mode2); + DocServer.setFieldWriteMode("_viewType", mode2); }; const aclsMenu: ContextMenuProps[] = []; aclsMenu.push({ description: "Default (write/read all)", event: () => setWriteMode(DocServer.WriteMode.Default), icon: DocServer.AclsMode === DocServer.WriteMode.Default ? "check" : "exclamation" }); @@ -735,6 +817,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu } }); const path = this.props.LibraryPath.reduce((p: string, d: Doc) => p + "/" + (Doc.AreProtosEqual(d, (Doc.UserDoc().LibraryBtn as Doc).sourcePanel as Doc) ? "" : d.title), ""); + cm.addItem({ description: "Pin to Presentation", event: () => this.props.pinToPres(this.props.Document), icon: "map-pin" }); cm.addItem({ description: `path: ${path}`, event: () => { this.props.LibraryPath.map(lp => Doc.GetProto(lp).treeViewOpen = lp.treeViewOpen = true); @@ -753,14 +836,20 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu select = (ctrlPressed: boolean) => { SelectionManager.SelectDoc(this, ctrlPressed); }; chromeHeight = () => { - const showOverlays = this.props.showOverlays ? this.props.showOverlays(this.Document) : undefined; - const showTitle = showOverlays && "title" in showOverlays ? showOverlays.title : StrCast(this.layoutDoc.showTitle); - const showTitleHover = showOverlays && "titleHover" in showOverlays ? showOverlays.titleHover : StrCast(this.layoutDoc.showTitleHover); + const showTitle = StrCast(this.layoutDoc.showTitle); + const showTitleHover = StrCast(this.layoutDoc.showTitleHover); return (showTitle && !showTitleHover ? 0 : 0) + 1; } - @computed get finalLayoutKey() { return this.props.layoutKey || StrCast(this.props.Document.layoutKey, "layout"); } - childScaling = () => (this.layoutDoc.fitWidth ? this.props.PanelWidth() / this.nativeWidth : this.props.ContentScaling()); + @computed get finalLayoutKey() { + const { layoutKey } = this.props; + if (typeof layoutKey === "string") { + return layoutKey; + } + const fallback = Cast(this.props.Document.layoutKey, "string"); + return typeof fallback === "string" ? fallback : "layout"; + } + childScaling = () => (this.layoutDoc._fitWidth ? this.props.PanelWidth() / this.nativeWidth : this.props.ContentScaling()); @computed get contents() { TraceMobx(); return (<DocumentContentsView ContainingCollectionView={this.props.ContainingCollectionView} @@ -774,9 +863,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu moveDocument={this.props.moveDocument} ScreenToLocalTransform={this.props.ScreenToLocalTransform} renderDepth={this.props.renderDepth} - showOverlays={this.props.showOverlays} ContentScaling={this.childScaling} - ruleProvider={this.props.ruleProvider} PanelWidth={this.props.PanelWidth} PanelHeight={this.props.PanelHeight} focus={this.props.focus} @@ -808,11 +895,10 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu @computed get innards() { TraceMobx(); - const showOverlays = this.props.showOverlays ? this.props.showOverlays(this.Document) : undefined; - const showTitle = showOverlays && "title" in showOverlays ? showOverlays.title : StrCast(this.getLayoutPropStr("showTitle")); - const showTitleHover = showOverlays && "titleHover" in showOverlays ? showOverlays.titleHover : StrCast(this.getLayoutPropStr("showTitleHover")); - const showCaption = showOverlays && "caption" in showOverlays ? showOverlays.caption : this.getLayoutPropStr("showCaption"); - const showTextTitle = showTitle && StrCast(this.layoutDoc.layout).indexOf("FormattedTextBox") !== -1 ? showTitle : undefined; + const showTitle = StrCast(this.getLayoutPropStr("showTitle")); + const showTitleHover = StrCast(this.getLayoutPropStr("showTitleHover")); + const showCaption = this.getLayoutPropStr("showCaption"); + const showTextTitle = showTitle && (StrCast(this.layoutDoc.layout).indexOf("PresBox") !== -1 || StrCast(this.layoutDoc.layout).indexOf("FormattedTextBox") !== -1) ? showTitle : undefined; const searchHighlight = (!this.Document.searchFields ? (null) : <div className="documentView-searchHighlight"> {this.Document.searchFields} @@ -828,12 +914,12 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu const titleView = (!showTitle ? (null) : <div className={`documentView-titleWrapper${showTitleHover ? "-hover" : ""}`} style={{ position: showTextTitle ? "relative" : "absolute", - pointerEvents: SelectionManager.GetIsDragging() ? "none" : "all", + pointerEvents: SelectionManager.GetIsDragging() || this.onClickHandler || this.Document.ignoreClick ? "none" : "all", }}> <EditableView ref={this._titleRef} - contents={(this.props.DataDoc || this.props.Document)[showTitle]} + contents={(this.props.DataDoc || this.props.Document)[showTitle]?.toString()} display={"block"} height={72} fontSize={12} - GetValue={() => StrCast((this.props.DataDoc || this.props.Document)[showTitle])} + GetValue={() => (this.props.DataDoc || this.props.Document)[showTitle]?.toString()} SetValue={undoBatch((value: string) => (Doc.GetProto(this.props.DataDoc || this.props.Document)[showTitle] = value) ? true : true)} /> </div>); @@ -868,28 +954,22 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu render() { if (!(this.props.Document instanceof Doc)) return (null); - const ruleColor = this.props.ruleProvider ? StrCast(this.props.ruleProvider["ruleColor_" + this.Document.heading]) : undefined; - const ruleRounding = this.props.ruleProvider ? StrCast(this.props.ruleProvider["ruleRounding_" + this.Document.heading]) : undefined; const colorSet = this.setsLayoutProp("backgroundColor"); const clusterCol = this.props.ContainingCollectionDoc && this.props.ContainingCollectionDoc.clusterOverridesDefaultBackground; const backgroundColor = (clusterCol && !colorSet) ? this.props.backgroundColor(this.Document) || StrCast(this.layoutDoc.backgroundColor) : - ruleColor && !colorSet ? ruleColor : StrCast(this.layoutDoc.backgroundColor) || this.props.backgroundColor(this.Document); + StrCast(this.layoutDoc.backgroundColor) || this.props.backgroundColor(this.Document); const fullDegree = Doc.isBrushedHighlightedDegree(this.props.Document); - const borderRounding = this.getLayoutPropStr("borderRounding") || ruleRounding; + const borderRounding = this.getLayoutPropStr("borderRounding"); const localScale = fullDegree; - const animDims = this.Document.animateToDimensions ? Array.from(this.Document.animateToDimensions) : undefined; - const animheight = animDims ? animDims[1] : "100%"; - const animwidth = animDims ? animDims[0] : "100%"; - const highlightColors = ["transparent", "maroon", "maroon", "yellow", "magenta", "cyan", "orange"]; const highlightStyles = ["solid", "dashed", "solid", "solid", "solid", "solid", "solid"]; - let highlighting = fullDegree && this.layoutDoc.type !== DocumentType.FONTICON && this.layoutDoc.viewType !== CollectionViewType.Linear; + let highlighting = fullDegree && this.layoutDoc.type !== DocumentType.FONTICON && this.layoutDoc._viewType !== CollectionViewType.Linear; highlighting = highlighting && this.props.focus !== emptyFunction; // bcz: hack to turn off highlighting onsidebar panel documents. need to flag a document as not highlightable in a more direct way return <div className={`documentView-node${this.topMost ? "-topmost" : ""}`} ref={this._mainCont} onKeyDown={this.onKeyDown} - onDrop={this.onDrop} onContextMenu={this.onContextMenu} onPointerDown={this.onPointerDown} onClick={this.onClick} + onContextMenu={this.onContextMenu} onPointerDown={this.onPointerDown} onClick={this.onClick} onPointerEnter={e => Doc.BrushDoc(this.props.Document)} onPointerLeave={e => Doc.UnBrushDoc(this.props.Document)} style={{ transition: this.Document.isAnimating ? ".5s linear" : StrCast(this.Document.transition), @@ -897,14 +977,15 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu color: StrCast(this.Document.color), outline: highlighting && !borderRounding ? `${highlightColors[fullDegree]} ${highlightStyles[fullDegree]} ${localScale}px` : "solid 0px", border: highlighting && borderRounding ? `${highlightStyles[fullDegree]} ${highlightColors[fullDegree]} ${localScale}px` : undefined, - background: this.layoutDoc.type === DocumentType.FONTICON || this.layoutDoc.viewType === CollectionViewType.Linear ? undefined : backgroundColor, - width: animwidth, - height: animheight, + boxShadow: this.props.Document.isTemplateForField ? "black 0.2vw 0.2vw 0.8vw" : undefined, + background: this.layoutDoc.type === DocumentType.FONTICON || this.layoutDoc._viewType === CollectionViewType.Linear ? undefined : backgroundColor, + width: "100%", + height: "100%", opacity: this.Document.opacity - }} onTouchStart={this.onTouchStart}> + }}> {this.innards} </div>; } } -Scripting.addGlobal(function toggleDetail(doc: any) { doc.layoutKey = StrCast(doc.layoutKey, "layout") === "layout" ? "layoutCustom" : "layout"; });
\ No newline at end of file +Scripting.addGlobal(function toggleDetail(doc: any) { doc.layoutKey = StrCast(doc.layoutKey, "layout") === "layout" ? "layout_custom" : "layout"; });
\ No newline at end of file diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx index c56fde186..dbbb76f83 100644 --- a/src/client/views/nodes/FieldView.tsx +++ b/src/client/views/nodes/FieldView.tsx @@ -27,7 +27,6 @@ export interface FieldViewProps { fitToBox?: boolean; ContainingCollectionView: Opt<CollectionView>; ContainingCollectionDoc: Opt<Doc>; - ruleProvider: Doc | undefined; Document: Doc; DataDoc?: Doc; LibraryPath: Doc[]; @@ -54,7 +53,7 @@ export interface FieldViewProps { @observer export class FieldView extends React.Component<FieldViewProps> { public static LayoutString(fieldType: { name: string }, fieldStr: string) { - return `<${fieldType.name} {...props} fieldKey={'${fieldStr}'}/>`; //e.g., "<ImageBox {...props} fieldKey={"dada} />" + return `<${fieldType.name} {...props} fieldKey={'${fieldStr}'}/>`; //e.g., "<ImageBox {...props} fieldKey={"data} />" } @computed @@ -70,12 +69,12 @@ export class FieldView extends React.Component<FieldViewProps> { // if (typeof field === "string") { // return <p>{field}</p>; // } - else if (field instanceof RichTextField) { - return <FormattedTextBox {...this.props} />; - } - else if (field instanceof ImageField) { - return <ImageBox {...this.props} />; - } + // else if (field instanceof RichTextField) { + // return <FormattedTextBox {...this.props} />; + // } + // else if (field instanceof ImageField) { + // return <ImageBox {...this.props} />; + // } // else if (field instaceof PresBox) { // return <PresBox {...this.props} />; // } diff --git a/src/client/views/nodes/FontIconBox.tsx b/src/client/views/nodes/FontIconBox.tsx index 2433251b3..a191ac4f4 100644 --- a/src/client/views/nodes/FontIconBox.tsx +++ b/src/client/views/nodes/FontIconBox.tsx @@ -5,10 +5,11 @@ import { createSchema, makeInterface } from '../../../new_fields/Schema'; import { DocComponent } from '../DocComponent'; import './FontIconBox.scss'; import { FieldView, FieldViewProps } from './FieldView'; -import { StrCast } from '../../../new_fields/Types'; +import { StrCast, Cast } from '../../../new_fields/Types'; import { Utils } from "../../../Utils"; import { runInAction, observable, reaction, IReactionDisposer } from 'mobx'; import { Doc } from '../../../new_fields/Doc'; +import { ContextMenu } from '../ContextMenu'; const FontIconSchema = createSchema({ icon: "string" }); @@ -32,13 +33,25 @@ export class FontIconBox extends DocComponent<FieldViewProps, FontIconDocument>( } }, { fireImmediately: true }); } + + showTemplate = (): void => { + const dragFactory = Cast(this.props.Document.dragFactory, Doc, null); + dragFactory && this.props.addDocTab(dragFactory, undefined, "onRight"); + } + + specificContextMenu = (): void => { + const cm = ContextMenu.Instance; + cm.addItem({ description: "Show Template", event: this.showTemplate, icon: "tag" }); + } + componentWillUnmount() { - this._backgroundReaction && this._backgroundReaction(); + this._backgroundReaction?.(); } + render() { const referenceDoc = (this.props.Document.dragFactory instanceof Doc ? this.props.Document.dragFactory : this.props.Document); const referenceLayout = Doc.Layout(referenceDoc); - return <button className="fontIconBox-outerDiv" title={StrCast(this.props.Document.title)} ref={this._ref} + return <button className="fontIconBox-outerDiv" title={StrCast(this.props.Document.title)} ref={this._ref} onContextMenu={this.specificContextMenu} style={{ background: StrCast(referenceLayout.backgroundColor), boxShadow: this.props.Document.ischecked ? `4px 4px 12px black` : undefined diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 60842bcb0..9370d3745 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -1,7 +1,7 @@ import { library } from '@fortawesome/fontawesome-svg-core'; import { faEdit, faSmile, faTextHeight, faUpload } from '@fortawesome/free-solid-svg-icons'; import { isEqual } from "lodash"; -import { action, computed, IReactionDisposer, Lambda, observable, reaction, runInAction, trace } from "mobx"; +import { action, computed, IReactionDisposer, Lambda, observable, reaction, runInAction, trace, _allowStateChangesInsideComputed } from "mobx"; import { observer } from "mobx-react"; import { baseKeymap } from "prosemirror-commands"; import { history } from "prosemirror-history"; @@ -12,7 +12,7 @@ import { EditorState, NodeSelection, Plugin, TextSelection, Transaction } from " import { ReplaceStep } from 'prosemirror-transform'; import { EditorView } from "prosemirror-view"; import { DateField } from '../../../new_fields/DateField'; -import { Doc, DocListCastAsync, Opt, WidthSym, HeightSym } from "../../../new_fields/Doc"; +import { Doc, DocListCastAsync, Opt, WidthSym, HeightSym, DataSym, Field } from "../../../new_fields/Doc"; import { Copy, Id } from '../../../new_fields/FieldSymbols'; import { RichTextField } from "../../../new_fields/RichTextField"; import { RichTextUtils } from '../../../new_fields/RichTextUtils'; @@ -27,12 +27,10 @@ import { DictationManager } from '../../util/DictationManager'; import { DragManager } from "../../util/DragManager"; import buildKeymap from "../../util/ProsemirrorExampleTransfer"; import { inpRules } from "../../util/RichTextRules"; -import { DashDocCommentView, FootnoteView, ImageResizeView, DashDocView, OrderedListView, schema, SummaryView } from "../../util/RichTextSchema"; +import { DashDocCommentView, FootnoteView, ImageResizeView, DashDocView, OrderedListView, schema, SummaryView, DashFieldView } from "../../util/RichTextSchema"; import { SelectionManager } from "../../util/SelectionManager"; -import { TooltipLinkingMenu } from "../../util/TooltipLinkingMenu"; -import { TooltipTextMenu } from "../../util/TooltipTextMenu"; import { undoBatch, UndoManager } from "../../util/UndoManager"; -import { DocAnnotatableComponent } from "../DocComponent"; +import { DocAnnotatableComponent, DocAnnotatableProps } from "../DocComponent"; import { DocumentButtonBar } from '../DocumentButtonBar'; import { InkingControl } from "../InkingControl"; import { FieldView, FieldViewProps } from "./FieldView"; @@ -48,17 +46,12 @@ import { CollectionFreeFormView } from '../collections/collectionFreeForm/Collec import { InkTool } from '../../../new_fields/InkField'; import { TraceMobx } from '../../../new_fields/util'; import RichTextMenu from '../../util/RichTextMenu'; -import { DocumentDecorations } from '../DocumentDecorations'; library.add(faEdit); library.add(faSmile, faTextHeight, faUpload); export interface FormattedTextBoxProps { hideOnLeave?: boolean; - height?: string; - color?: string; - outer_div?: (domminus: HTMLElement) => void; - firstinstance?: boolean; } const richTextSchema = createSchema({ @@ -77,7 +70,6 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & public static LayoutString(fieldStr: string) { return FieldView.LayoutString(FormattedTextBox, fieldStr); } public static blankState = () => EditorState.create(FormattedTextBox.Instance.config); public static Instance: FormattedTextBox; - public static ToolTipTextMenu: TooltipTextMenu | undefined = undefined; public ProseRef?: HTMLDivElement; private _ref: React.RefObject<HTMLDivElement> = React.createRef(); private _scrollRef: React.RefObject<HTMLDivElement> = React.createRef(); @@ -92,16 +84,12 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & private _scrollToRegionReactionDisposer: Opt<IReactionDisposer>; private _reactionDisposer: Opt<IReactionDisposer>; private _heightReactionDisposer: Opt<IReactionDisposer>; - private _rulesReactionDisposer: Opt<IReactionDisposer>; private _proxyReactionDisposer: Opt<IReactionDisposer>; private _pullReactionDisposer: Opt<IReactionDisposer>; private _pushReactionDisposer: Opt<IReactionDisposer>; private _buttonBarReactionDisposer: Opt<IReactionDisposer>; private dropDisposer?: DragManager.DragDropDisposer; - @observable private _ruleFontSize = 0; - @observable private _ruleFontFamily = "Arial"; - @observable private _fontAlign = ""; @observable private _entered = false; public static FocusedBox: FormattedTextBox | undefined; @@ -127,10 +115,6 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & return ""; } - public static getToolTip(ev: EditorView) { - return this.ToolTipTextMenu ? this.ToolTipTextMenu : this.ToolTipTextMenu = new TooltipTextMenu(ev); - } - @undoBatch public setFontColor(color: string) { const view = this._editorView!; @@ -160,7 +144,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & const id = Utils.GenerateDeterministicGuid(this.dataDoc[Id] + key); DocServer.GetRefField(value).then(doc => { DocServer.GetRefField(id).then(linkDoc => { - this.dataDoc[key] = doc || Docs.Create.FreeformDocument([], { title: value, width: 500, height: 500 }, value); + this.dataDoc[key] = doc || Docs.Create.FreeformDocument([], { title: value, _width: 500, _height: 500 }, value); DocUtils.Publish(this.dataDoc[key] as Doc, value, this.props.addDocument, this.props.removeDocument); if (linkDoc) { (linkDoc as Doc).anchor2 = this.dataDoc[key] as Doc; } else DocUtils.MakeLink({ doc: this.dataDoc, ctx: this.props.ContainingCollectionDoc }, { doc: this.dataDoc[key] as Doc }, "Ref:" + value, "link to named target", id); @@ -201,9 +185,11 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & const tsel = this._editorView.state.selection.$from; tsel.marks().filter(m => m.type === this._editorView!.state.schema.marks.user_mark).map(m => AudioBox.SetScrubTime(Math.max(0, m.attrs.modified * 5000 - 1000))); this._applyingChange = true; - this.extensionDoc && !this.extensionDoc.lastModified && (this.extensionDoc.backgroundColor = "lightGray"); - this.extensionDoc && (this.extensionDoc.lastModified = new DateField(new Date(Date.now()))); - this.dataDoc[this.props.fieldKey] = new RichTextField(JSON.stringify(state.toJSON()), state.doc.textBetween(0, state.doc.content.size, "\n\n")); + if (!this.props.Document._textTemplate || Doc.GetProto(this.props.Document) === this.dataDoc) { + this.dataDoc[this.props.fieldKey + "-lastModified"] && (this.dataDoc[this.props.fieldKey + "-backgroundColor"] = "lightGray"); + this.dataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now())); + this.dataDoc[this.props.fieldKey] = new RichTextField(JSON.stringify(state.toJSON()), state.doc.textBetween(0, state.doc.content.size, "\n\n")); + } this._applyingChange = false; this.updateTitle(); this.tryUpdateHeight(); @@ -271,15 +257,15 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & newLayout = Doc.MakeDelegate(draggedDoc); newLayout.layout = StrCast(newLayout.layout).replace(/fieldKey={'[^']*'}/, `fieldKey={'${this.props.fieldKey}'}`); } - this.Document.layoutCustom = newLayout; - this.Document.layoutKey = "layoutCustom"; + this.Document.layout_custom = newLayout; + this.Document.layoutKey = "layout_custom"; e.stopPropagation(); // embed document when dragging with a userDropAction or an embedDoc flag set } else if (de.complete.docDragData.userDropAction || de.complete.docDragData.embedDoc) { const target = de.complete.docDragData.droppedDocuments[0]; // const link = DocUtils.MakeLink({ doc: this.dataDoc, ctx: this.props.ContainingCollectionDoc }, { doc: target }, "Embedded Doc:" + target.title); // if (link) { - target.fitToBox = true; + target._fitToBox = true; const node = schema.nodes.dashDoc.create({ width: target[WidthSym](), height: target[HeightSym](), title: "dashDoc", docid: target[Id], @@ -393,7 +379,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & specificContextMenu = (e: React.MouseEvent): void => { const funcs: ContextMenuProps[] = []; - funcs.push({ description: "Toggle Sidebar", event: () => { e.stopPropagation(); this.toggleSidebar(); }, icon: "expand-arrows-alt" }); + funcs.push({ description: "Toggle Sidebar", event: () => { e.stopPropagation(); this.props.Document._showSidebar = !this.props.Document._showSidebar }, icon: "expand-arrows-alt" }); funcs.push({ description: "Record Bullet", event: () => { e.stopPropagation(); this.recordBullet(); }, icon: "expand-arrows-alt" }); ["My Text", "Text from Others", "Todo Items", "Important Items", "Ignore Items", "Disagree Items", "By Recent Minute", "By Recent Hour"].forEach(option => funcs.push({ @@ -485,11 +471,10 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & schema, plugins: [ inputRules(inpRules), - this.tooltipTextMenuPlugin(), + this.richTextMenuPlugin(), history(), keymap(this._keymap), keymap(baseKeymap), - // this.tooltipLinkingMenuPlugin(), new Plugin({ props: { attributes: { class: "ProseMirror-example-setup-style" } @@ -513,7 +498,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & this._reactionDisposer = reaction( () => { - const field = this.dataDoc ? Cast(this.dataDoc[this.props.fieldKey], RichTextField) : undefined; + const field = Cast(this.props.Document._textTemplate || this.dataDoc[this.props.fieldKey], RichTextField); return field ? field.Data : RichTextUtils.Initialize(); }, incomingValue => { @@ -547,46 +532,16 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & ); this._heightReactionDisposer = reaction( - () => [this.layoutDoc[WidthSym](), this.layoutDoc.autoHeight], + () => [this.layoutDoc[WidthSym](), this.layoutDoc._autoHeight], () => this.tryUpdateHeight() ); - this.setupEditor(this.config, this.dataDoc, this.props.fieldKey); + this.setupEditor(this.config, this.props.fieldKey); this._searchReactionDisposer = reaction(() => this.layoutDoc.searchMatch, search => search ? this.highlightSearchTerms([Doc.SearchQuery()]) : this.unhighlightSearchTerms(), { fireImmediately: true }); - this._rulesReactionDisposer = reaction(() => { - const ruleProvider = this.props.ruleProvider; - const heading = NumCast(this.layoutDoc.heading); - if (ruleProvider instanceof Doc) { - return { - align: StrCast(ruleProvider["ruleAlign_" + heading], ""), - font: StrCast(ruleProvider["ruleFont_" + heading], "Arial"), - size: NumCast(ruleProvider["ruleSize_" + heading], 13) - }; - } - return undefined; - }, - action((rules: any) => { - this._ruleFontFamily = rules ? rules.font : "Arial"; - this._ruleFontSize = rules ? rules.size : 0; - rules && setTimeout(() => { - const view = this._editorView!; - if (this.ProseRef) { - const n = new NodeSelection(view.state.doc.resolve(0)); - if (this._editorView!.state.doc.textContent === "") { - view.dispatch(view.state.tr.setSelection(new TextSelection(view.state.doc.resolve(0), view.state.doc.resolve(2))). - replaceSelectionWith(this._editorView!.state.schema.nodes.paragraph.create({ align: rules.align }), true)); - } else if (n.node && n.node.type === view.state.schema.nodes.paragraph) { - view.dispatch(view.state.tr.setNodeMarkup(0, n.node.type, { ...n.node.attrs, align: rules.align })); - } - this.tryUpdateHeight(); - } - }, 0); - }), { fireImmediately: true } - ); this._scrollToRegionReactionDisposer = reaction( () => StrCast(this.layoutDoc.scrollToLinkID), async (scrollToLinkID) => { @@ -743,11 +698,9 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & DocServer.GetRefField(pdfRegionId).then(pdfRegion => { if ((pdfDoc instanceof Doc) && (pdfRegion instanceof Doc)) { setTimeout(async () => { - const extension = Doc.fieldExtensionDoc(pdfDoc, "data"); - if (extension) { - const targetAnnotations = await DocListCastAsync(extension.annotations);// bcz: NO... this assumes the pdf is using its 'data' field. need to have the PDF's view handle updating its own annotations - targetAnnotations && targetAnnotations.push(pdfRegion); - } + const targetField = Doc.LayoutFieldKey(pdfDoc); + const targetAnnotations = await DocListCastAsync(pdfDoc[DataSym][targetField + "-annotations"]);// bcz: better to have the PDF's view handle updating its own annotations + targetAnnotations?.push(pdfRegion); }); const link = DocUtils.MakeLink({ doc: this.props.Document, ctx: this.props.ContainingCollectionDoc }, { doc: pdfRegion, ctx: pdfDoc }, "note on " + pdfDoc.title, "pasted PDF link"); @@ -785,28 +738,18 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & } } - private setupEditor(config: any, doc: Doc, fieldKey: string) { - const field = doc ? Cast(doc[fieldKey], RichTextField) : undefined; - let startup = StrCast(doc.documentText); - startup = startup.startsWith("@@@") ? startup.replace("@@@", "") : ""; - if (!field && doc) { - const text = StrCast(doc[fieldKey]); - if (text) { - startup = text; - } else if (Cast(doc[fieldKey], "number")) { - startup = NumCast(doc[fieldKey], 99).toString(); - } - } + private setupEditor(config: any, fieldKey: string) { + const rtfField = Cast(this.props.Document._textTemplate || this.dataDoc[fieldKey], RichTextField); if (this.ProseRef) { const self = this; - this._editorView && this._editorView.destroy(); + this._editorView?.destroy(); this._editorView = new EditorView(this.ProseRef, { - state: field && field.Data ? EditorState.fromJSON(config, JSON.parse(field.Data)) : EditorState.create(config), + state: rtfField?.Data ? EditorState.fromJSON(config, JSON.parse(rtfField.Data)) : EditorState.create(config), handleScrollToSelection: (editorView) => { const ref = editorView.domAtPos(editorView.state.selection.from); let refNode = ref.node as any; while (refNode && !("getBoundingClientRect" in refNode)) refNode = refNode.parentElement; - const r1 = refNode && refNode.getBoundingClientRect(); + const r1 = refNode?.getBoundingClientRect(); const r3 = self._ref.current!.getBoundingClientRect(); if (r1.top < r3.top || r1.top > r3.bottom) { r1 && (self._scrollRef.current!.scrollTop += (r1.top - r3.top) * self.props.ScreenToLocalTransform().Scale); @@ -816,6 +759,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & dispatchTransaction: this.dispatchTransaction, nodeViews: { dashComment(node, view, getPos) { return new DashDocCommentView(node, view, getPos); }, + dashField(node, view, getPos) { return new DashFieldView(node, view, getPos, self); }, dashDoc(node, view, getPos) { return new DashDocView(node, view, getPos, self); }, image(node, view, getPos) { return new ImageResizeView(node, view, getPos, self.props.addDocTab); }, summary(node, view, getPos) { return new SummaryView(node, view, getPos); }, @@ -826,9 +770,9 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & handlePaste: this.handlePaste, }); this._editorView.state.schema.Document = this.props.Document; - if (startup && this._editorView) { - Doc.GetProto(doc).documentText = undefined; - this._editorView.dispatch(this._editorView.state.tr.insertText(startup)); + const startupText = !rtfField && this._editorView && Field.toString(this.dataDoc[fieldKey] as Field); + if (startupText) { + this._editorView.dispatch(this._editorView.state.tr.insertText(startupText)); } } @@ -837,8 +781,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & FormattedTextBox.SelectOnLoad = ""; this.props.select(false); } - const rtf = doc ? Cast(doc[fieldKey], RichTextField) : undefined; - (selectOnLoad || (rtf && !rtf.Text)) && this._editorView!.focus(); + (selectOnLoad /* || !rtfField?.Text*/) && this._editorView!.focus(); // add user mark for any first character that was typed since the user mark that gets set in KeyPress won't have been called yet. this._editorView!.state.storedMarks = [...(this._editorView!.state.storedMarks ? this._editorView!.state.storedMarks : []), schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.round(Date.now() / 1000 / 5) })]; } @@ -857,7 +800,6 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & componentWillUnmount() { this._scrollToRegionReactionDisposer && this._scrollToRegionReactionDisposer(); - this._rulesReactionDisposer && this._rulesReactionDisposer(); this._reactionDisposer && this._reactionDisposer(); this._proxyReactionDisposer && this._proxyReactionDisposer(); this._pushReactionDisposer && this._pushReactionDisposer(); @@ -1038,25 +980,16 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & } } - tooltipTextMenuPlugin() { + richTextMenuPlugin() { const self = FormattedTextBox; return new Plugin({ view(newView) { - // return self.ToolTipTextMenu = FormattedTextBox.getToolTip(newView); RichTextMenu.Instance.changeView(newView); return RichTextMenu.Instance; } }); } - tooltipLinkingMenuPlugin() { - const myprops = this.props; - return new Plugin({ - view(_editorView) { - return new TooltipLinkingMenu(_editorView, myprops); - } - }); - } onBlur = (e: any) => { //DictationManager.Controls.stop(false); if (this._undoTyping) { @@ -1111,47 +1044,46 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & @action tryUpdateHeight(limitHeight?: number) { let scrollHeight = this._ref.current?.scrollHeight; - if (!this.layoutDoc.animateToPos && this.layoutDoc.autoHeight && scrollHeight && + if (this.layoutDoc._autoHeight && scrollHeight && getComputedStyle(this._ref.current!.parentElement!).top === "0px") { // if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation if (limitHeight && scrollHeight > limitHeight) { scrollHeight = limitHeight; this.layoutDoc.limitHeight = undefined; - this.layoutDoc.autoHeight = false; + this.layoutDoc._autoHeight = false; } - const nh = this.Document.isTemplateField ? 0 : NumCast(this.dataDoc.nativeHeight, 0); - const dh = NumCast(this.layoutDoc.height, 0); + const nh = this.Document.isTemplateForField ? 0 : NumCast(this.dataDoc._nativeHeight, 0); + const dh = NumCast(this.layoutDoc._height, 0); const newHeight = Math.max(10, (nh ? dh / nh * scrollHeight : scrollHeight) + (this.props.ChromeHeight ? this.props.ChromeHeight() : 0)); if (Math.abs(newHeight - dh) > 1) { // bcz: Argh! without this, we get into a React crash if the same document is opened in a freeform view and in the treeview. no idea why, but after dragging the freeform document, selecting it, and selecting text, it will compute to 1 pixel higher than the treeview which causes a cycle - this.layoutDoc.height = newHeight; - this.dataDoc.nativeHeight = nh ? scrollHeight : undefined; + this.layoutDoc._height = newHeight; + this.dataDoc._nativeHeight = nh ? scrollHeight : undefined; } } } @computed get sidebarWidthPercent() { return StrCast(this.props.Document.sidebarWidthPercent, "0%"); } - @computed get sidebarWidth() { return Number(this.sidebarWidthPercent.substring(0, this.sidebarWidthPercent.length - 1)) / 100 * this.props.PanelWidth(); } - @computed get annotationsKey() { return "annotations"; } + sidebarWidth = () => { return Number(this.sidebarWidthPercent.substring(0, this.sidebarWidthPercent.length - 1)) / 100 * this.props.PanelWidth(); } + sidebarScreenToLocal = () => this.props.ScreenToLocalTransform().translate(-(this.props.PanelWidth() - this.sidebarWidth()), 0); + @computed get sidebarColor() { return StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], "transparent")); } render() { TraceMobx(); const rounded = StrCast(this.layoutDoc.borderRounding) === "100%" ? "-rounded" : ""; const interactive = InkingControl.Instance.selectedTool || this.layoutDoc.isBackground; if (this.props.isSelected()) { - // TODO: ftong --> update from dash in richtextmenu - RichTextMenu.Instance.updateFromDash(this._editorView!, undefined, this.props); - // FormattedTextBox.ToolTipTextMenu!.updateFromDash(this._editorView!, undefined, this.props); + this._editorView && RichTextMenu.Instance.updateFromDash(this._editorView, undefined, this.props); } else if (FormattedTextBoxComment.textBox === this) { FormattedTextBoxComment.Hide(); } return ( <div className={`formattedTextBox-cont`} ref={this._ref} style={{ - height: this.layoutDoc.autoHeight ? "max-content" : this.props.height ? this.props.height : undefined, + height: this.layoutDoc._autoHeight ? "max-content" : undefined, background: this.props.hideOnLeave ? "rgba(0,0,0 ,0.4)" : undefined, opacity: this.props.hideOnLeave ? (this._entered ? 1 : 0.1) : 1, - color: this.props.color ? this.props.color : this.props.hideOnLeave ? "white" : "inherit", + color: this.props.hideOnLeave ? "white" : "inherit", pointerEvents: interactive ? "none" : "all", - fontSize: this._ruleFontSize ? this._ruleFontSize : NumCast(this.layoutDoc.fontSize, 13), - fontFamily: this._ruleFontFamily ? this._ruleFontFamily : StrCast(this.layoutDoc.fontFamily, "Crimson Text"), + fontSize: NumCast(this.layoutDoc.fontSize, 13), + fontFamily: StrCast(this.layoutDoc.fontFamily, "Crimson Text"), }} onContextMenu={this.specificContextMenu} onKeyDown={this.onKeyPress} @@ -1169,14 +1101,14 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & <div className={`formattedTextBox-outer`} style={{ width: `calc(100% - ${this.sidebarWidthPercent})`, }} ref={this._scrollRef}> <div className={`formattedTextBox-inner${rounded}`} style={{ whiteSpace: "pre-wrap", pointerEvents: ((this.Document.isButton || this.props.onClick) && !this.props.isSelected()) ? "none" : undefined }} ref={this.createDropTarget} /> </div> - {this.props.Document.hideSidebar ? (null) : this.sidebarWidthPercent === "0%" ? + {!this.props.Document._showSidebar ? (null) : this.sidebarWidthPercent === "0%" ? <div className="formattedTextBox-sidebar-handle" onPointerDown={this.sidebarDown} onClick={e => this.toggleSidebar()} /> : <div className={"formattedTextBox-sidebar" + (InkingControl.Instance.selectedTool !== InkTool.None ? "-inking" : "")} - style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${StrCast(this.extensionDoc?.backgroundColor, "transparent")}` }}> + style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}> <CollectionFreeFormView {...this.props} PanelHeight={this.props.PanelHeight} - PanelWidth={() => this.sidebarWidth} - annotationsKey={this.annotationsKey} + PanelWidth={this.sidebarWidth} + annotationsKey={this.annotationKey} isAnnotationOverlay={false} focus={this.props.focus} isSelected={this.props.isSelected} @@ -1186,10 +1118,9 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & whenActiveChanged={this.whenActiveChanged} removeDocument={this.removeDocument} moveDocument={this.moveDocument} - addDocument={(doc: Doc) => { doc.hideSidebar = true; return this.addDocument(doc); }} + addDocument={this.addDocument} CollectionView={undefined} - ScreenToLocalTransform={() => this.props.ScreenToLocalTransform().translate(-(this.props.PanelWidth() - this.sidebarWidth), 0)} - ruleProvider={undefined} + ScreenToLocalTransform={this.sidebarScreenToLocal} renderDepth={this.props.renderDepth + 1} ContainingCollectionDoc={this.props.ContainingCollectionDoc} chromeCollapsed={true}> diff --git a/src/client/views/nodes/FormattedTextBoxComment.tsx b/src/client/views/nodes/FormattedTextBoxComment.tsx index f7a530790..fda3e3285 100644 --- a/src/client/views/nodes/FormattedTextBoxComment.tsx +++ b/src/client/views/nodes/FormattedTextBoxComment.tsx @@ -86,7 +86,7 @@ export class FormattedTextBoxComment { DocumentManager.Instance.FollowLink(FormattedTextBoxComment.linkDoc, textBox.props.Document, (doc: Doc, maxLocation: string) => textBox.props.addDocTab(doc, undefined, e.ctrlKey ? "inTab" : "onRight")); } else if (textBox && (FormattedTextBoxComment.tooltipText as any).href) { - textBox.props.addDocTab(Docs.Create.WebDocument((FormattedTextBoxComment.tooltipText as any).href, { title: (FormattedTextBoxComment.tooltipText as any).href, width: 200, height: 400 }), undefined, "onRight"); + textBox.props.addDocTab(Docs.Create.WebDocument((FormattedTextBoxComment.tooltipText as any).href, { title: (FormattedTextBoxComment.tooltipText as any).href, _width: 200, _height: 400 }), undefined, "onRight"); } keep && textBox && FormattedTextBoxComment.start !== undefined && textBox.adoptAnnotation( FormattedTextBoxComment.start, FormattedTextBoxComment.end, FormattedTextBoxComment.mark); @@ -185,7 +185,6 @@ export class FormattedTextBoxComment { active={returnFalse} addDocument={returnFalse} removeDocument={returnFalse} - ruleProvider={undefined} addDocTab={returnFalse} pinToPres={returnFalse} dontRegisterView={true} diff --git a/src/client/views/nodes/IconBox.tsx b/src/client/views/nodes/IconBox.tsx index 9462ff024..172338eb6 100644 --- a/src/client/views/nodes/IconBox.tsx +++ b/src/client/views/nodes/IconBox.tsx @@ -79,7 +79,7 @@ export class IconBox extends React.Component<FieldViewProps> { <Measure offset onResize={(r) => runInAction(() => { if (r.offset!.width || this.props.Document.hideLabel) { this.props.Document.iconWidth = (r.offset!.width + Number(MINIMIZED_ICON_SIZE)); - if (this.props.Document.height === Number(MINIMIZED_ICON_SIZE)) this.props.Document.width = this.props.Document.iconWidth; + if (this.props.Document._height === Number(MINIMIZED_ICON_SIZE)) this.props.Document._width = this.props.Document.iconWidth; } })}> {({ measureRef }) => diff --git a/src/client/views/nodes/ImageBox.scss b/src/client/views/nodes/ImageBox.scss index cf5d999a7..7bbf4a368 100644 --- a/src/client/views/nodes/ImageBox.scss +++ b/src/client/views/nodes/ImageBox.scss @@ -1,12 +1,14 @@ -.imageBox, .imageBox-dragging{ +.imageBox, +.imageBox-dragging { pointer-events: all; border-radius: inherit; - width:100%; - height:100%; + width: 100%; + height: 100%; position: absolute; transform-origin: top left; + .imageBox-fader { - pointer-events: all; + pointer-events: inherit; } } @@ -16,6 +18,14 @@ } } +#upload-icon { + position: absolute; + bottom: 0; + right: 0; + width: 20px; + height: 20px; +} + .imageBox-cont { padding: 0vw; position: absolute; @@ -24,12 +34,12 @@ height: 100%; max-width: 100%; max-height: 100%; - pointer-events: none; - background:transparent; + pointer-events: inherit; + background: transparent; + img { height: auto; width: 100%; - pointer-events: all; } } @@ -55,9 +65,10 @@ padding: 3px; background: white; cursor: pointer; - opacity:0.15; + opacity: 0.15; } -#google-photos:hover{ + +#google-photos:hover { opacity: 1; } diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 634555012..896ac1685 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -4,11 +4,11 @@ import { faAsterisk, faFileAudio, faImage, faPaintBrush } from '@fortawesome/fre import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, computed, observable, runInAction, trace } from 'mobx'; import { observer } from "mobx-react"; -import { Doc, DocListCast, HeightSym, WidthSym } from '../../../new_fields/Doc'; +import { Doc, DocListCast, HeightSym, WidthSym, DataSym } from '../../../new_fields/Doc'; import { List } from '../../../new_fields/List'; import { createSchema, listSpec, makeInterface } from '../../../new_fields/Schema'; import { ComputedField } from '../../../new_fields/ScriptField'; -import { Cast, NumCast } from '../../../new_fields/Types'; +import { Cast, NumCast, StrCast } from '../../../new_fields/Types'; import { AudioField, ImageField } from '../../../new_fields/URLField'; import { Utils, returnOne, emptyFunction } from '../../../Utils'; import { CognitiveServices, Confidence, Service, Tag } from '../../cognitive_services/CognitiveServices'; @@ -24,9 +24,12 @@ import "./ImageBox.scss"; import React = require("react"); import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView'; import { documentSchema } from '../../../new_fields/documentSchemas'; -import { Id } from '../../../new_fields/FieldSymbols'; +import { Id, Copy } from '../../../new_fields/FieldSymbols'; import { TraceMobx } from '../../../new_fields/util'; import { SelectionManager } from '../../util/SelectionManager'; +import { cache } from 'sharp'; +import { ObjectField } from '../../../new_fields/ObjectField'; +import { Networking } from '../../Network'; const requestImageSize = require('../../util/request-image-size'); const path = require('path'); const { Howl } = require('howler'); @@ -39,7 +42,6 @@ library.add(faFileAudio, faAsterisk); export const pageSchema = createSchema({ curPage: "number", fitWidth: "boolean", - rotation: "number", googlePhotosUrl: "string", googlePhotosTags: "string" }); @@ -56,13 +58,22 @@ declare class MediaRecorder { type ImageDocument = makeInterface<[typeof pageSchema, typeof documentSchema]>; const ImageDocument = makeInterface(pageSchema, documentSchema); +const uploadIcons = { + idle: "downarrow.png", + loading: "loading.gif", + success: "greencheck.png", + failure: "redx.png" +}; + @observer export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocument>(ImageDocument) { + protected multiTouchDisposer?: import("../../util/InteractionUtils").InteractionUtils.MultiTouchEventDisposer | undefined; public static LayoutString(fieldKey: string) { return FieldView.LayoutString(ImageBox, fieldKey); } private _imgRef: React.RefObject<HTMLImageElement> = React.createRef(); private _dropDisposer?: DragManager.DragDropDisposer; @observable private _audioState = 0; @observable static _showControls: boolean; + @observable uploadIcon = uploadIcons.idle; protected createDropTarget = (ele: HTMLDivElement) => { this._dropDisposer && this._dropDisposer(); @@ -73,14 +84,21 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum @action drop = (e: Event, de: DragManager.DropEvent) => { if (de.complete.docDragData) { - if (de.altKey && de.complete.docDragData.draggedDocuments.length && de.complete.docDragData.draggedDocuments[0].data instanceof ImageField) { - Doc.GetProto(this.dataDoc)[this.props.fieldKey] = new ImageField(de.complete.docDragData.draggedDocuments[0].data.url); - e.stopPropagation(); + if (de.metaKey) { + de.complete.docDragData.droppedDocuments.forEach(action((drop: Doc) => { + Doc.AddDocToList(this.dataDoc, this.props.fieldKey + "-alternates", drop); + e.stopPropagation(); + })); + } else if (de.altKey || !this.dataDoc[this.props.fieldKey]) { + const layoutDoc = de.complete.docDragData?.draggedDocuments[0]; + const targetField = Doc.LayoutFieldKey(layoutDoc); + if (layoutDoc?.[DataSym][targetField] instanceof ImageField) { + this.dataDoc[this.props.fieldKey] = ObjectField.MakeCopy(layoutDoc[DataSym][targetField] as ImageField); + this.dataDoc[this.props.fieldKey + "-nativeWidth"] = NumCast(layoutDoc[DataSym][targetField + "-nativeWidth"]); + this.dataDoc[this.props.fieldKey + "-nativeHeight"] = NumCast(layoutDoc[DataSym][targetField + "-nativeHeight"]); + e.stopPropagation(); + } } - de.metaKey && de.complete.docDragData.droppedDocuments.forEach(action((drop: Doc) => { - this.extensionDoc && Doc.AddDocToList(Doc.GetProto(this.extensionDoc), "Alternates", drop); - e.stopPropagation(); - })); } } @@ -88,8 +106,7 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum let gumStream: any; let recorder: any; const self = this; - const extensionDoc = this.extensionDoc; - extensionDoc && navigator.mediaDevices.getUserMedia({ + navigator.mediaDevices.getUserMedia({ audio: true }).then(function (stream) { gumStream = stream; @@ -97,18 +114,18 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum recorder.ondataavailable = async function (e: any) { const formData = new FormData(); formData.append("file", e.data); - const res = await fetch(Utils.prepend("/upload"), { + const res = await fetch(Utils.prepend("/uploadFormData"), { method: 'POST', body: formData }); const files = await res.json(); const url = Utils.prepend(files[0].path); // upload to server with known URL - const audioDoc = Docs.Create.AudioDocument(url, { title: "audio test", width: 200, height: 32 }); + const audioDoc = Docs.Create.AudioDocument(url, { title: "audio test", _width: 200, _height: 32 }); audioDoc.treeViewExpandedView = "layout"; - const audioAnnos = Cast(extensionDoc.audioAnnotations, listSpec(Doc)); + const audioAnnos = Cast(this.dataDoc[this.props.fieldKey + "-audioAnnotations"], listSpec(Doc)); if (audioAnnos === undefined) { - extensionDoc.audioAnnotations = new List([audioDoc]); + this.dataDoc[this.props.fieldKey + "-audioAnnotations"] = new List([audioDoc]); } else { audioAnnos.push(audioDoc); } @@ -125,15 +142,15 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum @undoBatch rotate = action(() => { - const nw = this.Document.nativeWidth; - const nh = this.Document.nativeHeight; - const w = this.Document.width; - const h = this.Document.height; - this.Document.rotation = ((this.Document.rotation || 0) + 90) % 360; - this.Document.nativeWidth = nh; - this.Document.nativeHeight = nw; - this.Document.width = h; - this.Document.height = w; + const nw = NumCast(this.Document[this.props.fieldKey + "-nativeWidth"]); + const nh = NumCast(this.Document[this.props.fieldKey + "-nativeHeight"]); + const w = this.Document._width; + const h = this.Document._height; + this.dataDoc[this.props.fieldKey + "-rotation"] = (NumCast(this.dataDoc[this.props.fieldKey + "-rotation"]) + 90) % 360; + this.dataDoc[this.props.fieldKey + "-nativeWidth"] = nh; + this.dataDoc[this.props.fieldKey + "-nativeHeight"] = nw; + this.Document._width = h; + this.Document._height = w; }); specificContextMenu = (e: React.MouseEvent): void => { @@ -159,7 +176,7 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum results.reduce((face: CognitiveServices.Image.Face, faceDocs: List<Doc>) => faceDocs.push(Docs.Get.DocumentHierarchyFromJson(face, `Face: ${face.faceId}`)!), new List<Doc>()); return faceDocs; }; - this.url && this.extensionDoc && CognitiveServices.Image.Appliers.ProcessImage(this.extensionDoc, ["faces"], this.url, Service.Face, converter); + this.url && CognitiveServices.Image.Appliers.ProcessImage(this.dataDoc, [this.props.fieldKey + "-faces"], this.url, Service.Face, converter); } generateMetadata = (threshold: Confidence = Confidence.Excellent) => { @@ -171,12 +188,12 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum const sanitized = tag.name.replace(" ", "_"); tagDoc[sanitized] = ComputedField.MakeFunction(`(${tag.confidence} >= this.confidence) ? ${tag.confidence} : "${ComputedField.undefined}"`); }); - this.extensionDoc && (this.extensionDoc.generatedTags = tagsList); + this.dataDoc[this.props.fieldKey + "-generatedTags"] = tagsList; tagDoc.title = "Generated Tags Doc"; tagDoc.confidence = threshold; return tagDoc; }; - this.url && this.extensionDoc && CognitiveServices.Image.Appliers.ProcessImage(this.extensionDoc, ["generatedTagsDoc"], this.url, Service.ComputerVision, converter); + this.url && CognitiveServices.Image.Appliers.ProcessImage(this.dataDoc, [this.props.fieldKey + "-generatedTagsDoc"], this.url, Service.ComputerVision, converter); } @computed private get url() { @@ -206,39 +223,54 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum if (this._curSuffix === "_m") this._mediumRetryCount++; if (this._curSuffix === "_l") this._largeRetryCount++; } - @action onError = () => { + @action onError = (error: any) => { const timeout = this._curSuffix === "_s" ? this._smallRetryCount : this._curSuffix === "_m" ? this._mediumRetryCount : this._largeRetryCount; if (timeout < 10) { - setTimeout(this.retryPath, Math.min(10000, timeout * 5)); + // setTimeout(this.retryPath, 500); + } + const original = StrCast(this.dataDoc.originalUrl); + if (error.type === "error" && original) { + this.dataDoc[this.props.fieldKey] = new ImageField(original); } } _curSuffix = "_m"; - _resized = ""; resize = (imgPath: string) => { - requestImageSize(imgPath) - .then((size: any) => { - const rotation = NumCast(this.dataDoc.rotation) % 180; - const realsize = rotation === 90 || rotation === 270 ? { height: size.width, width: size.height } : size; - const aspect = realsize.height / realsize.width; - if (this.Document.width && (Math.abs(1 - NumCast(this.Document.height) / NumCast(this.Document.width) / (realsize.height / realsize.width)) > 0.1)) { - setTimeout(action(() => { - if (this.pathInfos.srcpath === imgPath && (!this.layoutDoc.isTemplateDoc || this.dataDoc !== this.layoutDoc)) { - this._resized = imgPath; - this.Document.height = this.Document[WidthSym]() * aspect; - this.Document.nativeHeight = realsize.height; - this.Document.nativeWidth = realsize.width; - } - }), 0); - } else this._resized = imgPath; + const cachedNativeSize = { + width: NumCast(this.dataDoc[this.props.fieldKey + "-nativeWidth"]), + height: NumCast(this.dataDoc[this.props.fieldKey + "-nativeHeight"]) + }; + const cachedImgPath = this.dataDoc[this.props.fieldKey + "-imgPath"]; + if (!cachedNativeSize.width || !cachedNativeSize.height || imgPath !== cachedImgPath) { + (!this.layoutDoc.isTemplateDoc || this.dataDoc !== this.layoutDoc) && requestImageSize(imgPath).then((inquiredSize: any) => { + const rotation = NumCast(this.dataDoc[this.props.fieldKey + "-rotation"]) % 180; + const rotatedNativeSize = rotation === 90 || rotation === 270 ? { height: inquiredSize.width, width: inquiredSize.height } : inquiredSize; + const rotatedAspect = rotatedNativeSize.height / rotatedNativeSize.width; + const docAspect = this.Document[HeightSym]() / this.Document[WidthSym](); + setTimeout(action(() => { + if (this.Document[WidthSym]() && (!cachedNativeSize.width || !cachedNativeSize.height || Math.abs(1 - docAspect / rotatedAspect) > 0.1)) { + this.Document._height = this.Document[WidthSym]() * rotatedAspect; + this.dataDoc[this.props.fieldKey + "-nativeWidth"] = this.Document._nativeWidth = rotatedNativeSize.width; + this.dataDoc[this.props.fieldKey + "-nativeHeight"] = this.Document._nativeHeight = rotatedNativeSize.height; + } + this.dataDoc[this.props.fieldKey + "-imgPath"] = imgPath; + }), 0); }) - .catch((err: any) => console.log(err)); + .catch((err: any) => console.log(err)); + } else if (this.Document._nativeWidth !== cachedNativeSize.width || this.Document._nativeHeight !== cachedNativeSize.height) { + !(this.Document[StrCast(this.props.Document.layoutKey)] instanceof Doc) && setTimeout(() => { + if (!(this.Document[StrCast(this.props.Document.layoutKey)] instanceof Doc)) { + this.Document._nativeWidth = cachedNativeSize.width; + this.Document._nativeHeight = cachedNativeSize.height; + } + }, 0); + } } @action onPointerEnter = () => { const self = this; - const audioAnnos = this.extensionDoc && DocListCast(this.extensionDoc.audioAnnotations); + const audioAnnos = DocListCast(this.dataDoc[this.props.fieldKey + "-audioAnnotations"]); if (audioAnnos && audioAnnos.length && this._audioState === 0) { const anno = audioAnnos[Math.floor(Math.random() * audioAnnos.length)]; anno.data instanceof AudioField && new Howl({ @@ -271,45 +303,78 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum return !tags ? (null) : (<img id={"google-tags"} src={"/assets/google_tags.png"} />); } + @computed + private get considerDownloadIcon() { + const data = this.dataDoc[this.props.fieldKey]; + if (!(data instanceof ImageField)) { + return (null); + } + const primary = data.url.href; + if (primary.includes(window.location.origin)) { + return (null); + } + return ( + <img + id={"upload-icon"} + src={`/assets/${this.uploadIcon}`} + onClick={async () => { + const { dataDoc } = this; + const { success, failure, idle, loading } = uploadIcons; + runInAction(() => this.uploadIcon = loading); + const [{ clientAccessPath }] = await Networking.PostToServer("/uploadRemoteImage", { sources: [primary] }); + dataDoc.originalUrl = primary; + let succeeded = true; + let data: ImageField | undefined; + try { + data = new ImageField(Utils.prepend(clientAccessPath)); + } catch { + succeeded = false; + } + runInAction(() => this.uploadIcon = succeeded ? success : failure); + setTimeout(action(() => { + this.uploadIcon = idle; + if (data) { + dataDoc[this.props.fieldKey] = data; + } + }), 2000); + }} + /> + ); + } + @computed get nativeSize() { const pw = typeof this.props.PanelWidth === "function" ? this.props.PanelWidth() : typeof this.props.PanelWidth === "number" ? (this.props.PanelWidth as any) as number : 50; - const nativeWidth = (this.Document.nativeWidth || pw); - const nativeHeight = (this.Document.nativeHeight || 1); + const nativeWidth = NumCast(this.dataDoc[this.props.fieldKey + "-nativeWidth"], pw); + const nativeHeight = NumCast(this.dataDoc[this.props.fieldKey + "-nativeHeight"], 1); return { nativeWidth, nativeHeight }; } - @computed get pathInfos() { - const extensionDoc = this.extensionDoc!; - const { nativeWidth, nativeHeight } = this.nativeSize; - let paths = [[Utils.CorsProxy("http://www.cs.brown.edu/~bcz/noImage.png"), nativeWidth / nativeHeight]]; + @computed get paths() { + let paths = [Utils.CorsProxy("http://www.cs.brown.edu/~bcz/noImage.png")]; // this._curSuffix = ""; // if (w > 20) { - const alts = DocListCast(extensionDoc.Alternates); - const altpaths = alts.filter(doc => doc.data instanceof ImageField).map(doc => [this.choosePath((doc.data as ImageField).url), doc[WidthSym]() / doc[HeightSym]()]); + const alts = DocListCast(this.dataDoc[this.props.fieldKey + "-alternates"]); + const altpaths = alts.filter(doc => doc.data instanceof ImageField).map(doc => this.choosePath((doc.data as ImageField).url)); const field = this.dataDoc[this.props.fieldKey]; // 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), nativeWidth / nativeHeight]]; + if (field instanceof ImageField) paths = [this.choosePath(field.url)]; paths.push(...altpaths); - const srcpath = paths[Math.min(paths.length - 1, (this.Document.curPage || 0))][0] as string; - const srcaspect = paths[Math.min(paths.length - 1, (this.Document.curPage || 0))][1] as number; - const fadepath = paths[Math.min(paths.length - 1, 1)][0] as string; - return { srcpath, srcaspect, fadepath }; + return paths; } @computed get content() { TraceMobx(); - const extensionDoc = this.extensionDoc; - if (!extensionDoc) return (null); - const { srcpath, srcaspect, fadepath } = this.pathInfos; + const srcpath = this.paths[NumCast(this.props.Document.curPage, 0)]; + const fadepath = this.paths[Math.min(1, this.paths.length - 1)]; const { nativeWidth, nativeHeight } = this.nativeSize; - const rotation = NumCast(this.Document.rotation, 0); + const rotation = NumCast(this.dataDoc[this.props.fieldKey + "-rotation"]); const aspect = (rotation % 180) ? this.Document[HeightSym]() / this.Document[WidthSym]() : 1; const shift = (rotation % 180) ? (nativeHeight - nativeWidth / aspect) / 2 : 0; - !this.Document.ignoreAspect && this._resized !== srcpath && this.resize(srcpath); + !this.Document.ignoreAspect && this.resize(srcpath); return <div className="imageBox-cont" key={this.props.Document[Id]} ref={this.createDropTarget} onContextMenu={this.specificContextMenu}> <div className="imageBox-fader" > @@ -319,7 +384,7 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum width={nativeWidth} ref={this._imgRef} onError={this.onError} /> - {fadepath === srcpath ? (null) : <div className="imageBox-fadeBlocker" style={{ width: nativeWidth, height: nativeWidth / srcaspect }}> + {fadepath === srcpath ? (null) : <div className="imageBox-fadeBlocker"> <img className="imageBox-fadeaway" key={"fadeaway" + this._smallRetryCount + (this._mediumRetryCount << 4) + (this._largeRetryCount << 8)} // force cache to update on retrys src={fadepath} @@ -334,10 +399,12 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum style={{ height: `calc(${.1 * nativeHeight / nativeWidth * 100}%)` }} > <FontAwesomeIcon className="imageBox-audioFont" - style={{ color: [DocListCast(extensionDoc.audioAnnotations).length ? "blue" : "gray", "green", "red"][this._audioState] }} icon={!DocListCast(extensionDoc.audioAnnotations).length ? "microphone" : faFileAudio} size="sm" /> + style={{ color: [DocListCast(this.dataDoc[this.props.fieldKey + "-audioAnnotations"]).length ? "blue" : "gray", "green", "red"][this._audioState] }} + icon={!DocListCast(this.dataDoc[this.props.fieldKey + "-audioAnnotations"]).length ? "microphone" : faFileAudio} size="sm" /> </div> + {this.considerDownloadIcon} {this.considerGooglePhotosLink()} - <FaceRectangles document={extensionDoc} color={"#0000FF"} backgroundColor={"#0000FF"} /> + <FaceRectangles document={this.dataDoc} color={"#0000FF"} backgroundColor={"#0000FF"} /> </div>; } @@ -349,12 +416,13 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum style={{ transform: `scale(${this.props.ContentScaling()})`, width: `${100 / this.props.ContentScaling()}%`, - height: `${100 / this.props.ContentScaling()}%` + height: `${100 / this.props.ContentScaling()}%`, + pointerEvents: this.props.Document.isBackground ? "none" : undefined }} > <CollectionFreeFormView {...this.props} PanelHeight={this.props.PanelHeight} PanelWidth={this.props.PanelWidth} - annotationsKey={this.annotationsKey} + annotationsKey={this.annotationKey} isAnnotationOverlay={true} focus={this.props.focus} isSelected={this.props.isSelected} @@ -367,7 +435,6 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum addDocument={this.addDocument} CollectionView={undefined} ScreenToLocalTransform={this.props.ScreenToLocalTransform} - ruleProvider={undefined} renderDepth={this.props.renderDepth + 1} ContainingCollectionDoc={this.props.ContainingCollectionDoc} chromeCollapsed={true}> diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx index 234a6a9d3..7aad6f90e 100644 --- a/src/client/views/nodes/KeyValueBox.tsx +++ b/src/client/views/nodes/KeyValueBox.tsx @@ -179,7 +179,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> { } getTemplate = async () => { - const parent = Docs.Create.StackingDocument([], { width: 800, height: 800, title: "Template" }); + const parent = Docs.Create.StackingDocument([], { _width: 800, _height: 800, title: "Template" }); parent.singleColumn = false; parent.columnWidth = 100; for (const row of this.rows.filter(row => row.isChecked)) { @@ -200,17 +200,17 @@ export class KeyValueBox extends React.Component<FieldViewProps> { if (!fieldTemplate) { return; } - const previousViewType = fieldTemplate.viewType; + const previousViewType = fieldTemplate._viewType; Doc.MakeMetadataFieldTemplate(fieldTemplate, Doc.GetProto(parentStackingDoc)); - previousViewType && (fieldTemplate.viewType = previousViewType); + previousViewType && (fieldTemplate._viewType = previousViewType); Cast(parentStackingDoc.data, listSpec(Doc))!.push(fieldTemplate); } inferType = async (data: FieldResult, metaKey: string) => { - const options = { width: 300, height: 300, title: metaKey }; + const options = { _width: 300, _height: 300, title: metaKey }; if (data instanceof RichTextField || typeof data === "string" || typeof data === "number") { - return Docs.Create.TextDocument(options); + return Docs.Create.TextDocument("", options); } else if (data instanceof List) { if (data.length === 0) { return Docs.Create.StackingDocument([], options); diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx index 91f8bb3b0..e6b512adf 100644 --- a/src/client/views/nodes/KeyValuePair.tsx +++ b/src/client/views/nodes/KeyValuePair.tsx @@ -46,7 +46,7 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> { if (value instanceof Doc) { e.stopPropagation(); e.preventDefault(); - ContextMenu.Instance.addItem({ description: "Open Fields", event: () => this.props.addDocTab(Docs.Create.KVPDocument(value, { width: 300, height: 300 }), undefined, "onRight"), icon: "layer-group" }); + ContextMenu.Instance.addItem({ description: "Open Fields", event: () => this.props.addDocTab(Docs.Create.KVPDocument(value, { _width: 300, _height: 300 }), undefined, "onRight"), icon: "layer-group" }); ContextMenu.Instance.displayMenu(e.clientX, e.clientY); } } @@ -58,7 +58,6 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> { LibraryPath: [], ContainingCollectionView: undefined, ContainingCollectionDoc: undefined, - ruleProvider: undefined, fieldKey: this.props.keyName, isSelected: returnFalse, select: emptyFunction, diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 8370df6ba..e1c5fd27f 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -50,9 +50,9 @@ export class PDFBox extends DocAnnotatableComponent<FieldViewProps, PdfDocument> constructor(props: any) { super(props); this._initialScale = this.props.ScreenToLocalTransform().Scale; - const nw = this.Document.nativeWidth = NumCast(this.extensionDocSync.nativeWidth, NumCast(this.Document.nativeWidth, 927)); - const nh = this.Document.nativeHeight = NumCast(this.extensionDocSync.nativeHeight, NumCast(this.Document.nativeHeight, 1200)); - !this.Document.fitWidth && !this.Document.ignoreAspect && (this.Document.height = this.Document[WidthSym]() * (nh / nw)); + const nw = this.Document._nativeWidth = NumCast(this.dataDoc[this.props.fieldKey + "-nativeWidth"], NumCast(this.Document._nativeWidth, 927)); + const nh = this.Document._nativeHeight = NumCast(this.dataDoc[this.props.fieldKey + "-nativeHeight"], NumCast(this.Document._nativeHeight, 1200)); + !this.Document._fitWidth && !this.Document.ignoreAspect && (this.Document._height = this.Document[WidthSym]() * (nh / nw)); const backup = "oldPath"; const { Document } = this.props; @@ -90,10 +90,10 @@ export class PDFBox extends DocAnnotatableComponent<FieldViewProps, PdfDocument> } loaded = (nw: number, nh: number, np: number) => { - this.extensionDocSync.numPages = np; - this.extensionDocSync.nativeWidth = this.Document.nativeWidth = nw * 96 / 72; - this.extensionDocSync.nativeHeight = this.Document.nativeHeight = nh * 96 / 72; - !this.Document.fitWidth && !this.Document.ignoreAspect && (this.Document.height = this.Document[WidthSym]() * (nh / nw)); + this.dataDoc[this.props.fieldKey + "-numPages"] = np; + this.dataDoc[this.props.fieldKey + "-nativeWidth"] = this.Document._nativeWidth = nw * 96 / 72; + this.dataDoc[this.props.fieldKey + "-nativeHeight"] = this.Document._nativeHeight = nh * 96 / 72; + !this.Document._fitWidth && !this.Document.ignoreAspect && (this.Document._height = this.Document[WidthSym]() * (nh / nw)); } public search(string: string, fwd: boolean) { this._pdfViewer && this._pdfViewer.search(string, fwd); } @@ -211,7 +211,7 @@ export class PDFBox extends DocAnnotatableComponent<FieldViewProps, PdfDocument> const pdfUrl = Cast(this.dataDoc[this.props.fieldKey], PdfField); const funcs: ContextMenuProps[] = []; pdfUrl && funcs.push({ description: "Copy path", event: () => Utils.CopyText(pdfUrl.url.pathname), icon: "expand-arrows-alt" }); - funcs.push({ description: "Toggle Fit Width " + (this.Document.fitWidth ? "Off" : "On"), event: () => this.Document.fitWidth = !this.Document.fitWidth, icon: "expand-arrows-alt" }); + funcs.push({ description: "Toggle Fit Width " + (this.Document._fitWidth ? "Off" : "On"), event: () => this.Document._fitWidth = !this.Document._fitWidth, icon: "expand-arrows-alt" }); ContextMenu.Instance.addItem({ description: "Pdf Funcs...", subitems: funcs, icon: "asterisk" }); } @@ -220,8 +220,8 @@ export class PDFBox extends DocAnnotatableComponent<FieldViewProps, PdfDocument> @computed get renderTitleBox() { const classname = "pdfBox" + (this.active() ? "-interactive" : ""); return <div className={classname} style={{ - width: !this.props.Document.fitWidth ? this.Document.nativeWidth || 0 : `${100 / this.contentScaling}%`, - height: !this.props.Document.fitWidth ? this.Document.nativeHeight || 0 : `${100 / this.contentScaling}%`, + width: !this.props.Document._fitWidth ? this.Document._nativeWidth || 0 : `${100 / this.contentScaling}%`, + height: !this.props.Document._fitWidth ? this.Document._nativeHeight || 0 : `${100 / this.contentScaling}%`, transform: `scale(${this.contentScaling})` }} > <div className="pdfBox-title-outer"> @@ -252,13 +252,15 @@ export class PDFBox extends DocAnnotatableComponent<FieldViewProps, PdfDocument> render() { const pdfUrl = Cast(this.dataDoc[this.props.fieldKey], PdfField, null); if (this.props.isSelected() || this.props.Document.scrollY !== undefined) this._everActive = true; - if (pdfUrl && this.extensionDoc && (this._everActive || (this.extensionDoc.nativeWidth && this.props.ScreenToLocalTransform().Scale < 2.5))) { + if (pdfUrl && (this._everActive || (this.dataDoc[this.props.fieldKey + "-nativeWidth"] && this.props.ScreenToLocalTransform().Scale < 2.5))) { if (pdfUrl instanceof PdfField && this._pdf) { return this.renderPdfView; } if (!this._pdfjsRequested) { this._pdfjsRequested = true; - Pdfjs.getDocument(pdfUrl.url.href).promise.then(pdf => runInAction(() => this._pdf = pdf)); + const promise = Pdfjs.getDocument(pdfUrl.url.href).promise; + promise.then(pdf => { runInAction(() => { this._pdf = pdf; console.log("promise"); }) }); + } } return this.renderTitleBox; diff --git a/src/client/views/nodes/PresBox.scss b/src/client/views/nodes/PresBox.scss index e5a79ab11..7618aa7e3 100644 --- a/src/client/views/nodes/PresBox.scss +++ b/src/client/views/nodes/PresBox.scss @@ -6,7 +6,7 @@ top: 0; bottom: 0; width: 100%; - min-width: 200px; + min-width: 120px; height: 100%; min-height: 50px; letter-spacing: 2px; @@ -14,12 +14,6 @@ transition: 0.7s opacity ease; pointer-events: all; - .presBox-listCont { - position: relative; - padding-left: 10px; - padding-right: 10px; - } - .presBox-buttons { padding: 10px; width: 100%; @@ -30,4 +24,17 @@ border-radius: 5px; } } + .presBox-backward, .presBox-forward { + width: 25px; + border-radius: 5px; + top:50%; + position: absolute; + display: inline-block; + } + .presBox-backward { + left:5; + } + .presBox-forward { + right:5; + } }
\ No newline at end of file diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx index 1e6894f37..6c4cbba12 100644 --- a/src/client/views/nodes/PresBox.tsx +++ b/src/client/views/nodes/PresBox.tsx @@ -2,23 +2,27 @@ import React = require("react"); import { library } from '@fortawesome/fontawesome-svg-core'; import { faArrowLeft, faArrowRight, faEdit, faMinus, faPlay, faPlus, faStop, faTimes } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { action, computed, reaction, IReactionDisposer } from "mobx"; +import { action, computed, IReactionDisposer, reaction, observable, runInAction } from "mobx"; import { observer } from "mobx-react"; import { Doc, DocListCast, DocListCastAsync } from "../../../new_fields/Doc"; -import { listSpec } from "../../../new_fields/Schema"; +import { listSpec, makeInterface } from "../../../new_fields/Schema"; import { Cast, FieldValue, NumCast } from "../../../new_fields/Types"; import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils"; +import { Docs } from "../../documents/Documents"; import { DocumentManager } from "../../util/DocumentManager"; import { undoBatch } from "../../util/UndoManager"; -import { CollectionViewType } from "../collections/CollectionView"; import { CollectionDockingView } from "../collections/CollectionDockingView"; -import { CollectionView } from "../collections/CollectionView"; +import { CollectionView, CollectionViewType } from "../collections/CollectionView"; import { ContextMenu } from "../ContextMenu"; import { FieldView, FieldViewProps } from './FieldView'; import "./PresBox.scss"; -import { DocumentType } from "../../documents/DocumentTypes"; -import { Docs } from "../../documents/Documents"; -import { ComputedField } from "../../../new_fields/ScriptField"; +import { CollectionCarouselView } from "../collections/CollectionCarouselView"; +import { returnFalse } from "../../../Utils"; +import { ContextMenuProps } from "../ContextMenuItem"; +import { CollectionTimeView } from "../collections/CollectionTimeView"; +import { documentSchema } from "../../../new_fields/documentSchemas"; +import { InkingControl } from "../InkingControl"; +import { InkTool } from "../../../new_fields/InkField"; library.add(faArrowLeft); library.add(faArrowRight); @@ -32,34 +36,41 @@ library.add(faEdit); @observer export class PresBox extends React.Component<FieldViewProps> { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(PresBox, fieldKey); } - _docListChangedReaction: IReactionDisposer | undefined; + _childReaction: IReactionDisposer | undefined; + _slideshowReaction: IReactionDisposer | undefined; + @observable _isChildActive = false; + componentDidMount() { - this._docListChangedReaction = reaction(() => { - const value = FieldValue(Cast(this.props.Document[this.props.fieldKey], listSpec(Doc))); - return value ? value.slice() : value; - }, () => { - const value = FieldValue(Cast(this.props.Document[this.props.fieldKey], listSpec(Doc))); - if (value) { - value.forEach((item, i) => { - if (item instanceof Doc && item.type !== DocumentType.PRESELEMENT) { - const pinDoc = Docs.Create.PresElementBoxDocument({ backgroundColor: "transparent" }); - Doc.GetProto(pinDoc).presentationTargetDoc = item; - Doc.GetProto(pinDoc).title = ComputedField.MakeFunction('(this.presentationTargetDoc instanceof Doc) && this.presentationTargetDoc.title.toString()'); - value.splice(i, 1, pinDoc); + const userDoc = CurrentUserUtils.UserDocument; + this._slideshowReaction = reaction(() => this.props.Document._slideshow, + (slideshow) => { + if (!slideshow) { + let presTemp = Cast(userDoc.presentationTemplate, Doc); + if (presTemp instanceof Promise) { + presTemp.then(presTemp => this.props.Document.childLayout = presTemp); } - }); - } - }); + else if (presTemp === undefined) { + presTemp = userDoc.presentationTemplate = Docs.Create.PresElementBoxDocument({ backgroundColor: "transparent", _xMargin: 5, isTemplateDoc: true, isTemplateForField: "data" }); + } + else { + this.props.Document.childLayout = presTemp; + } + } else { + this.props.Document.childLayout = undefined; + } + }, { fireImmediately: true }); + this._childReaction = reaction(() => this.childDocs.slice(), (children) => children.forEach((child, i) => child.presentationIndex = i), { fireImmediately: true }); } - componentWillUnmount() { - this._docListChangedReaction && this._docListChangedReaction(); + this._childReaction?.(); + this._slideshowReaction?.(); } @computed get childDocs() { return DocListCast(this.props.Document[this.props.fieldKey]); } next = async () => { - const current = NumCast(this.props.Document.selectedDoc); + runInAction(() => Doc.UserDoc().curPresentation = this.props.Document); + const current = NumCast(this.props.Document._itemIndex); //asking to get document at current index const docAtCurrentNext = await this.getDocAtIndex(current + 1); if (docAtCurrentNext !== undefined) { @@ -76,7 +87,8 @@ export class PresBox extends React.Component<FieldViewProps> { } } back = async () => { - const current = NumCast(this.props.Document.selectedDoc); + action(() => Doc.UserDoc().curPresentation = this.props.Document); + const current = NumCast(this.props.Document._itemIndex); //requesting for the doc at current index const docAtCurrent = await this.getDocAtIndex(current); if (docAtCurrent !== undefined) { @@ -115,12 +127,17 @@ export class PresBox extends React.Component<FieldViewProps> { } } + whenActiveChanged = action((isActive: boolean) => this.props.whenActiveChanged(this._isChildActive = isActive)); + active = (outsideReaction?: boolean) => ((InkingControl.Instance.selectedTool === InkTool.None && !this.props.Document.isBackground) && + (this.props.Document.forceActive || this.props.isSelected(outsideReaction) || this._isChildActive || this.props.renderDepth === 0) ? true : false) + /** * This is the method that checks for the actions that need to be performed * after the document has been presented, which involves 3 button options: * Hide Until Presented, Hide After Presented, Fade After Presented */ showAfterPresented = (index: number) => { + action(() => Doc.UserDoc().curPresentation = this.props.Document); this.childDocs.forEach((doc, ind) => { //the order of cases is aligned based on priority if (doc.hideTillShownButton && ind <= index) { @@ -141,6 +158,7 @@ export class PresBox extends React.Component<FieldViewProps> { * Hide Until Presented, Hide After Presented, Fade After Presented */ hideIfNotPresented = (index: number) => { + action(() => Doc.UserDoc().curPresentation = this.props.Document); this.childDocs.forEach((key, ind) => { //the order of cases is aligned based on priority @@ -162,6 +180,7 @@ export class PresBox extends React.Component<FieldViewProps> { * te option open, navigates to that element. */ navigateToElement = async (curDoc: Doc, fromDocIndex: number) => { + action(() => Doc.UserDoc().curPresentation = this.props.Document); const fromDoc = this.childDocs[fromDocIndex].presentationTargetDoc as Doc; let docToJump = curDoc; let willZoom = false; @@ -188,15 +207,17 @@ export class PresBox extends React.Component<FieldViewProps> { }); //docToJump stayed same meaning, it was not in the group or was the last element in the group + const aliasOf = await Cast(docToJump.aliasOf, Doc); + const srcContext = aliasOf && await Cast(aliasOf.sourceContext, Doc); if (docToJump === curDoc) { //checking if curDoc has navigation open - const target = await curDoc.presentationTargetDoc as Doc; - if (curDoc.navButton) { - DocumentManager.Instance.jumpToDocument(target, false); - } else if (curDoc.showButton) { + const target = await Cast(curDoc.presentationTargetDoc, Doc); + if (curDoc.navButton && target) { + DocumentManager.Instance.jumpToDocument(target, false, undefined, srcContext); + } else if (curDoc.showButton && target) { const curScale = DocumentManager.Instance.getScaleOfDocView(fromDoc); //awaiting jump so that new scale can be found, since jumping is async - await DocumentManager.Instance.jumpToDocument(target, true); + await DocumentManager.Instance.jumpToDocument(target, true, undefined, srcContext); curDoc.viewScale = DocumentManager.Instance.getScaleOfDocView(target); //saving the scale user was on before zooming in @@ -210,7 +231,8 @@ export class PresBox extends React.Component<FieldViewProps> { const curScale = DocumentManager.Instance.getScaleOfDocView(fromDoc); //awaiting jump so that new scale can be found, since jumping is async - await DocumentManager.Instance.jumpToDocument(await docToJump.presentationTargetDoc as Doc, willZoom); + const presTargetDoc = await docToJump.presentationTargetDoc as Doc; + await DocumentManager.Instance.jumpToDocument(presTargetDoc, willZoom, undefined, srcContext); const newScale = DocumentManager.Instance.getScaleOfDocView(await curDoc.presentationTargetDoc as Doc); curDoc.viewScale = newScale; //saving the scale that user was on @@ -226,7 +248,7 @@ export class PresBox extends React.Component<FieldViewProps> { getDocAtIndex = async (index: number) => { const list = FieldValue(Cast(this.props.Document[this.props.fieldKey], listSpec(Doc))); if (list && index >= 0 && index < list.length) { - this.props.Document.selectedDoc = index; + this.props.Document._itemIndex = index; //awaiting async call to finish to get Doc instance return list[index]; } @@ -249,12 +271,12 @@ export class PresBox extends React.Component<FieldViewProps> { //The function that is called when a document is clicked or reached through next or back. //it'll also execute the necessary actions if presentation is playing. - @action public gotoDocument = async (index: number, fromDoc: number) => { + action(() => Doc.UserDoc().curPresentation = this.props.Document); Doc.UnBrushAllDocs(); const list = FieldValue(Cast(this.props.Document[this.props.fieldKey], listSpec(Doc))); if (list && index >= 0 && index < list.length) { - this.props.Document.selectedDoc = index; + this.props.Document._itemIndex = index; if (!this.props.Document.presStatus) { this.props.Document.presStatus = true; @@ -271,26 +293,33 @@ export class PresBox extends React.Component<FieldViewProps> { } //The function that starts or resets presentaton functionally, depending on status flag. - @action startOrResetPres = () => { + action(() => Doc.UserDoc().curPresentation = this.props.Document); if (this.props.Document.presStatus) { this.resetPresentation(); } else { this.props.Document.presStatus = true; this.startPresentation(0); - this.gotoDocument(0, NumCast(this.props.Document.selectedDoc)); + this.gotoDocument(0, NumCast(this.props.Document._itemIndex)); } } + addDocument = (doc: Doc) => { + const newPinDoc = Doc.MakeAlias(doc); + newPinDoc.presentationTargetDoc = doc; + return Doc.AddDocToList(this.props.Document, this.props.fieldKey, newPinDoc); + } + + //The function that resets the presentation by removing every action done by it. It also //stops the presentaton. - @action resetPresentation = () => { + action(() => Doc.UserDoc().curPresentation = this.props.Document); this.childDocs.forEach((doc: Doc) => { doc.opacity = 1; doc.viewScale = 1; }); - this.props.Document.selectedDoc = 0; + this.props.Document._itemIndex = 0; this.props.Document.presStatus = false; if (this.childDocs.length !== 0) { DocumentManager.Instance.zoomIntoScale(this.childDocs[0], 1); @@ -300,6 +329,7 @@ export class PresBox extends React.Component<FieldViewProps> { //The function that starts the presentation, also checking if actions should be applied //directly at start. startPresentation = (startIndex: number) => { + action(() => Doc.UserDoc().curPresentation = this.props.Document); this.childDocs.map(doc => { if (doc.hideTillShownButton && this.childDocs.indexOf(doc) > startIndex) { doc.opacity = 0; @@ -327,22 +357,25 @@ export class PresBox extends React.Component<FieldViewProps> { })); specificContextMenu = (e: React.MouseEvent): void => { - ContextMenu.Instance.addItem({ description: "Make Current Presentation", event: action(() => Doc.UserDoc().curPresentation = this.props.Document), icon: "asterisk" }); + const funcs: ContextMenuProps[] = []; + funcs.push({ description: "Show as Slideshow", event: action(() => this.props.Document._slideshow = "slideshow"), icon: "asterisk" }); + funcs.push({ description: "Show as Timeline", event: action(() => this.props.Document._slideshow = "timeline"), icon: "asterisk" }); + funcs.push({ description: "Show as List", event: action(() => this.props.Document._slideshow = undefined), icon: "asterisk" }); + ContextMenu.Instance.addItem({ description: "Presentation Funcs...", subitems: funcs, icon: "asterisk" }); } /** * Initially every document starts with a viewScale 1, which means * that they will be displayed in a canvas with scale 1. */ - @action initializeScaleViews = (docList: Doc[], viewtype: number) => { - this.props.Document.chromeStatus = "disabled"; - const hgt = (viewtype === CollectionViewType.Tree) ? 50 : 72; + this.props.Document._chromeStatus = "disabled"; + const hgt = (viewtype === CollectionViewType.Tree) ? 50 : 46; docList.forEach((doc: Doc) => { doc.presBox = this.props.Document; doc.presBoxKey = this.props.fieldKey; doc.collapsedHeight = hgt; - doc.height = ComputedField.MakeFunction("this.collapsedHeight + Number(this.embedOpen ? 100:0)"); + doc._nativeWidth = doc._nativeHeight = undefined; const curScale = NumCast(doc.viewScale, null); if (curScale === undefined) { doc.viewScale = 1; @@ -350,19 +383,37 @@ export class PresBox extends React.Component<FieldViewProps> { }); } - selectElement = (doc: Doc) => { const index = DocListCast(this.props.Document[this.props.fieldKey]).indexOf(doc); - index !== -1 && this.gotoDocument(index, NumCast(this.props.Document.selectedDoc)); + index !== -1 && this.gotoDocument(index, NumCast(this.props.Document._itemIndex)); } getTransform = () => { - return this.props.ScreenToLocalTransform().translate(-10, -50);// listBox padding-left and pres-box-cont minHeight + return this.props.ScreenToLocalTransform().translate(0, -50);// listBox padding-left and pres-box-cont minHeight + } + panelHeight = () => { + return this.props.PanelHeight() - 20; } render() { - this.initializeScaleViews(this.childDocs, NumCast(this.props.Document.viewType)); - return ( - <div className="presBox-cont" onContextMenu={this.specificContextMenu}> + this.initializeScaleViews(this.childDocs, NumCast(this.props.Document._viewType)); + return (this.props.Document._slideshow ? + <div className="presBox-cont" onContextMenu={this.specificContextMenu} style={{ pointerEvents: this.active() ? "all" : "none" }} > + {this.props.Document.inOverlay ? (null) : + <div className="presBox-listCont" > + {this.props.Document._slideshow === "slideshow" ? + <CollectionCarouselView {...this.props} PanelHeight={this.panelHeight} chromeCollapsed={true} annotationsKey={""} CollectionView={undefined} + moveDocument={returnFalse} + addDocument={this.addDocument} removeDocument={returnFalse} focus={this.selectElement} ScreenToLocalTransform={this.getTransform} /> + : + <CollectionTimeView {...this.props} PanelHeight={this.panelHeight} chromeCollapsed={true} annotationsKey={""} CollectionView={undefined} + moveDocument={returnFalse} + addDocument={this.addDocument} removeDocument={returnFalse} focus={this.selectElement} ScreenToLocalTransform={this.getTransform} /> + } + </div>} + <button className="presBox-backward" title="Back" onClick={this.back}><FontAwesomeIcon icon={"arrow-left"} /></button> + <button className="presBox-forward" title="Next" onClick={this.next}><FontAwesomeIcon icon={"arrow-right"} /></button> + </div> + : <div className="presBox-cont" onContextMenu={this.specificContextMenu}> <div className="presBox-buttons"> <button className="presBox-button" title="Back" onClick={this.back}><FontAwesomeIcon icon={"arrow-left"} /></button> <button className="presBox-button" title={"Reset Presentation" + this.props.Document.presStatus ? "" : " From Start"} onClick={this.startOrResetPres}> @@ -370,13 +421,10 @@ export class PresBox extends React.Component<FieldViewProps> { </button> <button className="presBox-button" title="Next" onClick={this.next}><FontAwesomeIcon icon={"arrow-right"} /></button> <button className="presBox-button" title={this.props.Document.inOverlay ? "Expand" : "Minimize"} onClick={this.toggleMinimize}><FontAwesomeIcon icon={"eye"} /></button> - </div> - {this.props.Document.inOverlay ? (null) : - <div className="presBox-listCont" > - <CollectionView {...this.props} focus={this.selectElement} ScreenToLocalTransform={this.getTransform} /> - </div> - } - </div> - ); + {this.props.Document.inOverlay ? (null) : + <div className="presBox-listCont"> + <CollectionView {...this.props} whenActiveChanged={this.whenActiveChanged} PanelHeight={this.panelHeight} addDocument={this.addDocument} focus={this.selectElement} ScreenToLocalTransform={this.getTransform} /> + </div>} + </div></div>); } }
\ No newline at end of file diff --git a/src/client/views/nodes/RadialMenu.scss b/src/client/views/nodes/RadialMenu.scss new file mode 100644 index 000000000..ce0c263ef --- /dev/null +++ b/src/client/views/nodes/RadialMenu.scss @@ -0,0 +1,83 @@ +@import "../globalCssVariables"; + +.radialMenu-cont { + position: absolute; + z-index: $radialMenu-zindex; + flex-direction: column; +} + +.radialMenu-subMenu-cont { + position: absolute; + display: flex; + z-index: 1000; + flex-direction: column; + border-radius: 15px; + padding-top: 10px; + padding-bottom: 10px; +} + +.radialMenu-item { + // width: 11vw; //10vw + display: flex; //comment out to allow search icon to be inline with search text + align-items: center; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + transition: all .1s; + border-style: none; + white-space: nowrap; + font-size: 13px; + letter-spacing: 2px; + text-transform: uppercase; +} + +s +.radialMenu-itemSelected { + border-style: none; +} + +.radialMenu-group { + // width: 11vw; //10vw + display: flex; //comment out to allow search icon to be inline with search text + justify-content: left; + align-items: center; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + transition: all .1s; + border-width: .11px; + border-style: none; + border-color: $intermediate-color; // rgb(187, 186, 186); + // padding: 10px 0px 10px 0px; + white-space: nowrap; + font-size: 13px; + text-transform: uppercase; + letter-spacing: 2px; + padding-left: 5px; +} + + +.radialMenu-description { + margin-left: 5px; + text-align: left; + display: inline; //need this? +} + + + +.icon-background { + pointer-events: all; + height:100%; + margin-top: 15px; + background-color: transparent; + width: 35px; + text-align: center; + font-size: 20px; + margin-left: 5px; +}
\ No newline at end of file diff --git a/src/client/views/nodes/RadialMenu.tsx b/src/client/views/nodes/RadialMenu.tsx new file mode 100644 index 000000000..9314a3899 --- /dev/null +++ b/src/client/views/nodes/RadialMenu.tsx @@ -0,0 +1,224 @@ +import React = require("react"); +import { observer } from "mobx-react"; +import { action, observable, computed, IReactionDisposer, reaction, runInAction } from "mobx"; +import { RadialMenuItem, RadialMenuProps } from "./RadialMenuItem"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import Measure from "react-measure"; +import "./RadialMenu.scss"; + +@observer +export class RadialMenu extends React.Component { + static Instance: RadialMenu; + static readonly buffer = 20; + + constructor(props: Readonly<{}>) { + super(props); + + RadialMenu.Instance = this; + } + + @observable private _mouseX: number = -1; + @observable private _mouseY: number = -1; + @observable private _shouldDisplay: boolean = false; + @observable private _mouseDown: boolean = false; + private _reactionDisposer?: IReactionDisposer; + + + @action + onPointerDown = (e: PointerEvent) => { + this._mouseDown = true; + this._mouseX = e.clientX; + this._mouseY = e.clientY; + document.addEventListener("pointermove", this.onPointerMove); + } + + @observable + private _closest: number = -1; + + @action + onPointerMove = (e: PointerEvent) => { + const curX = e.clientX; + const curY = e.clientY; + const deltX = this._mouseX - curX; + const deltY = this._mouseY - curY; + const scale = Math.hypot(deltY, deltX); + + if (scale < 150 && scale > 50) { + const rad = Math.atan2(deltY, deltX) + Math.PI; + let closest = 0; + let closestval = 999999999; + for (let x = 0; x < this._items.length; x++) { + const curmin = (x / this._items.length) * 2 * Math.PI; + if (rad - curmin < closestval && rad - curmin > 0) { + closestval = rad - curmin; + closest = x; + } + } + this._closest = closest; + } + else { + this._closest = -1; + } + } + @action + onPointerUp = (e: PointerEvent) => { + this._mouseDown = false; + const curX = e.clientX; + const curY = e.clientY; + if (this._mouseX !== curX || this._mouseY !== curY) { + this._shouldDisplay = false; + } + this._shouldDisplay && (this._display = true); + document.removeEventListener("pointermove", this.onPointerMove); + if (this._closest !== -1 && this._items?.length > this._closest) { + this._items[this._closest].event(); + } + } + componentWillUnmount() { + document.removeEventListener("pointerdown", this.onPointerDown); + + document.removeEventListener("pointerup", this.onPointerUp); + this._reactionDisposer && this._reactionDisposer(); + } + + @action + componentDidMount = () => { + document.addEventListener("pointerdown", this.onPointerDown); + document.addEventListener("pointerup", this.onPointerUp); + this.previewcircle(); + this._reactionDisposer = reaction( + () => this._shouldDisplay, + () => this._shouldDisplay && !this._mouseDown && runInAction(() => this._display = true) + ); + } + + componentDidUpdate = () => { + this.previewcircle(); + } + + @observable private _pageX: number = 0; + @observable private _pageY: number = 0; + @observable private _display: boolean = false; + @observable private _yRelativeToTop: boolean = true; + + + @observable private _width: number = 0; + @observable private _height: number = 0; + + + getItems() { + return this._items; + } + + @action + addItem(item: RadialMenuProps) { + if (this._items.indexOf(item) === -1) { + this._items.push(item); + } + } + + @observable + private _items: Array<RadialMenuProps> = []; + + @action + displayMenu = (x: number, y: number) => { + //maxX and maxY will change if the UI/font size changes, but will work for any amount + //of items added to the menu + + this._pageX = x; + this._pageY = y; + this._shouldDisplay = true; + } + + get pageX() { + const x = this._pageX; + if (x < 0) { + return 0; + } + const width = this._width; + if (x + width > window.innerWidth - RadialMenu.buffer) { + return window.innerWidth - RadialMenu.buffer - width; + } + return x; + } + + get pageY() { + const y = this._pageY; + if (y < 0) { + return 0; + } + const height = this._height; + if (y + height > window.innerHeight - RadialMenu.buffer) { + return window.innerHeight - RadialMenu.buffer - height; + } + return y; + } + + @computed get menuItems() { + return this._items.map((item, index) => <RadialMenuItem {...item} key={item.description} closeMenu={this.closeMenu} max={this._items.length} min={index} selected={this._closest} />); + } + + @action + closeMenu = () => { + this.clearItems(); + this._display = false; + this._shouldDisplay = false; + } + + @action + openMenu = () => { + this._shouldDisplay; + this._display = true; + } + + @action + clearItems() { + this._items = []; + } + + + previewcircle() { + if (document.getElementById("newCanvas") !== null) { + const c: any = document.getElementById("newCanvas"); + if (c.getContext) { + const ctx = c.getContext("2d"); + ctx.beginPath(); + ctx.arc(150, 150, 50, 0, 2 * Math.PI); + ctx.fillStyle = "white"; + ctx.fill(); + ctx.font = "12px Arial"; + ctx.fillStyle = "black"; + ctx.textAlign = "center"; + let description = ""; + if (this._closest !== -1) { + description = this._items[this._closest].description; + } + if (description.length > 15) { + description = description.slice(0, 12); + description += "..."; + } + ctx.fillText(description, 150, 150, 90); + } + } + } + + + render() { + if (!this._display) { + return null; + } + const style = this._yRelativeToTop ? { left: this._mouseX - 150, top: this._mouseY - 150 } : + { left: this._mouseX - 150, top: this._mouseY - 150 }; + + return ( + + <div className="radialMenu-cont" style={style}> + <canvas id="newCanvas" style={{ position: "absolute" }} height="300" width="300"> Your browser does not support the HTML5 canvas tag.</canvas> + {this.menuItems} + </div> + + ); + } + + +}
\ No newline at end of file diff --git a/src/client/views/nodes/RadialMenuItem.tsx b/src/client/views/nodes/RadialMenuItem.tsx new file mode 100644 index 000000000..fdc732d3f --- /dev/null +++ b/src/client/views/nodes/RadialMenuItem.tsx @@ -0,0 +1,117 @@ +import React = require("react"); +import { observable, action } from "mobx"; +import { observer } from "mobx-react"; +import { IconProp, library } from '@fortawesome/fontawesome-svg-core'; +import { faAngleRight } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { UndoManager } from "../../util/UndoManager"; + +library.add(faAngleRight); + +export interface RadialMenuProps { + description: string; + event: (stuff?: any) => void; + undoable?: boolean; + icon: IconProp; + closeMenu?: () => void; + min?: number; + max?: number; + selected: number; +} + + +@observer +export class RadialMenuItem extends React.Component<RadialMenuProps> { + + componentDidMount = () => { + this.setcircle(); + } + + componentDidUpdate = () => { + this.setcircle(); + } + + handleEvent = async (e: React.PointerEvent) => { + this.props.closeMenu && this.props.closeMenu(); + let batch: UndoManager.Batch | undefined; + if (this.props.undoable !== false) { + batch = UndoManager.StartBatch(`Context menu event: ${this.props.description}`); + } + await this.props.event({ x: e.clientX, y: e.clientY }); + batch && batch.end(); + } + + + setcircle() { + let circlemin = 0; + let circlemax = 1 + this.props.min ? circlemin = this.props.min : null; + this.props.max ? circlemax = this.props.max : null; + if (document.getElementById("myCanvas") !== null) { + var c: any = document.getElementById("myCanvas"); + let color = "white" + switch (circlemin % 3) { + case 1: + color = "#c2c2c5"; + break; + case 0: + color = "#f1efeb"; + break; + case 2: + color = "lightgray"; + break; + } + if (circlemax % 3 === 1 && circlemin === circlemax - 1) { + color = "#c2c2c5"; + } + + if (this.props.selected === this.props.min) { + color = "#808080"; + + } + if (c.getContext) { + var ctx = c.getContext("2d"); + ctx.beginPath(); + ctx.arc(150, 150, 150, (circlemin / circlemax) * 2 * Math.PI, ((circlemin + 1) / circlemax) * 2 * Math.PI); + ctx.arc(150, 150, 50, ((circlemin + 1) / circlemax) * 2 * Math.PI, (circlemin / circlemax) * 2 * Math.PI, true); + ctx.fillStyle = color; + ctx.fill() + } + } + } + + calculatorx() { + let circlemin = 0; + let circlemax = 1 + this.props.min ? circlemin = this.props.min : null; + this.props.max ? circlemax = this.props.max : null; + let avg = ((circlemin / circlemax) + ((circlemin + 1) / circlemax)) / 2; + let degrees = 360 * avg; + let x = 100 * Math.cos(degrees * Math.PI / 180); + let y = -125 * Math.sin(degrees * Math.PI / 180); + return x; + } + + calculatory() { + + let circlemin = 0; + let circlemax = 1 + this.props.min ? circlemin = this.props.min : null; + this.props.max ? circlemax = this.props.max : null; + let avg = ((circlemin / circlemax) + ((circlemin + 1) / circlemax)) / 2; + let degrees = 360 * avg; + let x = 125 * Math.cos(degrees * Math.PI / 180); + let y = -100 * Math.sin(degrees * Math.PI / 180); + return y; + } + + + render() { + return ( + <div className={"radialMenu-item" + (this.props.selected ? " radialMenu-itemSelected" : "")} onPointerUp={this.handleEvent}> + <canvas id="myCanvas" height="300" width="300"> Your browser does not support the HTML5 canvas tag.</canvas> + <FontAwesomeIcon icon={this.props.icon} size="3x" style={{ position: "absolute", left: this.calculatorx() + 150 - 19, top: this.calculatory() + 150 - 19 }} /> + </div> + ); + } +}
\ No newline at end of file diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index 376d27380..d12a8d151 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -55,12 +55,12 @@ export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocum videoLoad = () => { const aspect = this.player!.videoWidth / this.player!.videoHeight; - const nativeWidth = (this.Document.nativeWidth || 0); - const nativeHeight = (this.Document.nativeHeight || 0); + const nativeWidth = (this.Document._nativeWidth || 0); + const nativeHeight = (this.Document._nativeHeight || 0); if (!nativeWidth || !nativeHeight) { - if (!this.Document.nativeWidth) this.Document.nativeWidth = this.player!.videoWidth; - this.Document.nativeHeight = (this.Document.nativeWidth || 0) / aspect; - this.Document.height = (this.Document.width || 0) / aspect; + if (!this.Document._nativeWidth) this.Document._nativeWidth = this.player!.videoWidth; + this.Document._nativeHeight = (this.Document._nativeWidth || 0) / aspect; + this.Document._height = (this.Document._width || 0) / aspect; } if (!this.Document.duration) this.Document.duration = this.player!.duration; } @@ -101,11 +101,11 @@ export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocum } @action public Snapshot() { - const width = this.Document.width || 0; - const height = this.Document.height || 0; + const width = this.Document._width || 0; + const height = this.Document._height || 0; const canvas = document.createElement('canvas'); canvas.width = 640; - canvas.height = 640 * (this.Document.nativeHeight || 0) / (this.Document.nativeWidth || 1); + canvas.height = 640 * (this.Document._nativeHeight || 0) / (this.Document._nativeWidth || 1); const ctx = canvas.getContext('2d');//draw image to canvas. scale to target dimensions if (ctx) { ctx.rect(0, 0, canvas.width, canvas.height); @@ -117,7 +117,7 @@ export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocum if (!this._videoRef) { // can't find a way to take snapshots of videos const b = Docs.Create.ButtonDocument({ x: (this.Document.x || 0) + width, y: (this.Document.y || 0), - width: 150, height: 50, title: (this.Document.currentTimecode || 0).toString() + _width: 150, _height: 50, title: (this.Document.currentTimecode || 0).toString() }); b.onClick = ScriptField.MakeScript(`this.currentTimecode = ${(this.Document.currentTimecode || 0)}`); } else { @@ -130,7 +130,7 @@ export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocum const url = this.choosePath(Utils.prepend(returnedFilename)); const imageSummary = Docs.Create.ImageDocument(url, { x: (this.Document.x || 0) + width, y: (this.Document.y || 0), - width: 150, height: height / width * 150, title: "--snapshot" + (this.Document.currentTimecode || 0) + " image-" + _width: 150, _height: height / width * 150, title: "--snapshot" + (this.Document.currentTimecode || 0) + " image-" }); imageSummary.isButton = true; this.props.addDocument && this.props.addDocument(imageSummary); @@ -151,12 +151,12 @@ export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocum if (this.youtubeVideoId) { const youtubeaspect = 400 / 315; - const nativeWidth = (this.Document.nativeWidth || 0); - const nativeHeight = (this.Document.nativeHeight || 0); + const nativeWidth = (this.Document._nativeWidth || 0); + const nativeHeight = (this.Document._nativeHeight || 0); if (!nativeWidth || !nativeHeight) { - if (!this.Document.nativeWidth) this.Document.nativeWidth = 600; - this.Document.nativeHeight = (this.Document.nativeWidth || 0) / youtubeaspect; - this.Document.height = (this.Document.width || 0) / youtubeaspect; + if (!this.Document._nativeWidth) this.Document._nativeWidth = 600; + this.Document._nativeHeight = (this.Document._nativeWidth || 0) / youtubeaspect; + this.Document._height = (this.Document._width || 0) / youtubeaspect; } } } @@ -321,7 +321,7 @@ export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocum const style = "videoBox-content-YouTube" + (this._fullScreen ? "-fullScreen" : ""); const start = untracked(() => Math.round(this.Document.currentTimecode || 0)); return <iframe key={this._youtubeIframeId} id={`${this.youtubeVideoId + this._youtubeIframeId}-player`} - onLoad={this.youtubeIframeLoaded} className={`${style}`} width={(this.Document.nativeWidth || 640)} height={(this.Document.nativeHeight || 390)} + onLoad={this.youtubeIframeLoaded} className={`${style}`} width={(this.Document._nativeWidth || 640)} height={(this.Document._nativeHeight || 390)} src={`https://www.youtube.com/embed/${this.youtubeVideoId}?enablejsapi=1&rel=0&showinfo=1&autoplay=1&mute=1&start=${start}&modestbranding=1&controls=${VideoBox._showControls ? 1 : 0}`} />; } @@ -340,7 +340,7 @@ export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocum <CollectionFreeFormView {...this.props} PanelHeight={this.props.PanelHeight} PanelWidth={this.props.PanelWidth} - annotationsKey={this.annotationsKey} + annotationsKey={this.annotationKey} focus={this.props.focus} isSelected={this.props.isSelected} isAnnotationOverlay={true} @@ -353,7 +353,6 @@ export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocum addDocument={this.addDocumentWithTimestamp} CollectionView={undefined} ScreenToLocalTransform={this.props.ScreenToLocalTransform} - ruleProvider={undefined} renderDepth={this.props.renderDepth + 1} ContainingCollectionDoc={this.props.ContainingCollectionDoc} chromeCollapsed={true}> diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index b35ea0bb0..a48dc286e 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -34,17 +34,17 @@ export class WebBox extends DocAnnotatableComponent<FieldViewProps, WebDocument> @observable private collapsed: boolean = true; @observable private url: string = ""; - componentWillMount() { + componentDidMount() { const field = Cast(this.props.Document[this.props.fieldKey], WebField); if (field && field.url.href.indexOf("youtube") !== -1) { const youtubeaspect = 400 / 315; - const nativeWidth = NumCast(this.layoutDoc.nativeWidth); - const nativeHeight = NumCast(this.layoutDoc.nativeHeight); + 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; + if (!nativeWidth) this.layoutDoc._nativeWidth = 600; + this.layoutDoc._nativeHeight = NumCast(this.layoutDoc._nativeWidth) / youtubeaspect; + this.layoutDoc._height = NumCast(this.layoutDoc._width) / youtubeaspect; } } @@ -83,13 +83,12 @@ export class WebBox extends DocAnnotatableComponent<FieldViewProps, WebDocument> const field = Cast(this.props.Document[this.props.fieldKey], WebField); if (field) url = field.url.href; - const newBox = Docs.Create.TextDocument({ + const newBox = Docs.Create.TextDocument(url, { x: NumCast(this.props.Document.x), y: NumCast(this.props.Document.y), title: url, - width: 200, - height: 70, - documentText: "@@@" + url + _width: 200, + _height: 70, }); SelectionManager.SelectedDocuments().map(dv => { @@ -198,7 +197,7 @@ export class WebBox extends DocAnnotatableComponent<FieldViewProps, WebDocument> <CollectionFreeFormView {...this.props} PanelHeight={this.props.PanelHeight} PanelWidth={this.props.PanelWidth} - annotationsKey={this.annotationsKey} + annotationsKey={this.annotationKey} focus={this.props.focus} isSelected={this.props.isSelected} isAnnotationOverlay={true} @@ -211,7 +210,6 @@ export class WebBox extends DocAnnotatableComponent<FieldViewProps, WebDocument> addDocument={this.addDocument} CollectionView={undefined} ScreenToLocalTransform={this.props.ScreenToLocalTransform} - ruleProvider={undefined} renderDepth={this.props.renderDepth + 1} ContainingCollectionDoc={this.props.ContainingCollectionDoc} chromeCollapsed={true}> |
