diff options
Diffstat (limited to 'src/client/views/nodes')
25 files changed, 766 insertions, 856 deletions
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx index 86bd23b67..95c765e8a 100644 --- a/src/client/views/nodes/AudioBox.tsx +++ b/src/client/views/nodes/AudioBox.tsx @@ -8,7 +8,6 @@ import { DocExtendableComponent } from "../DocComponent"; import { makeInterface, createSchema } from "../../../new_fields/Schema"; import { documentSchema } from "../../../new_fields/documentSchemas"; import { Utils, returnTrue, emptyFunction, returnOne, returnTransparent } from "../../../Utils"; -import { RouteStore } from "../../../server/RouteStore"; import { runInAction, observable, reaction, IReactionDisposer, computed, action } from "mobx"; import { DateField } from "../../../new_fields/DateField"; import { SelectionManager } from "../../util/SelectionManager"; @@ -57,19 +56,19 @@ export class AudioBox extends DocExtendableComponent<FieldViewProps, AudioDocume this._linkPlayDisposer = reaction(() => this.layoutDoc.scrollToLinkID, scrollLinkId => { scrollLinkId && DocListCast(this.dataDoc.links).filter(l => l[Id] === scrollLinkId).map(l => { - let la1 = l.anchor1 as Doc; - let linkTime = Doc.AreProtosEqual(la1, this.dataDoc) ? NumCast(l.anchor1Timecode) : NumCast(l.anchor2Timecode); + const la1 = l.anchor1 as Doc; + const linkTime = Doc.AreProtosEqual(la1, this.dataDoc) ? NumCast(l.anchor1Timecode) : NumCast(l.anchor2Timecode); setTimeout(() => { this.playFrom(linkTime); Doc.linkFollowHighlight(l); }, 250); }); scrollLinkId && Doc.SetInPlace(this.layoutDoc, "scrollToLinkID", undefined, false); }, { fireImmediately: true }); this._reactionDisposer = reaction(() => SelectionManager.SelectedDocuments(), selected => { - let sel = selected.length ? selected[0].props.Document : undefined; + const sel = selected.length ? selected[0].props.Document : undefined; this.Document.playOnSelect && sel && !Doc.AreProtosEqual(sel, this.props.Document) && this.playFrom(DateCast(sel.creationTime).date.getTime()); }); this._scrubbingDisposer = reaction(() => AudioBox._scrubTime, timeInMillisecondsFrom1970 => { - let start = this.extensionDoc && DateCast(this.extensionDoc.recordingStart); + const start = this.extensionDoc && DateCast(this.extensionDoc.recordingStart); start && this.playFrom((timeInMillisecondsFrom1970 - start.date.getTime()) / 1000); }); } @@ -128,7 +127,7 @@ export class AudioBox extends DocExtendableComponent<FieldViewProps, AudioDocume recordAudioAnnotation = () => { let gumStream: any; - let self = this; + const self = this; const extensionDoc = this.extensionDoc; extensionDoc && navigator.mediaDevices.getUserMedia({ audio: true @@ -140,7 +139,7 @@ export class AudioBox extends DocExtendableComponent<FieldViewProps, AudioDocume self._recorder.ondataavailable = async function (e: any) { const formData = new FormData(); formData.append("file", e.data); - const res = await fetch(Utils.prepend(RouteStore.upload), { + const res = await fetch(Utils.prepend("/upload"), { method: 'POST', body: formData }); @@ -161,7 +160,7 @@ export class AudioBox extends DocExtendableComponent<FieldViewProps, AudioDocume } specificContextMenu = (e: React.MouseEvent): void => { - let funcs: ContextMenuProps[] = []; + const funcs: ContextMenuProps[] = []; funcs.push({ description: (this.Document.playOnSelect ? "Don't play" : "Play") + " when document selected", event: () => this.Document.playOnSelect = !this.Document.playOnSelect, icon: "expand-arrows-alt" }); ContextMenu.Instance.addItem({ description: "Audio Funcs...", subitems: funcs, icon: "asterisk" }); @@ -171,7 +170,7 @@ export class AudioBox extends DocExtendableComponent<FieldViewProps, AudioDocume this._recorder.stop(); this.dataDoc.duration = (new Date().getTime() - this._recordStart) / 1000; this._audioState = "recorded"; - let ind = AudioBox.ActiveRecordings.indexOf(this.props.Document); + const ind = AudioBox.ActiveRecordings.indexOf(this.props.Document); ind !== -1 && (AudioBox.ActiveRecordings.splice(ind, 1)); }); @@ -199,13 +198,13 @@ export class AudioBox extends DocExtendableComponent<FieldViewProps, AudioDocume } @computed get path() { - let field = Cast(this.props.Document[this.props.fieldKey], AudioField); - let path = (field instanceof AudioField) ? field.url.href : ""; + const field = Cast(this.props.Document[this.props.fieldKey], AudioField); + const path = (field instanceof AudioField) ? field.url.href : ""; return path === nullAudio ? "" : path; } @computed get audio() { - let interactive = this.active() ? "-interactive" : ""; + const interactive = this.active() ? "-interactive" : ""; return <audio ref={this.setRef} className={`audiobox-control${interactive}`}> <source src={this.path} type="audio/mpeg" /> Not supported. @@ -213,7 +212,7 @@ export class AudioBox extends DocExtendableComponent<FieldViewProps, AudioDocume } render() { - let interactive = this.active() ? "-interactive" : ""; + const interactive = this.active() ? "-interactive" : ""; return (!this.extensionDoc ? (null) : <div className={`audiobox-container`} onContextMenu={this.specificContextMenu} onClick={!this.path ? this.recordClick : undefined}> @@ -229,7 +228,7 @@ export class AudioBox extends DocExtendableComponent<FieldViewProps, AudioDocume <div className="audiobox-timeline" onClick={e => e.stopPropagation()} onPointerDown={e => { if (e.button === 0 && !e.ctrlKey) { - let rect = (e.target as any).getBoundingClientRect(); + 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(); diff --git a/src/client/views/nodes/ButtonBox.tsx b/src/client/views/nodes/ButtonBox.tsx index 659ba154a..34151a311 100644 --- a/src/client/views/nodes/ButtonBox.tsx +++ b/src/client/views/nodes/ButtonBox.tsx @@ -51,10 +51,10 @@ export class ButtonBox extends DocComponent<FieldViewProps, ButtonDocument>(Butt } specificContextMenu = (e: React.MouseEvent): void => { - let funcs: ContextMenuProps[] = []; + const funcs: ContextMenuProps[] = []; funcs.push({ description: "Clear Script Params", event: () => { - let params = FieldValue(this.Document.buttonParams); + const params = FieldValue(this.Document.buttonParams); params && params.map(p => this.props.Document[p] = undefined); }, icon: "trash" }); @@ -73,8 +73,8 @@ export class ButtonBox extends DocComponent<FieldViewProps, ButtonDocument>(Butt } // (!missingParams || !missingParams.length ? "" : "(" + missingParams.map(m => m + ":").join(" ") + ")") render() { - let params = this.Document.buttonParams; - let missingParams = params && params.filter(p => this.props.Document[p] === undefined); + const params = this.Document.buttonParams; + const missingParams = params && params.filter(p => this.props.Document[p] === undefined); params && params.map(p => DocListCast(this.props.Document[p])); // bcz: really hacky form of prefetching ... return ( <div className="buttonBox-outerDiv" ref={this.createDropTarget} onContextMenu={this.specificContextMenu} diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index c85b59488..d599de56e 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -31,7 +31,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF 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 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() { - let hgt = this.renderScriptDim ? this.renderScriptDim.height : this.props.height !== undefined ? this.props.height : this.props.dataProvider && this.dataProvider ? this.dataProvider.height : this.layoutDoc[HeightSym](); + 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; } @@ -40,13 +40,13 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF @computed get renderScriptDim() { if (this.Document.renderScript) { - let someView = Cast(this.props.Document.someView, Doc); - let minimap = Cast(this.props.Document.minimap, Doc); + const someView = Cast(this.props.Document.someView, Doc); + const minimap = Cast(this.props.Document.minimap, Doc); if (someView instanceof Doc && minimap instanceof Doc) { - let 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; - let 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; - let w = NumCast(someView.width) / NumCast(someView.scale) / NumCast(minimap.fitW) * NumCast(minimap.width); - let 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 }; } } @@ -70,9 +70,9 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF .scale(1 / this.contentScaling()) borderRounding = () => { - let ruleRounding = this.props.ruleProvider ? StrCast(this.props.ruleProvider["ruleRounding_" + this.Document.heading]) : undefined; - let ld = this.layoutDoc[StrCast(this.layoutDoc.layoutKey, "layout")] instanceof Doc ? this.layoutDoc[StrCast(this.layoutDoc.layoutKey, "layout")] as Doc : undefined; - let br = StrCast((ld || this.props.Document).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; } @@ -94,7 +94,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF this.layoutDoc.opacity === 0 ? undefined : // if it's not visible, then no shadow this.layoutDoc.z ? `#9c9396 ${StrCast(this.layoutDoc.boxShadow, "10px 10px 0.9vw")}` : // if it's a floating doc, give it a big shadow 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 ? `1px 1px 1px ${this.clusterColor}` : // if it's a background & has a cluster color, make the shadow spread really big + 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(), transform: this.transform, diff --git a/src/client/views/nodes/ContentFittingDocumentView.tsx b/src/client/views/nodes/ContentFittingDocumentView.tsx index 573a55710..efc907f9b 100644 --- a/src/client/views/nodes/ContentFittingDocumentView.tsx +++ b/src/client/views/nodes/ContentFittingDocumentView.tsx @@ -47,7 +47,7 @@ export class ContentFittingDocumentView extends React.Component<ContentFittingDo private get nativeWidth() { return NumCast(this.layoutDoc!.nativeWidth, this.props.PanelWidth()); } private get nativeHeight() { return NumCast(this.layoutDoc!.nativeHeight, this.props.PanelHeight()); } private contentScaling = () => { - let wscale = this.props.PanelWidth() / (this.nativeWidth ? this.nativeWidth : this.props.PanelWidth()); + const wscale = this.props.PanelWidth() / (this.nativeWidth ? this.nativeWidth : this.props.PanelWidth()); if (wscale * this.nativeHeight > this.props.PanelHeight()) { return this.props.PanelHeight() / (this.nativeHeight ? this.nativeHeight : this.props.PanelHeight()); } @@ -59,7 +59,7 @@ export class ContentFittingDocumentView extends React.Component<ContentFittingDo drop = (e: Event, de: DragManager.DropEvent) => { if (de.data instanceof DragManager.DocumentDragData) { this.props.childDocs && this.props.childDocs.map(otherdoc => { - let target = Doc.GetProto(otherdoc); + const target = Doc.GetProto(otherdoc); target.layout = ComputedField.MakeFunction("this.image_data[0]"); target.layoutCustom = Doc.MakeDelegate(de.data.draggedDocuments[0]); }); @@ -82,7 +82,7 @@ export class ContentFittingDocumentView extends React.Component<ContentFittingDo transform: `translate(${this.centeringOffset}px, 0px)`, borderRadius: this.borderRounding, height: this.props.PanelHeight(), - width: this.props.PanelWidth() + width: `${100 * (this.props.PanelWidth() - this.centeringOffset * 2) / this.props.PanelWidth()}%` }}> <DocumentView {...this.props} DataDoc={this.props.DataDocument} diff --git a/src/client/views/nodes/DocuLinkBox.tsx b/src/client/views/nodes/DocuLinkBox.tsx index d73407903..a22472e9e 100644 --- a/src/client/views/nodes/DocuLinkBox.tsx +++ b/src/client/views/nodes/DocuLinkBox.tsx @@ -36,12 +36,12 @@ export class DocuLinkBox extends DocComponent<FieldViewProps, DocLinkSchema>(Doc (e.button === 0 && !e.ctrlKey) && e.stopPropagation(); } onPointerMove = action((e: PointerEvent) => { - let cdiv = this._ref && this._ref.current && this._ref.current.parentElement; + const cdiv = this._ref && this._ref.current && this._ref.current.parentElement; if (cdiv && (Math.abs(e.clientX - this._downx) > 5 || Math.abs(e.clientY - this._downy) > 5)) { - let bounds = cdiv.getBoundingClientRect(); - let pt = Utils.getNearestPointInPerimeter(bounds.left, bounds.top, bounds.width, bounds.height, e.clientX, e.clientY); - let separation = Math.sqrt((pt[0] - e.clientX) * (pt[0] - e.clientX) + (pt[1] - e.clientY) * (pt[1] - e.clientY)); - let dragdist = Math.sqrt((pt[0] - this._downx) * (pt[0] - this._downx) + (pt[1] - this._downy) * (pt[1] - this._downy)); + const bounds = cdiv.getBoundingClientRect(); + const pt = Utils.getNearestPointInPerimeter(bounds.left, bounds.top, bounds.width, bounds.height, e.clientX, e.clientY); + const separation = Math.sqrt((pt[0] - e.clientX) * (pt[0] - e.clientX) + (pt[1] - e.clientY) * (pt[1] - e.clientY)); + const dragdist = Math.sqrt((pt[0] - this._downx) * (pt[0] - this._downx) + (pt[1] - this._downy) * (pt[1] - this._downy)); if (separation > 100) { DragLinksAsDocuments(this._ref.current!, pt[0], pt[1], Cast(this.props.Document[this.props.fieldKey], Doc) as Doc, this.props.Document); // Containging collection is the document, not a collection... hack. document.removeEventListener("pointermove", this.onPointerMove); @@ -67,14 +67,14 @@ export class DocuLinkBox extends DocComponent<FieldViewProps, DocLinkSchema>(Doc } render() { - let anchorDoc = Cast(this.props.Document[this.props.fieldKey], Doc); - let hasAnchor = anchorDoc instanceof Doc && anchorDoc.type === DocumentType.PDFANNO; - let y = NumCast(this.props.Document[this.props.fieldKey + "_y"], 100); - let x = NumCast(this.props.Document[this.props.fieldKey + "_x"], 100); - let c = StrCast(this.props.Document.backgroundColor, "lightblue"); - let anchor = this.props.fieldKey === "anchor1" ? "anchor2" : "anchor1"; - let timecode = this.props.Document[anchor + "Timecode"]; - let targetTitle = StrCast((this.props.Document[anchor]! as Doc).title) + (timecode !== undefined ? ":" + timecode : ""); + const anchorDoc = Cast(this.props.Document[this.props.fieldKey], Doc); + const hasAnchor = anchorDoc instanceof Doc && anchorDoc.type === DocumentType.PDFANNO; + const y = NumCast(this.props.Document[this.props.fieldKey + "_y"], 100); + const x = NumCast(this.props.Document[this.props.fieldKey + "_x"], 100); + const c = StrCast(this.props.Document.backgroundColor, "lightblue"); + const anchor = this.props.fieldKey === "anchor1" ? "anchor2" : "anchor1"; + const timecode = this.props.Document[anchor + "Timecode"]; + const targetTitle = StrCast((this.props.Document[anchor]! as Doc).title) + (timecode !== undefined ? ":" + timecode : ""); return <div className="docuLinkBox-cont" onPointerDown={this.onPointerDown} onClick={this.onClick} title={targetTitle} ref={this._ref} style={{ background: c, left: `calc(${x}% - 12.5px)`, top: `calc(${y}% - 12.5px)`, diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index b9b84d5ce..13fd3cde1 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -83,7 +83,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & { } CreateBindings(): JsxBindings { - let list = { + const list = { ...OmitKeys(this.props, ['parentActive'], (obj: any) => obj.active = this.props.parentActive).omit, Document: this.layoutDoc, DataDoc: this.dataDoc, diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 467fd4b32..63ce6233c 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -122,7 +122,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu startDragging(x: number, y: number, dropAction: dropActionType, applyAsTemplate?: boolean) { if (this._mainCont.current) { - let dragData = new DragManager.DocumentDragData([this.props.Document]); + const dragData = new DragManager.DocumentDragData([this.props.Document]); const [left, top] = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).inverse().transformPoint(0, 0); dragData.offset = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).transformDirection(x - left, y - top); dragData.dropAction = dropAction; @@ -143,7 +143,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu 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 - let fullScreenAlias = Doc.MakeAlias(this.props.Document); + const fullScreenAlias = Doc.MakeAlias(this.props.Document); if (StrCast(fullScreenAlias.layoutKey) !== "layoutCustom" && fullScreenAlias.layoutCustom !== undefined) { fullScreenAlias.layoutKey = "layoutCustom"; } @@ -166,9 +166,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu } buttonClick = async (altKey: boolean, ctrlKey: boolean) => { - let maximizedDocs = await DocListCastAsync(this.Document.maximizedDocs); - let summarizedDocs = await DocListCastAsync(this.Document.summarizedDocs); - let linkDocs = LinkManager.Instance.getAllRelatedLinks(this.props.Document); + const maximizedDocs = await DocListCastAsync(this.Document.maximizedDocs); + const summarizedDocs = await DocListCastAsync(this.Document.summarizedDocs); + const linkDocs = LinkManager.Instance.getAllRelatedLinks(this.props.Document); let expandedDocs: Doc[] = []; expandedDocs = maximizedDocs ? [...maximizedDocs, ...expandedDocs] : expandedDocs; expandedDocs = summarizedDocs ? [...summarizedDocs, ...expandedDocs] : expandedDocs; @@ -179,7 +179,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu maxLocation = this.Document.maximizeLocation = (!ctrlKey ? !altKey ? maxLocation : (maxLocation !== "inPlace" ? "inPlace" : "onRight") : (maxLocation !== "inPlace" ? "inPlace" : "inTab")); if (maxLocation === "inPlace") { expandedDocs.forEach(maxDoc => this.props.addDocument && this.props.addDocument(maxDoc)); - let scrpt = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).inverse().transformPoint(NumCast(this.layoutDoc.width) / 2, NumCast(this.layoutDoc.height) / 2); + const scrpt = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).inverse().transformPoint(NumCast(this.layoutDoc.width) / 2, NumCast(this.layoutDoc.height) / 2); DocumentManager.Instance.animateBetweenPoint(scrpt, expandedDocs); } else { expandedDocs.forEach(maxDoc => (!this.props.addDocTab(maxDoc, undefined, "close") && this.props.addDocTab(maxDoc, undefined, maxLocation))); @@ -194,7 +194,11 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu } onPointerDown = (e: React.PointerEvent): void => { - if (e.nativeEvent.cancelBubble && e.button === 0) return; + 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; this._downX = e.clientX; this._downY = e.clientY; this._hitTemplateDrag = false; @@ -205,7 +209,7 @@ 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 && !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.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); document.removeEventListener("pointermove", this.onPointerMove); document.removeEventListener("pointerup", this.onPointerUp); document.addEventListener("pointermove", this.onPointerMove); @@ -220,7 +224,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu } else if (!e.cancelBubble && (SelectionManager.IsSelected(this, true) || this.props.parentActive(true) || this.Document.onDragStart || this.Document.onClick) && !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.TOUCH))) { + if (!e.altKey && (!this.topMost || this.Document.onDragStart || this.Document.onClick) && (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); @@ -274,7 +278,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu fieldTemplate.heading = 1; fieldTemplate.autoHeight = true; - let docTemplate = Docs.Create.FreeformDocument([fieldTemplate], { title: doc.title + "_layout", width: width + 20, height: Math.max(100, height + 45) }); + const docTemplate = Docs.Create.FreeformDocument([fieldTemplate], { title: doc.title + "_layout", width: width + 20, height: Math.max(100, height + 45) }); Doc.MakeMetadataFieldTemplate(fieldTemplate, Doc.GetProto(docTemplate), true); Doc.ApplyTemplateTo(docTemplate, dataDoc || doc, "layoutCustom", undefined); @@ -320,10 +324,10 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu @action onDrop = (e: React.DragEvent) => { - let text = e.dataTransfer.getData("text/plain"); + const text = e.dataTransfer.getData("text/plain"); if (!e.isDefaultPrevented() && text && text.startsWith("<div")) { - let oldLayout = this.Document.layout || ""; - let layout = text.replace("{layout}", oldLayout); + const oldLayout = this.Document.layout || ""; + const layout = text.replace("{layout}", oldLayout); this.Document.layout = layout; e.stopPropagation(); e.preventDefault(); @@ -344,11 +348,11 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu @undoBatch @action makeIntoPortal = async () => { - let anchors = await Promise.all(DocListCast(this.Document.links).map(async (d: Doc) => Cast(d.anchor2, Doc))); + const anchors = await Promise.all(DocListCast(this.Document.links).map(async (d: Doc) => Cast(d.anchor2, Doc))); if (!anchors.find(anchor2 => anchor2 && anchor2.title === this.Document.title + ".portal" ? true : false)) { - let portalID = (this.Document.title + ".portal").replace(/^-/, "").replace(/\([0-9]*\)$/, ""); + const portalID = (this.Document.title + ".portal").replace(/^-/, "").replace(/\([0-9]*\)$/, ""); DocServer.GetRefField(portalID).then(existingPortal => { - let 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; }); @@ -396,7 +400,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu e.preventDefault(); const cm = ContextMenu.Instance; - let subitems: ContextMenuProps[] = []; + const subitems: ContextMenuProps[] = []; subitems.push({ description: "Open Full Screen", event: () => CollectionDockingView.Instance && CollectionDockingView.Instance.OpenFullScreen(this), icon: "desktop" }); subitems.push({ description: "Open Tab ", event: () => this.props.addDocTab(this.props.Document, this.props.DataDoc, "inTab"), icon: "folder" }); subitems.push({ description: "Open Right ", event: () => this.props.addDocTab(this.props.Document, this.props.DataDoc, "onRight"), icon: "caret-square-right" }); @@ -406,8 +410,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu cm.addItem({ description: "Open...", subitems: subitems, icon: "external-link-alt" }); - let existingOnClick = ContextMenu.Instance.findByDescription("OnClick..."); - let onClicks: ContextMenuProps[] = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : []; + const existingOnClick = ContextMenu.Instance.findByDescription("OnClick..."); + const onClicks: ContextMenuProps[] = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : []; onClicks.push({ description: "Enter Portal", event: this.makeIntoPortal, icon: "window-restore" }); onClicks.push({ description: "Toggle Detail", event: () => this.Document.onClick = ScriptField.MakeScript("toggleDetail(this)"), icon: "window-restore" }); onClicks.push({ description: this.Document.ignoreClick ? "Select" : "Do Nothing", event: () => this.Document.ignoreClick = !this.Document.ignoreClick, icon: this.Document.ignoreClick ? "unlock" : "lock" }); @@ -421,7 +425,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu }); !existingOnClick && cm.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" }); - let funcs: ContextMenuProps[] = []; + const funcs: ContextMenuProps[] = []; if (this.Document.onDragStart) { funcs.push({ description: "Drag an Alias", icon: "edit", event: () => this.Document.dragFactory && (this.Document.onDragStart = ScriptField.MakeFunction('getAlias(this.dragFactory)')) }); funcs.push({ description: "Drag a Copy", icon: "edit", event: () => this.Document.dragFactory && (this.Document.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory, true)')) }); @@ -429,8 +433,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu ContextMenu.Instance.addItem({ description: "OnDrag...", subitems: funcs, icon: "asterisk" }); } - let existing = ContextMenu.Instance.findByDescription("Layout..."); - let layoutItems: ContextMenuProps[] = existing && "subitems" in existing ? existing.subitems : []; + 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" }); @@ -449,8 +453,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu } !existing && cm.addItem({ description: "Layout...", subitems: layoutItems, icon: "compass" }); - let more = ContextMenu.Instance.findByDescription("More..."); - let moreItems: ContextMenuProps[] = more && "subitems" in more ? more.subitems : []; + const more = ContextMenu.Instance.findByDescription("More..."); + const moreItems: ContextMenuProps[] = more && "subitems" in more ? more.subitems : []; if (!ClientUtils.RELEASE) { // let copies: ContextMenuProps[] = []; @@ -485,7 +489,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu !more && cm.addItem({ description: "More...", subitems: moreItems, icon: "hand-point-right" }); runInAction(() => { if (!ClientUtils.RELEASE) { - let setWriteMode = (mode: DocServer.WriteMode) => { + const setWriteMode = (mode: DocServer.WriteMode) => { DocServer.AclsMode = mode; const mode1 = mode; const mode2 = mode === DocServer.WriteMode.Default ? mode : DocServer.WriteMode.Playground; @@ -499,7 +503,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu DocServer.setFieldWriteMode("scale", mode2); DocServer.setFieldWriteMode("viewType", mode2); }; - let aclsMenu: ContextMenuProps[] = []; + const aclsMenu: ContextMenuProps[] = []; aclsMenu.push({ description: "Default (write/read all)", event: () => setWriteMode(DocServer.WriteMode.Default), icon: DocServer.AclsMode === DocServer.WriteMode.Default ? "check" : "exclamation" }); aclsMenu.push({ description: "Playground (write own/no read)", event: () => setWriteMode(DocServer.WriteMode.Playground), icon: DocServer.AclsMode === DocServer.WriteMode.Playground ? "check" : "exclamation" }); aclsMenu.push({ description: "Live Playground (write own/read others)", event: () => setWriteMode(DocServer.WriteMode.LivePlayground), icon: DocServer.AclsMode === DocServer.WriteMode.LivePlayground ? "check" : "exclamation" }); @@ -535,8 +539,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu select = (ctrlPressed: boolean) => { SelectionManager.SelectDoc(this, ctrlPressed); }; chromeHeight = () => { - let showOverlays = this.props.showOverlays ? this.props.showOverlays(this.Document) : undefined; - let showTitle = showOverlays && "title" in showOverlays ? showOverlays.title : StrCast(this.Document.showTitle); + const showOverlays = this.props.showOverlays ? this.props.showOverlays(this.Document) : undefined; + const showTitle = showOverlays && "title" in showOverlays ? showOverlays.title : StrCast(this.Document.showTitle); return (showTitle ? 25 : 0) + 1; } @@ -581,8 +585,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu // if it's a tempoarl link (currently just for Audio), then the audioBox will display the anchor and we don't want to display it here. // would be good to generalize this some way. isNonTemporalLink = (linkDoc: Doc) => { - let anchor = Cast(Doc.AreProtosEqual(this.props.Document, Cast(linkDoc.anchor1, Doc) as Doc) ? linkDoc.anchor1 : linkDoc.anchor2, Doc) as Doc; - let ept = Doc.AreProtosEqual(this.props.Document, Cast(linkDoc.anchor1, Doc) as Doc) ? linkDoc.anchor1Timecode : linkDoc.anchor2Timecode; + const anchor = Cast(Doc.AreProtosEqual(this.props.Document, Cast(linkDoc.anchor1, Doc) as Doc) ? linkDoc.anchor1 : linkDoc.anchor2, Doc) as Doc; + const ept = Doc.AreProtosEqual(this.props.Document, Cast(linkDoc.anchor1, Doc) as Doc) ? linkDoc.anchor1Timecode : linkDoc.anchor2Timecode; return anchor.type === DocumentType.AUDIO && NumCast(ept) ? false : true; } @@ -645,6 +649,21 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu return (this.Document.isBackground && !this.isSelected()) || (this.Document.type === DocumentType.INK && InkingControl.Instance.selectedTool !== InkTool.None); } + @action + handle2PointersMove = (e: TouchEvent) => { + const pt1 = e.targetTouches.item(0); + const pt2 = e.targetTouches.item(1); + if (pt1 && pt2 && this.prevPoints.has(pt1.identifier) && this.prevPoints.has(pt2.identifier)) { + const oldPoint1 = this.prevPoints.get(pt1.identifier); + const oldPoint2 = this.prevPoints.get(pt2.identifier); + const pinching = InteractionUtils.Pinning(pt1, pt2, oldPoint1!, oldPoint2!); + if (pinching !== 0) { + const newWidth = Math.max(Math.abs(oldPoint1!.clientX - oldPoint2!.clientX), Math.abs(pt1.clientX - pt2.clientX)); + this.props.Document.width = newWidth; + } + } + } + render() { if (!this.props.Document) return (null); const ruleColor = this.props.ruleProvider ? StrCast(this.props.ruleProvider["ruleColor_" + this.Document.heading]) : undefined; @@ -660,22 +679,15 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu const localScale = fullDegree; const animDims = this.Document.animateToDimensions ? Array.from(this.Document.animateToDimensions) : undefined; - let animheight = animDims ? animDims[1] : "100%"; - let animwidth = animDims ? animDims[0] : "100%"; + 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; + const highlighting = fullDegree && this.layoutDoc.type !== DocumentType.FONTICON && this.layoutDoc.viewType !== CollectionViewType.Linear; return <div className={`documentView-node${this.topMost ? "-topmost" : ""}`} ref={this._mainCont} onDrop={this.onDrop} onContextMenu={this.onContextMenu} onPointerDown={this.onPointerDown} onClick={this.onClick} - onPointerEnter={e => { - console.log("Brush" + this.props.Document.title); - Doc.BrushDoc(this.props.Document); - }} onPointerLeave={e => { - console.log("UnBrush" + this.props.Document.title); - Doc.UnBrushDoc(this.props.Document); - - }} + 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), pointerEvents: this.ignorePointerEvents ? "none" : "all", @@ -686,7 +698,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu width: animwidth, height: animheight, opacity: this.Document.opacity - }} > + }} onTouchStart={this.onTouchStart}> {this.innards} </div>; } diff --git a/src/client/views/nodes/FaceRectangle.tsx b/src/client/views/nodes/FaceRectangle.tsx index 887efc0d5..20afa4565 100644 --- a/src/client/views/nodes/FaceRectangle.tsx +++ b/src/client/views/nodes/FaceRectangle.tsx @@ -12,7 +12,7 @@ export default class FaceRectangle extends React.Component<{ rectangle: Rectangl } render() { - let rectangle = this.props.rectangle; + const rectangle = this.props.rectangle; return ( <div style={{ diff --git a/src/client/views/nodes/FaceRectangles.tsx b/src/client/views/nodes/FaceRectangles.tsx index acf1aced3..3c7f1f206 100644 --- a/src/client/views/nodes/FaceRectangles.tsx +++ b/src/client/views/nodes/FaceRectangles.tsx @@ -20,10 +20,10 @@ export interface RectangleTemplate { export default class FaceRectangles extends React.Component<FaceRectanglesProps> { render() { - let faces = DocListCast(this.props.document.faces); - let templates: RectangleTemplate[] = faces.map(faceDoc => { - let rectangle = Cast(faceDoc.faceRectangle, Doc) as Doc; - let style = { + const faces = DocListCast(this.props.document.faces); + const templates: RectangleTemplate[] = faces.map(faceDoc => { + const rectangle = Cast(faceDoc.faceRectangle, Doc) as Doc; + const style = { top: NumCast(rectangle.top), left: NumCast(rectangle.left), width: NumCast(rectangle.width), diff --git a/src/client/views/nodes/FontIconBox.scss b/src/client/views/nodes/FontIconBox.scss index 905601ce3..f0fe7a54e 100644 --- a/src/client/views/nodes/FontIconBox.scss +++ b/src/client/views/nodes/FontIconBox.scss @@ -2,12 +2,14 @@ width: 100%; height: 100%; pointer-events: all; + touch-action: none; border-radius: inherit; background: black; border-radius: 100%; transform-origin: top left; + svg { - width:95% !important; - height:95%; + width: 95% !important; + height: 95%; } -} +}
\ No newline at end of file diff --git a/src/client/views/nodes/FontIconBox.tsx b/src/client/views/nodes/FontIconBox.tsx index 960b55e3e..2433251b3 100644 --- a/src/client/views/nodes/FontIconBox.tsx +++ b/src/client/views/nodes/FontIconBox.tsx @@ -25,8 +25,8 @@ export class FontIconBox extends DocComponent<FieldViewProps, FontIconDocument>( this._backgroundReaction = reaction(() => this.props.Document.backgroundColor, () => { if (this._ref && this._ref.current) { - let col = Utils.fromRGBAstr(getComputedStyle(this._ref.current).backgroundColor); - let colsum = (col.r + col.g + col.b); + const col = Utils.fromRGBAstr(getComputedStyle(this._ref.current).backgroundColor); + const colsum = (col.r + col.g + col.b); if (colsum / col.a > 600 || col.a < 0.25) runInAction(() => this._foregroundColor = "black"); else if (colsum / col.a <= 600 || col.a >= .25) runInAction(() => this._foregroundColor = "white"); } @@ -36,8 +36,8 @@ export class FontIconBox extends DocComponent<FieldViewProps, FontIconDocument>( this._backgroundReaction && this._backgroundReaction(); } render() { - let referenceDoc = (this.props.Document.dragFactory instanceof Doc ? this.props.Document.dragFactory : this.props.Document); - let referenceLayout = Doc.Layout(referenceDoc); + 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} style={{ background: StrCast(referenceLayout.backgroundColor), diff --git a/src/client/views/nodes/FormattedTextBox.scss b/src/client/views/nodes/FormattedTextBox.scss index 77cdd3d42..c06f38a6c 100644 --- a/src/client/views/nodes/FormattedTextBox.scss +++ b/src/client/views/nodes/FormattedTextBox.scss @@ -31,8 +31,8 @@ flex-direction: row; .formattedTextBox-dictation { - height: 20px; - width: 20px; + height: 12px; + width: 10px; top: 0px; left: 0px; position: absolute; @@ -50,6 +50,7 @@ width: 100%; height: 100%; } + .formattedTextBox-sidebar-handle { position: absolute; top: calc(50% - 17.5px); @@ -58,19 +59,23 @@ background: lightgray; border-radius: 20px; } -.formattedTextBox-cont > .formattedTextBox-sidebar-handle { + +.formattedTextBox-cont>.formattedTextBox-sidebar-handle { right: 0; left: unset; } -.formattedTextBox-sidebar, .formattedTextBox-sidebar-inking { - border-left: dashed 1px black; - height: 100%; + +.formattedTextBox-sidebar, +.formattedTextBox-sidebar-inking { + border-left: dashed 1px black; + height: 100%; display: inline-block; position: absolute; right: 0; - > .formattedTextBox-sidebar-handle { - right:unset; - left:-5; + + >.formattedTextBox-sidebar-handle { + right: unset; + left: -5; } } @@ -184,15 +189,27 @@ footnote::after { width: 0; } -.formattedTextBox-summarizer { - opacity: 0.5; + +.formattedTextBox-inlineComment { position: relative; width: 40px; height: 20px; + &::after { + content: "→"; + } + &:hover { + background: orange; + } } -.formattedTextBox-summarizer::after { - content: "←"; +.formattedTextBox-summarizer { + opacity: 0.5; + position: relative; + width: 40px; + height: 20px; + &::after { + content: "←"; + } } .formattedTextBox-summarizer-collapsed { @@ -200,10 +217,9 @@ footnote::after { position: relative; width: 40px; height: 20px; -} - -.formattedTextBox-summarizer-collapsed::after { - content: "..."; + &::after { + content: "..."; + } } .ProseMirror { @@ -214,218 +230,27 @@ footnote::after { padding-left: 0px; } - .decimal1-ol { - counter-reset: deci1; - - p { - display: inline - } - - ; - font-size: 24; - - ul, - ol { - padding-left: 30px; - } - } - - .decimal2-ol { - counter-reset: deci2; - - p { - display: inline - } - - ; - font-size: 18; - - ul, - ol { - padding-left: 30px; - } - } - - .decimal3-ol { - counter-reset: deci3; - - p { - display: inline - } - - ; - font-size: 14; - - ul, - ol { - padding-left: 30px; - } - } - - .decimal4-ol { - counter-reset: deci4; - - p { - display: inline - } - - ; - font-size: 10; - - ul, - ol { - padding-left: 30px; - } - } - - .decimal5-ol { - counter-reset: deci5; - - p { - display: inline - } - - ; - font-size: 10; - - ul, - ol { - padding-left: 30px; - } - } - - .decimal6-ol { - counter-reset: deci6; - - p { - display: inline - } - - ; - font-size: 10; - - ul, - ol { - padding-left: 30px; - } - } - - .decimal7-ol { - counter-reset: deci7; - - p { - display: inline - } - - ; - font-size: 10; - - ul, - ol { - padding-left: 30px; - } - } - - .upper-alpha-ol { - counter-reset: ualph; - - p { - display: inline - } - - ; - font-size: 18; - } - - .lower-roman-ol { - counter-reset: lroman; - - p { - display: inline - } - - ; - font-size: 14; - } - - .lower-alpha-ol { - counter-reset: lalpha; - - p { - display: inline - } - - ; - font-size: 10; - } - - .decimal1:before { - content: counter(deci1) ") "; - counter-increment: deci1; - display: inline-block; - min-width: 30; - } - - .decimal2:before { - content: counter(deci1) "."counter(deci2) ") "; - counter-increment: deci2; - display: inline-block; - min-width: 35 - } - - .decimal3:before { - content: counter(deci1) "."counter(deci2) "."counter(deci3) ") "; - counter-increment: deci3; - display: inline-block; - min-width: 35 - } - - .decimal4:before { - content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) ") "; - counter-increment: deci4; - display: inline-block; - min-width: 40 - } - - .decimal5:before { - content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) "."counter(deci5) ") "; - counter-increment: deci5; - display: inline-block; - min-width: 40 - } - - .decimal6:before { - content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) "."counter(deci5) "."counter(deci6) ") "; - counter-increment: deci6; - display: inline-block; - min-width: 45 - } - - .decimal7:before { - content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) "."counter(deci5) "."counter(deci6) "."counter(deci7) ") "; - counter-increment: deci7; - display: inline-block; - min-width: 50 - } - - .upper-alpha:before { - content: counter(deci1) "."counter(ualph, upper-alpha) ") "; - counter-increment: ualph; - display: inline-block; - min-width: 35 - } - - .lower-roman:before { - content: counter(deci1) "."counter(ualph, upper-alpha) "."counter(lroman, lower-roman) ") "; - counter-increment: lroman; - display: inline-block; - min-width: 50 - } - - .lower-alpha:before { - content: counter(deci1) "."counter(ualph, upper-alpha) "."counter(lroman, lower-roman) "."counter(lalpha, lower-alpha) ") "; - counter-increment: lalpha; - display: inline-block; - min-width: 35 - } + .decimal1-ol { counter-reset: deci1; p { display: inline }; ul, ol { padding-left: 30px; } } + .decimal2-ol { counter-reset: deci2; p { display: inline }; font-size: smaller; ul, ol { padding-left: 30px; } } + .decimal3-ol { counter-reset: deci3; p { display: inline }; font-size: smaller; ul, ol { padding-left: 30px; } } + .decimal4-ol { counter-reset: deci4; p { display: inline }; font-size: smaller; ul, ol { padding-left: 30px; } } + .decimal5-ol { counter-reset: deci5; p { display: inline }; font-size: smaller; ul, ol { padding-left: 30px; } } + .decimal6-ol { counter-reset: deci6; p { display: inline }; font-size: smaller; ul, ol { padding-left: 30px; } } + .decimal7-ol { counter-reset: deci7; p { display: inline }; font-size: smaller; ul, ol { padding-left: 30px; } } + + .upper-alpha-ol { counter-reset: ualph; p { display: inline}; font-size: smaller; } + .lower-roman-ol { counter-reset: lroman; p { display: inline}; font-size: smaller; } + .lower-alpha-ol { counter-reset: lalpha; p { display: inline}; font-size: smaller; } + + .decimal1:before { counter-increment: deci1; display: inline-block; content: counter(deci1) ". "; } + .decimal2:before { counter-increment: deci2; display: inline-block; content: counter(deci1) "."counter(deci2) ". "; } + .decimal3:before { counter-increment: deci3; display: inline-block; content: counter(deci1) "."counter(deci2) "."counter(deci3) ". "; } + .decimal4:before { counter-increment: deci4; display: inline-block; content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) ". "; } + .decimal5:before { counter-increment: deci5; display: inline-block; content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) "."counter(deci5) ". "; } + .decimal6:before { counter-increment: deci5; display: inline-block; content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) "."counter(deci5) "."counter(deci6) ". "; } + .decimal7:before { counter-increment: deci5; display: inline-block; content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) "."counter(deci5) "."counter(deci6) "."counter(deci7) ". "; } + + .upper-alpha:before { counter-increment: ualph; display: inline-block; content: counter(deci1) "."counter(ualph, upper-alpha) ". "; } + .lower-roman:before { counter-increment: lroman; display: inline-block; content: counter(deci1) "."counter(ualph, upper-alpha) "."counter(lroman, lower-roman) ". "; } + .lower-alpha:before { counter-increment: lalpha; display: inline-block; content: counter(deci1) "."counter(ualph, upper-alpha) "."counter(lroman, lower-roman) "."counter(lalpha, lower-alpha) ". "; } }
\ No newline at end of file diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 9910c9ecd..f9b246c10 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -1,6 +1,6 @@ import { library } from '@fortawesome/fontawesome-svg-core'; import { faEdit, faSmile, faTextHeight, faUpload } from '@fortawesome/free-solid-svg-icons'; -import _ from "lodash"; +import { isEqual } from "lodash"; import { action, computed, IReactionDisposer, Lambda, observable, reaction, runInAction, trace } from "mobx"; import { observer } from "mobx-react"; import { baseKeymap } from "prosemirror-commands"; @@ -27,14 +27,13 @@ import { DictationManager } from '../../util/DictationManager'; import { DragManager } from "../../util/DragManager"; import buildKeymap from "../../util/ProsemirrorExampleTransfer"; import { inpRules } from "../../util/RichTextRules"; -import { FootnoteView, ImageResizeView, DashDocView, OrderedListView, schema, SummarizedView } from "../../util/RichTextSchema"; +import { DashDocCommentView, FootnoteView, ImageResizeView, DashDocView, OrderedListView, schema, SummarizedView } 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 { DocumentButtonBar } from '../DocumentButtonBar'; -import { DocumentDecorations } from '../DocumentDecorations'; import { InkingControl } from "../InkingControl"; import { FieldView, FieldViewProps } from "./FieldView"; import "./FormattedTextBox.scss"; @@ -83,6 +82,9 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & private _applyingChange: boolean = false; private _nodeClicked: any; private _searchIndex = 0; + private _sidebarMovement = 0; + private _lastX = 0; + private _lastY = 0; private _undoTyping?: UndoManager.Batch; private _searchReactionDisposer?: Lambda; private _scrollToRegionReactionDisposer: Opt<IReactionDisposer>; @@ -92,6 +94,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & private _proxyReactionDisposer: Opt<IReactionDisposer>; private _pullReactionDisposer: Opt<IReactionDisposer>; private _pushReactionDisposer: Opt<IReactionDisposer>; + private _buttonBarReactionDisposer: Opt<IReactionDisposer>; private dropDisposer?: DragManager.DragDropDisposer; @observable private _ruleFontSize = 0; @@ -103,8 +106,8 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & return html.indexOf("data-pm-slice") !== -1; } public static GetHref(html: string): string { - let parser = new DOMParser(); - let parsedHtml = parser.parseFromString(html, 'text/html'); + const parser = new DOMParser(); + const parsedHtml = parser.parseFromString(html, 'text/html'); if (parsedHtml.body.childNodes.length === 1 && parsedHtml.body.childNodes[0].childNodes.length === 1 && (parsedHtml.body.childNodes[0].childNodes[0] as any).href) { return (parsedHtml.body.childNodes[0].childNodes[0] as any).href; @@ -126,12 +129,12 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & @undoBatch public setFontColor(color: string) { - let view = this._editorView!; + const view = this._editorView!; if (view.state.selection.from === view.state.selection.to) return false; if (view.state.selection.to - view.state.selection.from > view.state.doc.nodeSize - 3) { this.layoutDoc.color = color; } - let colorMark = view.state.schema.mark(view.state.schema.marks.pFontColor, { color: color }); + const colorMark = view.state.schema.mark(view.state.schema.marks.pFontColor, { color: color }); view.dispatch(view.state.tr.addMark(view.state.selection.from, view.state.selection.to, colorMark)); return true; } @@ -147,9 +150,9 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & doLinkOnDeselect() { Array.from(this.linkOnDeselect.entries()).map(entry => { - let key = entry[0]; - let value = entry[1]; - let id = Utils.GenerateDeterministicGuid(this.dataDoc[Id] + key); + const key = entry[0]; + const value = entry[1]; + 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); @@ -164,24 +167,24 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & dispatchTransaction = (tx: Transaction) => { if (this._editorView) { - let metadata = tx.selection.$from.marks().find((m: Mark) => m.type === schema.marks.metadata); + const metadata = tx.selection.$from.marks().find((m: Mark) => m.type === schema.marks.metadata); if (metadata) { - let range = tx.selection.$from.blockRange(tx.selection.$to); + const range = tx.selection.$from.blockRange(tx.selection.$to); let text = range ? tx.doc.textBetween(range.start, range.end) : ""; let textEndSelection = tx.selection.to; for (; textEndSelection < range!.end && text[textEndSelection - range!.start] !== " "; textEndSelection++) { } text = text.substr(0, textEndSelection - range!.start); text = text.split(" ")[text.split(" ").length - 1]; - let split = text.split("::"); + const split = text.split("::"); if (split.length > 1 && split[1]) { - let key = split[0]; - let value = split[split.length - 1]; + const key = split[0]; + const value = split[split.length - 1]; this.linkOnDeselect.set(key, value); - let id = Utils.GenerateDeterministicGuid(this.dataDoc[Id] + key); + const id = Utils.GenerateDeterministicGuid(this.dataDoc[Id] + key); const link = this._editorView.state.schema.marks.link.create({ href: `http://localhost:1050/doc/${id}`, location: "onRight", title: value }); const mval = this._editorView.state.schema.marks.metadataVal.create(); - let offset = (tx.selection.to === range!.end - 1 ? -1 : 0); + const offset = (tx.selection.to === range!.end - 1 ? -1 : 0); tx = tx.addMark(textEndSelection - value.length + offset, textEndSelection, link).addMark(textEndSelection - value.length + offset, textEndSelection, mval); this.dataDoc[key] = value; } @@ -189,7 +192,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & const state = this._editorView.state.apply(tx); this._editorView.updateState(state); - let tsel = this._editorView.state.selection.$from; + 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 = new DateField(new Date(Date.now()))); @@ -202,8 +205,8 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & updateTitle = () => { if (StrCast(this.dataDoc.title).startsWith("-") && this._editorView && !this.Document.customTitle) { - let str = this._editorView.state.doc.textContent; - let titlestr = str.substr(0, Math.min(40, str.length)); + const str = this._editorView.state.doc.textContent; + const titlestr = str.substr(0, Math.min(40, str.length)); this.dataDoc.title = "-" + titlestr + (str.length > 40 ? "..." : ""); } } @@ -212,11 +215,11 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & if (this._editorView && (this._editorView as any).docView) { const mark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight); const activeMark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight, { selected: true }); - let res = terms.map(term => this.findInNode(this._editorView!, this._editorView!.state.doc, term)); + const res = terms.map(term => this.findInNode(this._editorView!, this._editorView!.state.doc, term)); let tr = this._editorView.state.tr; - let flattened: TextSelection[] = []; + const flattened: TextSelection[] = []; res.map(r => r.map(h => flattened.push(h))); - let lastSel = Math.min(flattened.length - 1, this._searchIndex); + const lastSel = Math.min(flattened.length - 1, this._searchIndex); flattened.forEach((h: TextSelection, ind: number) => tr = tr.addMark(h.from, h.to, ind === lastSel ? activeMark : mark)); this._searchIndex = ++this._searchIndex > flattened.length - 1 ? 0 : this._searchIndex; this._editorView.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(flattened[lastSel].from), tr.doc.resolve(flattened[lastSel].to))).scrollIntoView()); @@ -227,13 +230,13 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & if (this._editorView && (this._editorView as any).docView) { const mark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight); const activeMark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight, { selected: true }); - let end = this._editorView.state.doc.nodeSize - 2; + const end = this._editorView.state.doc.nodeSize - 2; this._editorView.dispatch(this._editorView.state.tr.removeMark(0, end, mark).removeMark(0, end, activeMark)); } } setAnnotation = (start: number, end: number, mark: Mark, opened: boolean, keep: boolean = false) => { - let view = this._editorView!; - let nmark = view.state.schema.marks.user_mark.create({ ...mark.attrs, userid: keep ? Doc.CurrentUserEmail : mark.attrs.userid, opened: opened }); + const view = this._editorView!; + const nmark = view.state.schema.marks.user_mark.create({ ...mark.attrs, userid: keep ? Doc.CurrentUserEmail : mark.attrs.userid, opened: opened }); view.dispatch(view.state.tr.removeMark(start, end, nmark).addMark(start, end, nmark)); } protected createDropTarget = (ele: HTMLDivElement) => { @@ -266,20 +269,20 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & e.stopPropagation(); // embed document when dragging with a userDropAction or an embedDoc flag set } else if (de.data.userDropAction || de.data.embedDoc) { - let target = de.data.droppedDocuments[0]; - const link = DocUtils.MakeLink({ doc: this.dataDoc, ctx: this.props.ContainingCollectionDoc }, { doc: target }, "Embedded Doc:" + target.title); - if (link) { - target.fitToBox = true; - let node = schema.nodes.dashDoc.create({ - width: target[WidthSym](), height: target[HeightSym](), - title: "dashDoc", docid: target[Id], - float: "right" - }); - let view = this._editorView!; - view.dispatch(view.state.tr.insert(view.posAtCoords({ left: de.x, top: de.y })!.pos, node)); - this.tryUpdateHeight(); - e.stopPropagation(); - } + const target = de.data.droppedDocuments[0]; + // const link = DocUtils.MakeLink({ doc: this.dataDoc, ctx: this.props.ContainingCollectionDoc }, { doc: target }, "Embedded Doc:" + target.title); + // if (link) { + target.fitToBox = true; + const node = schema.nodes.dashDoc.create({ + width: target[WidthSym](), height: target[HeightSym](), + title: "dashDoc", docid: target[Id], + float: "right" + }); + const view = this._editorView!; + view.dispatch(view.state.tr.insert(view.posAtCoords({ left: de.x, top: de.y })!.pos, node)); + this.tryUpdateHeight(); + e.stopPropagation(); + // } } // otherwise, fall through to outer collection to handle drop } } @@ -292,7 +295,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & if (node.isBlock) { // tslint:disable-next-line: prefer-for-of for (let i = 0; i < (context.content as any).content.length; i++) { - let result = this.getNodeEndpoints((context.content as any).content[i], node); + const result = this.getNodeEndpoints((context.content as any).content[i], node); if (result) { return { from: result.from + offset + (context.type.name === "doc" ? 0 : 1), @@ -313,9 +316,10 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & let ret: TextSelection[] = []; if (node.isTextblock) { - let index = 0, foundAt, ep = this.getNodeEndpoints(pm.state.doc, node); + let index = 0, foundAt; + const ep = this.getNodeEndpoints(pm.state.doc, node); while (ep && (foundAt = node.textContent.slice(index).search(RegExp(find, "i"))) > -1) { - let sel = new TextSelection(pm.state.doc.resolve(ep.from + index + foundAt + 1), pm.state.doc.resolve(ep.from + index + foundAt + find.length + 1)); + const sel = new TextSelection(pm.state.doc.resolve(ep.from + index + foundAt + 1), pm.state.doc.resolve(ep.from + index + foundAt + find.length + 1)); ret.push(sel); index = index + foundAt + find.length; } @@ -348,21 +352,39 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & } if (FormattedTextBox._highlights.indexOf("By Recent Minute") !== -1) { addStyleSheetRule(FormattedTextBox._userStyleSheet, "userMark-" + Doc.CurrentUserEmail.replace(".", "").replace("@", ""), { opacity: "0.1" }); - let min = Math.round(Date.now() / 1000 / 60); + const min = Math.round(Date.now() / 1000 / 60); numberRange(10).map(i => addStyleSheetRule(FormattedTextBox._userStyleSheet, "userMark-min-" + (min - i), { opacity: ((10 - i - 1) / 10).toString() })); setTimeout(() => this.updateHighlights()); } if (FormattedTextBox._highlights.indexOf("By Recent Hour") !== -1) { addStyleSheetRule(FormattedTextBox._userStyleSheet, "userMark-" + Doc.CurrentUserEmail.replace(".", "").replace("@", ""), { opacity: "0.1" }); - let hr = Math.round(Date.now() / 1000 / 60 / 60); + const hr = Math.round(Date.now() / 1000 / 60 / 60); numberRange(10).map(i => addStyleSheetRule(FormattedTextBox._userStyleSheet, "userMark-hr-" + (hr - i), { opacity: ((10 - i - 1) / 10).toString() })); } } - toggleSidebar = () => this.props.Document.sidebarWidthPercent = StrCast(this.props.Document.sidebarWidthPercent, "0%") === "0%" ? "25%" : "0%"; + sidebarDown = (e: React.PointerEvent) => { + this._lastX = e.clientX; + this._lastY = e.clientY; + this._sidebarMovement = 0; + document.addEventListener("pointermove", this.sidebarMove); + document.addEventListener("pointerup", this.sidebarUp); + e.stopPropagation(); + } + sidebarMove = (e: PointerEvent) => { + let bounds = this.CurrentDiv.getBoundingClientRect(); + this._sidebarMovement += Math.sqrt((e.clientX - this._lastX) * (e.clientX - this._lastX) + (e.clientY - this._lastY) * (e.clientY - this._lastY)) + this.props.Document.sidebarWidthPercent = "" + 100 * (1 - (e.clientX - bounds.left) / bounds.width) + "%"; + } + sidebarUp = (e: PointerEvent) => { + document.removeEventListener("pointermove", this.sidebarMove); + document.removeEventListener("pointerup", this.sidebarUp); + } + + toggleSidebar = () => this._sidebarMovement < 5 && (this.props.Document.sidebarWidthPercent = StrCast(this.props.Document.sidebarWidthPercent, "0%") === "0%" ? "25%" : "0%"); specificContextMenu = (e: React.MouseEvent): void => { - let funcs: ContextMenuProps[] = []; + const funcs: ContextMenuProps[] = []; funcs.push({ description: "Toggle Sidebar", event: () => { e.stopPropagation(); this.toggleSidebar(); }, 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 => @@ -403,8 +425,8 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & } recordBullet = async () => { - let completedCue = "end session"; - let results = await DictationManager.Controls.listen({ + const completedCue = "end session"; + const results = await DictationManager.Controls.listen({ interimHandler: this.setCurrentBulletContent, continuous: { indefinite: false }, terminators: [completedCue, "bullet", "next"] @@ -420,20 +442,20 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & setCurrentBulletContent = (value: string) => { if (this._editorView) { let state = this._editorView.state; - let from = state.selection.from; - let to = state.selection.to; + const from = state.selection.from; + const to = state.selection.to; this._editorView.dispatch(state.tr.insertText(value, from, to)); state = this._editorView.state; - let updated = TextSelection.create(state.doc, from, from + value.length); + const updated = TextSelection.create(state.doc, from, from + value.length); this._editorView.dispatch(state.tr.setSelection(updated)); } } nextBullet = (pos: number) => { if (this._editorView) { - let frag = Fragment.fromArray(this.newListItems(2)); + const frag = Fragment.fromArray(this.newListItems(2)); if (this._editorView.state.doc.resolve(pos).depth >= 2) { - let slice = new Slice(frag, 2, 2); + const slice = new Slice(frag, 2, 2); let state = this._editorView.state; this._editorView.dispatch(state.tr.step(new ReplaceStep(pos, pos, slice))); pos += 4; @@ -471,8 +493,15 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & } componentDidMount() { - this.pullFromGoogleDoc(this.checkState); - this.dataDoc[GoogleRef] && this.dataDoc.unchanged && runInAction(() => DocumentDecorations.Instance.isAnimatingFetch = true); + this._buttonBarReactionDisposer = reaction( + () => DocumentButtonBar.Instance, + instance => { + if (instance) { + this.pullFromGoogleDoc(this.checkState); + this.dataDoc[GoogleRef] && this.dataDoc.unchanged && runInAction(() => instance.isAnimatingFetch = true); + } + } + ); this._reactionDisposer = reaction( () => { @@ -481,7 +510,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & }, incomingValue => { if (this._editorView && !this._applyingChange) { - let updatedState = JSON.parse(incomingValue); + const updatedState = JSON.parse(incomingValue); this._editorView.updateState(EditorState.fromJSON(this.config, updatedState)); this.tryUpdateHeight(); } @@ -493,7 +522,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & () => { if (!DocumentButtonBar.hasPulledHack) { DocumentButtonBar.hasPulledHack = true; - let unchanged = this.dataDoc.unchanged; + const unchanged = this.dataDoc.unchanged; this.pullFromGoogleDoc(unchanged ? this.checkState : this.updateState); } } @@ -514,7 +543,6 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & () => this.tryUpdateHeight() ); - this.setupEditor(this.config, this.dataDoc, this.props.fieldKey); this._searchReactionDisposer = reaction(() => { @@ -528,10 +556,9 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & } }, { fireImmediately: true }); - this._rulesReactionDisposer = reaction(() => { - let ruleProvider = this.props.ruleProvider; - let heading = NumCast(this.layoutDoc.heading); + const ruleProvider = this.props.ruleProvider; + const heading = NumCast(this.layoutDoc.heading); if (ruleProvider instanceof Doc) { return { align: StrCast(ruleProvider["ruleAlign_" + heading], ""), @@ -547,7 +574,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & rules && setTimeout(() => { const view = this._editorView!; if (this._proseRef) { - let n = new NodeSelection(view.state.doc.resolve(0)); + 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)); @@ -562,10 +589,10 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & this._scrollToRegionReactionDisposer = reaction( () => StrCast(this.layoutDoc.scrollToLinkID), async (scrollToLinkID) => { - let findLinkFrag = (frag: Fragment, editor: EditorView) => { + const findLinkFrag = (frag: Fragment, editor: EditorView) => { const nodes: Node[] = []; frag.forEach((node, index) => { - let examinedNode = findLinkNode(node, editor); + const examinedNode = findLinkNode(node, editor); if (examinedNode && examinedNode.textContent) { nodes.push(examinedNode); start += index; @@ -573,7 +600,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & }); return { frag: Fragment.fromArray(nodes), start: start }; }; - let findLinkNode = (node: Node, editor: EditorView) => { + const findLinkNode = (node: Node, editor: EditorView) => { if (!node.isText) { const content = findLinkFrag(node.content, editor); return node.copy(content.frag); @@ -585,8 +612,8 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & let start = -1; if (this._editorView && scrollToLinkID) { - let editor = this._editorView; - let ret = findLinkFrag(editor.state.doc.content, editor); + const editor = this._editorView; + const ret = findLinkFrag(editor.state.doc.content, editor); if (ret.frag.size > 2 && ret.start >= 0) { let selection = TextSelection.near(editor.state.doc.resolve(ret.start)); // default to near the start @@ -610,28 +637,28 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & pushToGoogleDoc = async () => { this.pullFromGoogleDoc(async (exportState: Opt<GoogleApiClientUtils.Docs.ImportResult>, dataDoc: Doc) => { - let modes = GoogleApiClientUtils.Docs.WriteMode; + const modes = GoogleApiClientUtils.Docs.WriteMode; let mode = modes.Replace; let reference: Opt<GoogleApiClientUtils.Docs.Reference> = Cast(this.dataDoc[GoogleRef], "string"); if (!reference) { mode = modes.Insert; reference = { title: StrCast(this.dataDoc.title) }; } - let redo = async () => { + const redo = async () => { if (this._editorView && reference) { - let content = await RichTextUtils.GoogleDocs.Export(this._editorView.state); - let response = await GoogleApiClientUtils.Docs.write({ reference, content, mode }); + const content = await RichTextUtils.GoogleDocs.Export(this._editorView.state); + const response = await GoogleApiClientUtils.Docs.write({ reference, content, mode }); response && (this.dataDoc[GoogleRef] = response.documentId); - let pushSuccess = response !== undefined && !("errors" in response); + const pushSuccess = response !== undefined && !("errors" in response); dataDoc.unchanged = pushSuccess; DocumentButtonBar.Instance.startPushOutcome(pushSuccess); } }; - let undo = () => { + const undo = () => { if (!exportState) { return; } - let content: GoogleApiClientUtils.Docs.Content = { + const content: GoogleApiClientUtils.Docs.Content = { text: exportState.text, requests: [] }; @@ -645,8 +672,8 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & } pullFromGoogleDoc = async (handler: PullHandler) => { - let dataDoc = this.dataDoc; - let documentId = StrCast(dataDoc[GoogleRef]); + const dataDoc = this.dataDoc; + const documentId = StrCast(dataDoc[GoogleRef]); let exportState: Opt<GoogleApiClientUtils.Docs.ImportResult>; if (documentId) { exportState = await RichTextUtils.GoogleDocs.Import(documentId, dataDoc); @@ -661,8 +688,8 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & dataDoc.data = new RichTextField(JSON.stringify(exportState.state.toJSON())); setTimeout(() => { if (this._editorView) { - let state = this._editorView.state; - let end = state.doc.content.size - 1; + const state = this._editorView.state; + const end = state.doc.content.size - 1; this._editorView.dispatch(state.tr.setSelection(TextSelection.create(state.doc, end, end))); } }, 0); @@ -677,9 +704,9 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & checkState = (exportState: Opt<GoogleApiClientUtils.Docs.ImportResult>, dataDoc: Doc) => { if (exportState && this._editorView) { - let equalContent = _.isEqual(this._editorView.state.doc, exportState.state.doc); - let equalTitles = dataDoc.title === exportState.title; - let unchanged = equalContent && equalTitles; + const equalContent = isEqual(this._editorView.state.doc, exportState.state.doc); + const equalTitles = dataDoc.title === exportState.title; + const unchanged = equalContent && equalTitles; dataDoc.unchanged = unchanged; DocumentButtonBar.Instance.setPullState(unchanged); } @@ -707,7 +734,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & } handlePaste = (view: EditorView, event: Event, slice: Slice): boolean => { - let cbe = event as ClipboardEvent; + const cbe = event as ClipboardEvent; const pdfDocId = cbe.clipboardData && cbe.clipboardData.getData("dash/pdfOrigin"); const pdfRegionId = cbe.clipboardData && cbe.clipboardData.getData("dash/pdfRegion"); if (pdfDocId && pdfRegionId) { @@ -717,18 +744,18 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & setTimeout(async () => { const extension = Doc.fieldExtensionDoc(pdfDoc, "data"); if (extension) { - let 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 + 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); } }); - let link = DocUtils.MakeLink({ doc: this.props.Document, ctx: this.props.ContainingCollectionDoc }, { doc: pdfRegion, ctx: pdfDoc }, "note on " + pdfDoc.title, "pasted PDF link"); + const link = DocUtils.MakeLink({ doc: this.props.Document, ctx: this.props.ContainingCollectionDoc }, { doc: pdfRegion, ctx: pdfDoc }, "note on " + pdfDoc.title, "pasted PDF link"); if (link) { cbe.clipboardData!.setData("dash/linkDoc", link[Id]); - let linkId = link[Id]; - let frag = addMarkToFrag(slice.content, (node: Node) => addLinkMark(node, StrCast(pdfDoc.title), linkId)); + const linkId = link[Id]; + const frag = addMarkToFrag(slice.content, (node: Node) => addLinkMark(node, StrCast(pdfDoc.title), linkId)); slice = new Slice(frag, slice.openStart, slice.openEnd); - var tr = view.state.tr.replaceSelection(slice); + const tr = view.state.tr.replaceSelection(slice); view.dispatch(tr.scrollIntoView().setMeta("paste", true).setMeta("uiEvent", "paste")); } } @@ -758,11 +785,11 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & } private setupEditor(config: any, doc: Doc, fieldKey: string) { - let field = doc ? Cast(doc[fieldKey], RichTextField) : undefined; + const field = doc ? Cast(doc[fieldKey], RichTextField) : undefined; let startup = StrCast(doc.documentText); startup = startup.startsWith("@@@") ? startup.replace("@@@", "") : ""; if (!field && doc) { - let text = StrCast(doc[fieldKey]); + const text = StrCast(doc[fieldKey]); if (text) { startup = text; } else if (Cast(doc[fieldKey], "number")) { @@ -770,16 +797,16 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & } } if (this._proseRef) { - let self = this; + const self = this; this._editorView && this._editorView.destroy(); this._editorView = new EditorView(this._proseRef, { state: field && field.Data ? EditorState.fromJSON(config, JSON.parse(field.Data)) : EditorState.create(config), handleScrollToSelection: (editorView) => { - let ref = editorView.domAtPos(editorView.state.selection.from); + const ref = editorView.domAtPos(editorView.state.selection.from); let refNode = ref.node as any; while (refNode && !("getBoundingClientRect" in refNode)) refNode = refNode.parentElement; - let r1 = refNode && refNode.getBoundingClientRect(); - let r3 = self._ref.current!.getBoundingClientRect(); + const r1 = refNode && refNode.getBoundingClientRect(); + const r3 = self._ref.current!.getBoundingClientRect(); if (r1.top < r3.top || r1.top > r3.bottom) { r1 && (self._ref.current!.scrollTop += (r1.top - r3.top) * self.props.ScreenToLocalTransform().Scale); } @@ -787,6 +814,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & }, dispatchTransaction: this.dispatchTransaction, nodeViews: { + dashComment(node, view, getPos) { return new DashDocCommentView(node, view, getPos); }, dashDoc(node, view, getPos) { return new DashDocView(node, view, getPos, self); }, image(node, view, getPos) { return new ImageResizeView(node, view, getPos, self.props.addDocTab); }, star(node, view, getPos) { return new SummarizedView(node, view, getPos); }, @@ -802,12 +830,13 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & } } - let selectOnLoad = this.props.Document[Id] === FormattedTextBox.SelectOnLoad; + const selectOnLoad = this.props.Document[Id] === FormattedTextBox.SelectOnLoad; if (selectOnLoad) { FormattedTextBox.SelectOnLoad = ""; this.props.select(false); } - this._editorView!.focus(); + const rtf = doc ? Cast(doc[fieldKey], RichTextField) : undefined; + (selectOnLoad || (rtf && !rtf.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) })]; } @@ -833,11 +862,12 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & this._pullReactionDisposer && this._pullReactionDisposer(); this._heightReactionDisposer && this._heightReactionDisposer(); this._searchReactionDisposer && this._searchReactionDisposer(); + this._buttonBarReactionDisposer && this._buttonBarReactionDisposer(); this._editorView && this._editorView.destroy(); } onPointerDown = (e: React.PointerEvent): void => { FormattedTextBoxComment.textBox = this; - let pos = this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY }); + const pos = this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY }); pos && (this._nodeClicked = this._editorView!.state.doc.nodeAt(pos.pos)); if (this.props.onClick && e.button === 0) { e.preventDefault(); @@ -919,24 +949,30 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & } // this hackiness handles clicking on the list item bullets to do expand/collapse. the bullets are ::before pseudo elements so there's no real way to hit test against them. - hitBulletTargets(x: number, y: number, offsetX: number, select: boolean = false) { + hitBulletTargets(x: number, y: number, offsetX: number, select: boolean, highlightOnly = false) { clearStyleSheetRules(FormattedTextBox._bulletStyleSheet); if (this.props.isSelected(true) && offsetX < 40) { - let pos = this._editorView!.posAtCoords({ left: x, top: y }); + const pos = this._editorView!.posAtCoords({ left: x, top: y }); if (pos && pos.pos > 0) { - let node = this._editorView!.state.doc.nodeAt(pos.pos); - let node2 = node && node.type === schema.nodes.paragraph ? this._editorView!.state.doc.nodeAt(pos.pos - 1) : undefined; - if (node === this._nodeClicked && node2 && (node2.type === schema.nodes.ordered_list || node2.type === schema.nodes.list_item)) { - let hit = this._editorView!.domAtPos(pos.pos).node as any; // let beforeEle = document.querySelector("." + hit.className) as Element; - let before = hit ? window.getComputedStyle(hit, ':before') : undefined; - let beforeWidth = before ? Number(before.getPropertyValue('width').replace("px", "")) : undefined; - if (beforeWidth && offsetX < beforeWidth) { - let ol = this._editorView!.state.doc.nodeAt(pos.pos - 2) ? this._editorView!.state.doc.nodeAt(pos.pos - 2) : undefined; - if (ol && ol.type === schema.nodes.ordered_list && select) { - this._editorView!.dispatch(this._editorView!.state.tr.setSelection(new NodeSelection(this._editorView!.state.doc.resolve(pos.pos - 2)))); - addStyleSheetRule(FormattedTextBox._bulletStyleSheet, hit.className + ":before", { background: "gray" }); + const node = this._editorView!.state.doc.nodeAt(pos.pos); + const node2 = node?.type === schema.nodes.paragraph ? this._editorView!.state.doc.nodeAt(pos.pos - 1) : undefined; + if ((node === this._nodeClicked || highlightOnly) && (node2?.type === schema.nodes.ordered_list || node2?.type === schema.nodes.list_item)) { + const hit = this._editorView!.domAtPos(pos.pos).node as any; // let beforeEle = document.querySelector("." + hit.className) as Element; + const before = hit ? window.getComputedStyle(hit, ':before') : undefined; + const beforeWidth = before ? Number(before.getPropertyValue('width').replace("px", "")) : undefined; + if (beforeWidth && offsetX < beforeWidth * .9) { + const ol = this._editorView!.state.doc.nodeAt(pos.pos - 2) ? this._editorView!.state.doc.nodeAt(pos.pos - 2) : undefined; + if (ol?.type === schema.nodes.ordered_list && select) { + if (!highlightOnly) { + this._editorView!.dispatch(this._editorView!.state.tr.setSelection(new NodeSelection(this._editorView!.state.doc.resolve(pos.pos - 2)))); + } + addStyleSheetRule(FormattedTextBox._bulletStyleSheet, hit.className + ":before", { background: "lightgray" }); } else { - this._editorView!.dispatch(this._editorView!.state.tr.setNodeMarkup(pos.pos - 1, node2.type, { ...node2.attrs, visibility: !node2.attrs.visibility })); + if (highlightOnly) { + addStyleSheetRule(FormattedTextBox._bulletStyleSheet, hit.className + ":before", { background: "lightgray" }); + } else { + this._editorView!.dispatch(this._editorView!.state.tr.setNodeMarkup(pos.pos - 1, node2.type, { ...node2.attrs, visibility: !node2.attrs.visibility })); + } } } } @@ -946,11 +982,11 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & onMouseUp = (e: React.MouseEvent): void => { e.stopPropagation(); - let view = this._editorView as any; + const view = this._editorView as any; // this interposes on prosemirror's upHandler to prevent prosemirror's up from invoked multiple times when there // are nested prosemirrors. We only want the lowest level prosemirror to be invoked. if (view.mouseDown) { - let originalUpHandler = view.mouseDown.up; + const originalUpHandler = view.mouseDown.up; view.root.removeEventListener("mouseup", originalUpHandler); view.mouseDown.up = (e: MouseEvent) => { !(e as any).formattedHandled && originalUpHandler(e); @@ -962,7 +998,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & } tooltipTextMenuPlugin() { - let self = FormattedTextBox; + const self = FormattedTextBox; return new Plugin({ view(newView) { return self.ToolTipTextMenu = FormattedTextBox.getToolTip(newView); @@ -971,7 +1007,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & } tooltipLinkingMenuPlugin() { - let myprops = this.props; + const myprops = this.props; return new Plugin({ view(_editorView) { return new TooltipLinkingMenu(_editorView, myprops); @@ -994,7 +1030,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & if (e.key === "Tab" || e.key === "Enter") { e.preventDefault(); } - let mark = schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.round(Date.now() / 1000 / 5) }); + const mark = schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.round(Date.now() / 1000 / 5) }); this._editorView!.dispatch(this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark.create({})).addStoredMark(mark)); if (!this._undoTyping) { @@ -1011,8 +1047,8 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & const scrollHeight = this._ref.current?.scrollHeight; if (!this.layoutDoc.animateToPos && 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 - let nh = this.Document.isTemplateField ? 0 : NumCast(this.dataDoc.nativeHeight, 0); - let dh = NumCast(this.layoutDoc.height, 0); + const nh = this.Document.isTemplateField ? 0 : NumCast(this.dataDoc.nativeHeight, 0); + const dh = NumCast(this.layoutDoc.height, 0); this.layoutDoc.height = Math.max(10, (nh ? dh / nh * scrollHeight : scrollHeight) + (this.props.ChromeHeight ? this.props.ChromeHeight() : 0)); this.dataDoc.nativeHeight = nh ? scrollHeight : undefined; } @@ -1023,8 +1059,8 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & @computed get annotationsKey() { return "annotations"; } render() { TraceMobx(); - let rounded = StrCast(this.layoutDoc.borderRounding) === "100%" ? "-rounded" : ""; - let interactive = InkingControl.Instance.selectedTool || this.layoutDoc.isBackground; + const rounded = StrCast(this.layoutDoc.borderRounding) === "100%" ? "-rounded" : ""; + const interactive = InkingControl.Instance.selectedTool || this.layoutDoc.isBackground; if (this.props.isSelected()) { FormattedTextBox.ToolTipTextMenu!.updateFromDash(this._editorView!, undefined, this.props); } else if (FormattedTextBoxComment.textBox === this) { @@ -1045,10 +1081,12 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & onKeyDown={this.onKeyPress} onFocus={this.onFocused} onClick={this.onClick} + onPointerMove={e => this.hitBulletTargets(e.clientX, e.clientY, e.nativeEvent.offsetX, e.shiftKey, true)} onBlur={this.onBlur} onPointerUp={this.onPointerUp} onPointerDown={this.onPointerDown} onMouseUp={this.onMouseUp} + onTouchStart={this.onTouchStart} onWheel={this.onPointerWheel} onPointerEnter={action(() => this._entered = true)} onPointerLeave={action(() => this._entered = false)} @@ -1056,15 +1094,15 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & <div className={`formattedTextBox-outer`} style={{ width: `calc(100% - ${this.sidebarWidthPercent})`, }}> <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.sidebarWidthPercent === "0%" ? - <div className="formattedTextBox-sidebar-handle" onPointerDown={e => e.stopPropagation()} onClick={e => this.toggleSidebar()} /> : + {this.props.Document.hideSidebar ? (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")}` }}> <CollectionFreeFormView {...this.props} - PanelHeight={() => this.props.PanelHeight()} + PanelHeight={this.props.PanelHeight} PanelWidth={() => this.sidebarWidth} annotationsKey={this.annotationsKey} - isAnnotationOverlay={true} + isAnnotationOverlay={false} focus={this.props.focus} isSelected={this.props.isSelected} select={emptyFunction} @@ -1073,7 +1111,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & whenActiveChanged={this.whenActiveChanged} removeDocument={this.removeDocument} moveDocument={this.moveDocument} - addDocument={this.addDocument} + addDocument={(doc: Doc) => { doc.hideSidebar = true; return this.addDocument(doc); }} CollectionView={undefined} ScreenToLocalTransform={() => this.props.ScreenToLocalTransform().translate(-(this.props.PanelWidth() - this.sidebarWidth), 0)} ruleProvider={undefined} @@ -1081,7 +1119,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & ContainingCollectionDoc={this.props.ContainingCollectionDoc} chromeCollapsed={true}> </CollectionFreeFormView> - <div className="formattedTextBox-sidebar-handle" onPointerDown={e => e.stopPropagation()} onClick={e => this.toggleSidebar()} /> + <div className="formattedTextBox-sidebar-handle" onPointerDown={this.sidebarDown} onClick={e => this.toggleSidebar()} /> </div>} <div className="formattedTextBox-dictation" onClick={e => { @@ -1090,7 +1128,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & e.stopPropagation(); }} > <FontAwesomeIcon className="formattedTExtBox-audioFont" - style={{ color: this._recording ? "red" : "blue", opacity: this._recording ? 1 : 0.2 }} icon={"microphone"} size="sm" /> + style={{ color: this._recording ? "red" : "blue", opacity: this._recording ? 1 : 0.5 }} icon={"microphone"} size="sm" /> </div> </div> ); diff --git a/src/client/views/nodes/FormattedTextBoxComment.tsx b/src/client/views/nodes/FormattedTextBoxComment.tsx index c076fd34a..2ec30e3b3 100644 --- a/src/client/views/nodes/FormattedTextBoxComment.tsx +++ b/src/client/views/nodes/FormattedTextBoxComment.tsx @@ -81,7 +81,7 @@ export class FormattedTextBoxComment { FormattedTextBoxComment.tooltip.style.display = "none"; FormattedTextBoxComment.tooltip.appendChild(FormattedTextBoxComment.tooltipInput); FormattedTextBoxComment.tooltip.onpointerdown = (e: PointerEvent) => { - let keep = e.target && (e.target as any).type === "checkbox" ? true : false; + const keep = e.target && (e.target as any).type === "checkbox" ? true : false; const textBox = FormattedTextBoxComment.textBox; if (FormattedTextBoxComment.linkDoc && !keep && textBox) { DocumentManager.Instance.FollowLink(FormattedTextBoxComment.linkDoc, textBox.props.Document, @@ -113,7 +113,7 @@ export class FormattedTextBoxComment { } static update(view: EditorView, lastState?: EditorState) { - let state = view.state; + const state = view.state; // Don't do anything if the document/selection didn't change if (lastState && lastState.doc.eq(state.doc) && lastState.selection.eq(state.selection)) { @@ -136,11 +136,11 @@ export class FormattedTextBoxComment { // this section checks to see if the insertion point is over text entered by a different user. If so, it sets ths comment text to indicate the user and the modification date if (state.selection.$from) { nbef = findStartOfMark(state.selection.$from, view, findOtherUserMark); - let naft = findEndOfMark(state.selection.$from, view, findOtherUserMark); - let noselection = view.state.selection.$from === view.state.selection.$to; + const naft = findEndOfMark(state.selection.$from, view, findOtherUserMark); + const noselection = view.state.selection.$from === view.state.selection.$to; let child: any = null; state.doc.nodesBetween(state.selection.from, state.selection.to, (node: any, pos: number, parent: any) => !child && node.marks.length && (child = node)); - let mark = child && findOtherUserMark(child.marks); + const mark = child && findOtherUserMark(child.marks); if (mark && child && (nbef || naft) && (!mark.attrs.opened || noselection)) { FormattedTextBoxComment.SetState(FormattedTextBoxComment.textBox, mark.attrs.opened, state.selection.$from.pos - nbef, state.selection.$from.pos + naft, mark); } @@ -153,10 +153,10 @@ export class FormattedTextBoxComment { // this checks if the selection is a hyperlink. If so, it displays the target doc's text for internal links, and the url of the target for external links. if (set === "none" && state.selection.$from) { nbef = findStartOfMark(state.selection.$from, view, findLinkMark); - let naft = findEndOfMark(state.selection.$from, view, findLinkMark); + const naft = findEndOfMark(state.selection.$from, view, findLinkMark); let child: any = null; state.doc.nodesBetween(state.selection.from, state.selection.to, (node: any, pos: number, parent: any) => !child && node.marks.length && (child = node)); - let mark = child && findLinkMark(child.marks); + const mark = child && findLinkMark(child.marks); if (mark && child && nbef && naft) { FormattedTextBoxComment.tooltipText.textContent = "external => " + mark.attrs.href; if (mark.attrs.href.startsWith("https://en.wikipedia.org/wiki/")) { @@ -167,7 +167,7 @@ export class FormattedTextBoxComment { } (FormattedTextBoxComment.tooltipText as any).href = mark.attrs.href; if (mark.attrs.href.indexOf(Utils.prepend("/doc/")) === 0) { - let docTarget = mark.attrs.href.replace(Utils.prepend("/doc/"), "").split("?")[0]; + const docTarget = mark.attrs.href.replace(Utils.prepend("/doc/"), "").split("?")[0]; docTarget && DocServer.GetRefField(docTarget).then(linkDoc => { if (linkDoc instanceof Doc) { FormattedTextBoxComment.linkDoc = linkDoc; @@ -210,12 +210,12 @@ export class FormattedTextBoxComment { if (set !== "none") { // These are in screen coordinates // let start = view.coordsAtPos(state.selection.from), end = view.coordsAtPos(state.selection.to); - let start = view.coordsAtPos(state.selection.from - nbef), end = view.coordsAtPos(state.selection.from - nbef); + const start = view.coordsAtPos(state.selection.from - nbef), end = view.coordsAtPos(state.selection.from - nbef); // The box in which the tooltip is positioned, to use as base - let box = (document.getElementById("mainView-container") as any).getBoundingClientRect(); + const box = (document.getElementById("mainView-container") as any).getBoundingClientRect(); // Find a center-ish x position from the selection endpoints (when // crossing lines, end may be more to the left) - let left = Math.max((start.left + end.left) / 2, start.left + 3); + const left = Math.max((start.left + end.left) / 2, start.left + 3); FormattedTextBoxComment.tooltip.style.left = (left - box.left) + "px"; FormattedTextBoxComment.tooltip.style.bottom = (box.bottom - start.top) + "px"; } diff --git a/src/client/views/nodes/IconBox.tsx b/src/client/views/nodes/IconBox.tsx index 60f547b1e..9462ff024 100644 --- a/src/client/views/nodes/IconBox.tsx +++ b/src/client/views/nodes/IconBox.tsx @@ -51,7 +51,7 @@ export class IconBox extends React.Component<FieldViewProps> { } public static DocumentIcon(layout: string) { - let button = layout.indexOf("PDFBox") !== -1 ? faFilePdf : + const button = layout.indexOf("PDFBox") !== -1 ? faFilePdf : layout.indexOf("ImageBox") !== -1 ? faImage : layout.indexOf("Formatted") !== -1 ? faStickyNote : layout.indexOf("Video") !== -1 ? faFilm : @@ -65,14 +65,14 @@ export class IconBox extends React.Component<FieldViewProps> { } specificContextMenu = (): void => { - let cm = ContextMenu.Instance; + const cm = ContextMenu.Instance; cm.addItem({ description: this.props.Document.hideLabel ? "Show label with icon" : "Remove label from icon", event: this.setLabelField, icon: "tag" }); if (!this.props.Document.hideLabel) { cm.addItem({ description: "Use Target Title", event: () => IconBox.AutomaticTitle(this.props.Document), icon: "text-height" }); } } render() { - let label = this.props.Document.hideLabel ? "" : this.props.Document.title; + const label = this.props.Document.hideLabel ? "" : this.props.Document.title; return ( <div className="iconBox-container" onContextMenu={this.specificContextMenu}> {this.minimizedIcon} diff --git a/src/client/views/nodes/ImageBox.scss b/src/client/views/nodes/ImageBox.scss index ba4ef8879..3b42c2352 100644 --- a/src/client/views/nodes/ImageBox.scss +++ b/src/client/views/nodes/ImageBox.scss @@ -1,4 +1,13 @@ -.imageBox-cont, .imageBox-cont-interactive { +.imageBox { + pointer-events: all; + border-radius: inherit; + width:100%; + height:100%; + position: absolute; + transform-origin: top left; +} + +.imageBox-cont, .imageBox-cont-dragging { padding: 0vw; position: absolute; text-align: center; @@ -8,19 +17,20 @@ max-height: 100%; pointer-events: none; background:transparent; + img { + height: auto; + width: 100%; + pointer-events: all; + } + .imageBox-fader { + pointer-events: all; + } } -.imageBox-container { - pointer-events: all; - border-radius: inherit; - width:100%; - height:100%; - position: absolute; - transform-origin: top left; -} - -.imageBox-cont-interactive { - pointer-events: all; +.imageBox-cont-dragging { + .imageBox-fader { + pointer-events: none; + } } .imageBox-dot { @@ -33,15 +43,6 @@ background: gray; } -.imageBox-cont img { - height: auto; - width: 100%; -} - -.imageBox-cont-interactive img { - height: auto; - width: 100%; -} #google-photos { transition: all 0.5s ease 0s; @@ -100,18 +101,18 @@ } } -#cf { +.imageBox-fader { position: relative; width: 100%; margin: 0 auto; display: flex; - align-items: center; height: 100%; overflow: hidden; .imageBox-fadeBlocker { width: 100%; height: 100%; + position: absolute; background: black; display: flex; flex-direction: row; @@ -126,7 +127,7 @@ } } -#cf img { +.imageBox-fader img { position: absolute; left: 0; } @@ -138,10 +139,12 @@ transition: opacity 1s ease-in-out; } -.imageBox-fadeBlocker:hover { - -webkit-transition: opacity 1s ease-in-out; - -moz-transition: opacity 1s ease-in-out; - -o-transition: opacity 1s ease-in-out; - transition: opacity 1s ease-in-out; - opacity: 0; +.imageBox:hover { + .imageBox-fadeBlocker { + -webkit-transition: opacity 1s ease-in-out; + -moz-transition: opacity 1s ease-in-out; + -o-transition: opacity 1s ease-in-out; + transition: opacity 1s ease-in-out; + opacity: 0; + } }
\ No newline at end of file diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index f21ce3bf2..b4a51657f 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -8,10 +8,9 @@ import { Doc, DocListCast, HeightSym, WidthSym } 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 { BoolCast, Cast, FieldValue, NumCast, StrCast } from '../../../new_fields/Types'; +import { Cast, NumCast } from '../../../new_fields/Types'; import { AudioField, ImageField } from '../../../new_fields/URLField'; -import { RouteStore } from '../../../server/RouteStore'; -import { Utils, returnOne, emptyFunction, OmitKeys } from '../../../Utils'; +import { Utils, returnOne, emptyFunction } from '../../../Utils'; import { CognitiveServices, Confidence, Service, Tag } from '../../cognitive_services/CognitiveServices'; import { Docs } from '../../documents/Documents'; import { DragManager } from '../../util/DragManager'; @@ -28,8 +27,9 @@ import { CollectionFreeFormView } from '../collections/collectionFreeForm/Collec import { documentSchema } from '../../../new_fields/documentSchemas'; import { Id } from '../../../new_fields/FieldSymbols'; import { TraceMobx } from '../../../new_fields/util'; -var requestImageSize = require('../../util/request-image-size'); -var path = require('path'); +import { SelectionManager } from '../../util/SelectionManager'; +const requestImageSize = require('../../util/request-image-size'); +const path = require('path'); const { Howl } = require('howler'); @@ -88,7 +88,7 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum recordAudioAnnotation = () => { let gumStream: any; let recorder: any; - let self = this; + const self = this; const extensionDoc = this.extensionDoc; extensionDoc && navigator.mediaDevices.getUserMedia({ audio: true @@ -98,16 +98,16 @@ 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(RouteStore.upload), { + const res = await fetch(Utils.prepend("/upload"), { method: 'POST', body: formData }); const files = await res.json(); const url = Utils.prepend(files[0].path); // upload to server with known URL - let 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"; - let audioAnnos = Cast(extensionDoc.audioAnnotations, listSpec(Doc)); + const audioAnnos = Cast(extensionDoc.audioAnnotations, listSpec(Doc)); if (audioAnnos === undefined) { extensionDoc.audioAnnotations = new List([audioDoc]); } else { @@ -126,10 +126,10 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum @undoBatch rotate = action(() => { - let nw = this.Document.nativeWidth; - let nh = this.Document.nativeHeight; - let w = this.Document.width; - let h = this.Document.height; + 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; @@ -140,12 +140,12 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum specificContextMenu = (e: React.MouseEvent): void => { const field = Cast(this.Document[this.props.fieldKey], ImageField); if (field) { - let funcs: ContextMenuProps[] = []; + const funcs: ContextMenuProps[] = []; funcs.push({ description: "Copy path", event: () => Utils.CopyText(field.url.href), icon: "expand-arrows-alt" }); funcs.push({ description: "Rotate", event: this.rotate, icon: "expand-arrows-alt" }); - let existingAnalyze = ContextMenu.Instance.findByDescription("Analyzers..."); - let modes: ContextMenuProps[] = existingAnalyze && "subitems" in existingAnalyze ? existingAnalyze.subitems : []; + const existingAnalyze = ContextMenu.Instance.findByDescription("Analyzers..."); + const modes: ContextMenuProps[] = existingAnalyze && "subitems" in existingAnalyze ? existingAnalyze.subitems : []; modes.push({ description: "Generate Tags", event: this.generateMetadata, icon: "tag" }); modes.push({ description: "Find Faces", event: this.extractFaces, icon: "camera" }); !existingAnalyze && ContextMenu.Instance.addItem({ description: "Analyzers...", subitems: modes, icon: "hand-point-right" }); @@ -155,8 +155,8 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum } extractFaces = () => { - let converter = (results: any) => { - let faceDocs = new List<Doc>(); + const converter = (results: any) => { + const faceDocs = new List<Doc>(); results.reduce((face: CognitiveServices.Image.Face, faceDocs: List<Doc>) => faceDocs.push(Docs.Get.DocumentHierarchyFromJson(face, `Face: ${face.faceId}`)!), new List<Doc>()); return faceDocs; }; @@ -164,12 +164,12 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum } generateMetadata = (threshold: Confidence = Confidence.Excellent) => { - let converter = (results: any) => { - let tagDoc = new Doc; - let tagsList = new List(); + const converter = (results: any) => { + const tagDoc = new Doc; + const tagsList = new List(); results.tags.map((tag: Tag) => { tagsList.push(tag.name); - let sanitized = tag.name.replace(" ", "_"); + const sanitized = tag.name.replace(" ", "_"); tagDoc[sanitized] = ComputedField.MakeFunction(`(${tag.confidence} >= this.confidence) ? ${tag.confidence} : "${ComputedField.undefined}"`); }); this.extensionDoc && (this.extensionDoc.generatedTags = tagsList); @@ -181,7 +181,7 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum } @computed private get url() { - let data = Cast(this.dataDoc[this.props.fieldKey], ImageField); + const data = Cast(this.dataDoc[this.props.fieldKey], ImageField); return data ? data.url.href : undefined; } @@ -194,7 +194,7 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum } else if (!(lower.endsWith(".png") || lower.endsWith(".jpg") || lower.endsWith(".jpeg"))) { return url.href;//Why is this here } - let ext = path.extname(url.href); + const ext = path.extname(url.href); const suffix = this.props.renderDepth < 1 ? "_o" : this._curSuffix; return url.href.replace(ext, suffix + ext); } @@ -208,7 +208,7 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum if (this._curSuffix === "_l") this._largeRetryCount++; } @action onError = () => { - let timeout = this._curSuffix === "_s" ? this._smallRetryCount : this._curSuffix === "_m" ? this._mediumRetryCount : this._largeRetryCount; + 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)); } @@ -218,9 +218,25 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum resize = (srcpath: string) => { requestImageSize(srcpath) .then((size: any) => { - let rotation = NumCast(this.dataDoc.rotation) % 180; - let realsize = rotation === 90 || rotation === 270 ? { height: size.width, width: size.height } : size; - let aspect = realsize.height / realsize.width; + 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(() => { + this.Document.height = this.Document[WidthSym]() * aspect; + this.Document.nativeHeight = realsize.height; + this.Document.nativeWidth = realsize.width; + }), 0); + } + }) + .catch((err: any) => console.log(err)); + } + fadesize = (srcpath: string) => { + requestImageSize(srcpath) + .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(() => { this.Document.height = this.Document[WidthSym]() * aspect; @@ -234,10 +250,10 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum @action onPointerEnter = () => { - let self = this; - let audioAnnos = this.extensionDoc && DocListCast(this.extensionDoc.audioAnnotations); + const self = this; + const audioAnnos = this.extensionDoc && DocListCast(this.extensionDoc.audioAnnotations); if (audioAnnos && audioAnnos.length && this._audioState === 0) { - let anno = audioAnnos[Math.floor(Math.random() * audioAnnos.length)]; + const anno = audioAnnos[Math.floor(Math.random() * audioAnnos.length)]; anno.data instanceof AudioField && new Howl({ src: [anno.data.url.href], format: ["mp3"], @@ -273,69 +289,73 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum const extensionDoc = this.extensionDoc; if (!extensionDoc) return (null); // let transform = this.props.ScreenToLocalTransform().inverse(); - let pw = typeof this.props.PanelWidth === "function" ? this.props.PanelWidth() : typeof this.props.PanelWidth === "number" ? (this.props.PanelWidth as any) as number : 50; + const pw = typeof this.props.PanelWidth === "function" ? this.props.PanelWidth() : typeof this.props.PanelWidth === "number" ? (this.props.PanelWidth as any) as number : 50; // var [sptX, sptY] = transform.transformPoint(0, 0); // let [bptX, bptY] = transform.transformPoint(pw, this.props.PanelHeight()); // let w = bptX - sptX; - let nativeWidth = (this.Document.nativeWidth || pw); - let nativeHeight = (this.Document.nativeHeight || 0); - let paths = [Utils.CorsProxy("http://www.cs.brown.edu/~bcz/noImage.png")]; + const nativeWidth = (this.Document.nativeWidth || pw); + const nativeHeight = (this.Document.nativeHeight || 1); + let paths = [[Utils.CorsProxy("http://www.cs.brown.edu/~bcz/noImage.png"), nativeWidth / nativeHeight]]; // this._curSuffix = ""; // if (w > 20) { - let alts = DocListCast(extensionDoc.Alternates); - let altpaths = alts.filter(doc => doc.data instanceof ImageField).map(doc => this.choosePath((doc.data as ImageField).url)); - let field = this.dataDoc[this.props.fieldKey]; + 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 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)]; + if (field instanceof ImageField) paths = [[this.choosePath(field.url), nativeWidth / nativeHeight]]; paths.push(...altpaths); // } - let interactive = InkingControl.Instance.selectedTool || !this.props.isSelected() ? "" : "-interactive"; - let rotation = NumCast(this.Document.rotation, 0); - let aspect = (rotation % 180) ? this.Document[HeightSym]() / this.Document[WidthSym]() : 1; - let shift = (rotation % 180) ? (nativeHeight - nativeWidth / aspect) / 2 : 0; - let srcpath = paths[Math.min(paths.length - 1, (this.Document.curPage || 0))]; - let fadepath = paths[Math.min(paths.length - 1, 1)]; + const dragging = !SelectionManager.GetIsDragging() ? "" : "-dragging"; + const rotation = NumCast(this.Document.rotation, 0); + const aspect = (rotation % 180) ? this.Document[HeightSym]() / this.Document[WidthSym]() : 1; + const shift = (rotation % 180) ? (nativeHeight - nativeWidth / aspect) / 2 : 0; + 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; !this.Document.ignoreAspect && this.resize(srcpath); - return ( - <div className={`imageBox-cont${interactive}`} key={this.props.Document[Id]} ref={this.createDropTarget} onContextMenu={this.specificContextMenu}> - <div id="cf"> - <img - key={this._smallRetryCount + (this._mediumRetryCount << 4) + (this._largeRetryCount << 8)} // force cache to update on retrys - src={srcpath} - style={{ transform: `translate(0px, ${shift}px) rotate(${rotation}deg) scale(${aspect})` }} - width={nativeWidth} - ref={this._imgRef} - onError={this.onError} /> - {fadepath === srcpath ? (null) : <div className="imageBox-fadeBlocker"> <img className="imageBox-fadeaway" + return <div className={`imageBox-cont${dragging}`} key={this.props.Document[Id]} ref={this.createDropTarget} onContextMenu={this.specificContextMenu}> + <div className="imageBox-fader" > + <img key={this._smallRetryCount + (this._mediumRetryCount << 4) + (this._largeRetryCount << 8)} // force cache to update on retrys + src={srcpath} + style={{ transform: `translate(0px, ${shift}px) rotate(${rotation}deg) scale(${aspect})` }} + width={nativeWidth} + ref={this._imgRef} + onError={this.onError} /> + {fadepath === srcpath ? (null) : <div className="imageBox-fadeBlocker" style={{ width: nativeWidth, height: nativeWidth / srcaspect }}> + <img className="imageBox-fadeaway" key={"fadeaway" + this._smallRetryCount + (this._mediumRetryCount << 4) + (this._largeRetryCount << 8)} // force cache to update on retrys src={fadepath} - style={{ transform: `translate(0px, ${shift}px) rotate(${rotation}deg) scale(${aspect})` }} + style={{ transform: `translate(0px, ${shift}px) rotate(${rotation}deg) scale(${aspect})`, }} width={nativeWidth} ref={this._imgRef} onError={this.onError} /></div>} - </div> - <div className="imageBox-audioBackground" - onPointerDown={this.audioDown} - onPointerEnter={this.onPointerEnter} - 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" /> - </div> - {this.considerGooglePhotosLink()} - <FaceRectangles document={extensionDoc} color={"#0000FF"} backgroundColor={"#0000FF"} /> - </div>); + </div> + <div className="imageBox-audioBackground" + onPointerDown={this.audioDown} + onPointerEnter={this.onPointerEnter} + 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" /> + </div> + {this.considerGooglePhotosLink()} + <FaceRectangles document={extensionDoc} color={"#0000FF"} backgroundColor={"#0000FF"} /> + </div>; } contentFunc = () => [this.content]; render() { - return (<div className={"imageBox-container"} onContextMenu={this.specificContextMenu} - style={{ transform: `scale(${this.props.ContentScaling()})`, width: `${100 / this.props.ContentScaling()}%`, height: `${100 / this.props.ContentScaling()}%` }} > + return (<div className="imageBox" onContextMenu={this.specificContextMenu} + style={{ + transform: `scale(${this.props.ContentScaling()})`, + width: `${100 / this.props.ContentScaling()}%`, + height: `${100 / this.props.ContentScaling()}%` + }} > <CollectionFreeFormView {...this.props} PanelHeight={this.props.PanelHeight} PanelWidth={this.props.PanelWidth} diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx index aa6e135fe..fba29e4cd 100644 --- a/src/client/views/nodes/KeyValueBox.tsx +++ b/src/client/views/nodes/KeyValueBox.tsx @@ -53,13 +53,13 @@ export class KeyValueBox extends React.Component<FieldViewProps> { } } public static CompileKVPScript(value: string): KVPScript | undefined { - let eq = value.startsWith("="); + const eq = value.startsWith("="); value = eq ? value.substr(1) : value; const dubEq = value.startsWith(":=") ? "computed" : value.startsWith(";=") ? "script" : false; value = dubEq ? value.substr(2) : value; - let options: ScriptOptions = { addReturn: true, params: { this: "Doc" } }; + const options: ScriptOptions = { addReturn: true, params: { this: "Doc" } }; if (dubEq) options.typecheck = false; - let script = CompileScript(value, options); + const script = CompileScript(value, options); if (!script.compiled) { return undefined; } @@ -76,7 +76,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> { } else if (type === "script") { field = new ScriptField(script); } else { - let res = script.run({ this: target }, console.log); + const res = script.run({ this: target }, console.log); if (!res.success) return false; field = res.result; } @@ -106,14 +106,14 @@ export class KeyValueBox extends React.Component<FieldViewProps> { rowHeight = () => 30; createTable = () => { - let doc = this.fieldDocToLayout; + const doc = this.fieldDocToLayout; if (!doc) { return <tr><td>Loading...</td></tr>; } - let realDoc = doc; + const realDoc = doc; - let ids: { [key: string]: string } = {}; - let protos = Doc.GetAllPrototypes(doc); + const ids: { [key: string]: string } = {}; + const protos = Doc.GetAllPrototypes(doc); for (const proto of protos) { Object.keys(proto).forEach(key => { if (!(key in ids) && realDoc[key] !== ComputedField.undefined) { @@ -122,10 +122,10 @@ export class KeyValueBox extends React.Component<FieldViewProps> { }); } - let rows: JSX.Element[] = []; + const rows: JSX.Element[] = []; let i = 0; const self = this; - for (let key of Object.keys(ids).slice().sort()) { + for (const key of Object.keys(ids).slice().sort()) { rows.push(<KeyValuePair doc={realDoc} addDocTab={this.props.addDocTab} PanelWidth={this.props.PanelWidth} PanelHeight={this.rowHeight} ref={(function () { let oldEl: KeyValuePair | undefined; @@ -163,7 +163,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> { @action onDividerMove = (e: PointerEvent): void => { - let nativeWidth = this._mainCont.current!.getBoundingClientRect(); + const nativeWidth = this._mainCont.current!.getBoundingClientRect(); this.props.Document.schemaSplitPercentage = Math.max(0, 100 - Math.round((e.clientX - nativeWidth.left) / nativeWidth.width * 100)); } @action @@ -179,10 +179,10 @@ export class KeyValueBox extends React.Component<FieldViewProps> { } getTemplate = async () => { - let 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 (let row of this.rows.filter(row => row.isChecked)) { + for (const row of this.rows.filter(row => row.isChecked)) { await this.createTemplateField(parent, row); row.uncheck(); } @@ -190,17 +190,17 @@ export class KeyValueBox extends React.Component<FieldViewProps> { } createTemplateField = async (parentStackingDoc: Doc, row: KeyValuePair) => { - let metaKey = row.props.keyName; - let sourceDoc = await Cast(this.props.Document.data, Doc); + const metaKey = row.props.keyName; + const sourceDoc = await Cast(this.props.Document.data, Doc); if (!sourceDoc) { return; } - let fieldTemplate = await this.inferType(sourceDoc[metaKey], metaKey); + const fieldTemplate = await this.inferType(sourceDoc[metaKey], metaKey); if (!fieldTemplate) { return; } - let previousViewType = fieldTemplate.viewType; + const previousViewType = fieldTemplate.viewType; Doc.MakeMetadataFieldTemplate(fieldTemplate, Doc.GetProto(parentStackingDoc)); previousViewType && (fieldTemplate.viewType = previousViewType); @@ -208,14 +208,14 @@ export class KeyValueBox extends React.Component<FieldViewProps> { } inferType = async (data: FieldResult, metaKey: string) => { - let 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); } else if (data instanceof List) { if (data.length === 0) { return Docs.Create.StackingDocument([], options); } - let first = await Cast(data[0], Doc); + const first = await Cast(data[0], Doc); if (!first || !first.data) { return Docs.Create.StackingDocument([], options); } @@ -235,7 +235,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> { } render() { - let dividerDragger = this.splitPercentage === 0 ? (null) : + const dividerDragger = this.splitPercentage === 0 ? (null) : <div className="keyValueBox-dividerDragger" style={{ transform: `translate(calc(${100 - this.splitPercentage}% - 5px), 0px)` }}> <div className="keyValueBox-dividerDraggerThumb" onPointerDown={this.onDividerDown} /> </div>; diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx index 225565964..bca68cdd9 100644 --- a/src/client/views/nodes/KeyValuePair.tsx +++ b/src/client/views/nodes/KeyValuePair.tsx @@ -53,7 +53,7 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> { } render() { - let props: FieldViewProps = { + const props: FieldViewProps = { Document: this.props.doc, DataDoc: this.props.doc, ContainingCollectionView: undefined, @@ -73,7 +73,7 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> { pinToPres: returnZero, ContentScaling: returnOne }; - let contents = <FieldView {...props} />; + const contents = <FieldView {...props} />; // let fieldKey = Object.keys(props.Document).indexOf(props.fieldKey) !== -1 ? props.fieldKey : "(" + props.fieldKey + ")"; let protoCount = 0; let doc: Doc | undefined = props.Document; @@ -85,9 +85,9 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> { doc = doc.proto; } const parenCount = Math.max(0, protoCount - 1); - let keyStyle = protoCount === 0 ? "black" : "blue"; + const keyStyle = protoCount === 0 ? "black" : "blue"; - let hover = { transition: "0.3s ease opacity", opacity: this.isPointerOver || this.isChecked ? 1 : 0 }; + const hover = { transition: "0.3s ease opacity", opacity: this.isPointerOver || this.isChecked ? 1 : 0 }; return ( <tr className={this.props.rowStyle} onPointerEnter={action(() => this.isPointerOver = true)} onPointerLeave={action(() => this.isPointerOver = false)}> diff --git a/src/client/views/nodes/PDFBox.scss b/src/client/views/nodes/PDFBox.scss index 46af63a7d..c7d6f988c 100644 --- a/src/client/views/nodes/PDFBox.scss +++ b/src/client/views/nodes/PDFBox.scss @@ -1,35 +1,163 @@ -.pdfBox-ui { - position: absolute; - width: 100%; - height:100%; - z-index: 1; - pointer-events: none; -} - -.pdfBox-cont, -.pdfBox-cont-interactive { +.pdfBox, +.pdfBox-interactive { display: inline-block; - flex-direction: row; + position: absolute; height: 100%; - width:100%; + width: 100%; overflow: hidden; - position:absolute; cursor:auto; transform-origin: top left; z-index: 0; -} - -.pdfBox-title-outer { - z-index: 0; - position: absolute; - width: 100%; - height: 100%; - background: lightslategray; - .pdfBox-cont, .pdfBox-cont-interactive{ + .pdfBox-ui { + position: absolute; + width: 100%; + height: 100%; + z-index: 1; + pointer-events: none; + + .pdfBox-overlayButton { + border-bottom-left-radius: 50%; + display: flex; + justify-content: space-evenly; + align-items: center; + height: 20px; + background: none; + padding: 0; + position: absolute; + pointer-events: all; + + .pdfBox-overlayButton-arrow { + width: 0; + height: 0; + border-top: 10px solid transparent; + border-bottom: 10px solid transparent; + border-right: 15px solid #121721; + transition: all 0.5s; + } + + .pdfBox-overlayButton-iconCont { + background: #121721; + height: 20px; + width: 25px; + display: flex; + position: relative; + align-items: center; + justify-content: center; + border-radius: 3px; + pointer-events: all; + } + } + + .pdfBox-nextIcon, + .pdfBox-prevIcon { + background: #121721; + height: 20px; + width: 25px; + display: flex; + position: relative; + align-items: center; + justify-content: center; + border-radius: 3px; + pointer-events: all; + padding: 0px; + } + + .pdfBox-overlayButton:hover { + background: none; + } + + + .pdfBox-settingsCont { + position: absolute; + right: 0; + top: 3; + pointer-events: all; + + .pdfBox-settingsButton { + border-bottom-left-radius: 50%; + display: flex; + justify-content: space-evenly; + align-items: center; + height: 20px; + background: none; + padding: 0; + + .pdfBox-settingsButton-arrow { + width: 0; + height: 0; + border-top: 10px solid transparent; + border-bottom: 10px solid transparent; + border-right: 15px solid #121721; + transition: all 0.5s; + } + + .pdfBox-settingsButton-iconCont { + background: #121721; + height: 20px; + width: 25px; + display: flex; + justify-content: center; + align-items: center; + margin-left: -2px; + border-radius: 3px; + } + } + + .pdfBox-settingsButton:hover { + background: none; + } + + .pdfBox-settingsFlyout { + position: absolute; + background: #323232; + box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.25); + right: 20px; + border-radius: 7px; + padding: 20px; + display: flex; + flex-direction: column; + font-size: 14px; + transition: all 0.5s; + + .pdfBox-settingsFlyout-title { + color: white; + } + + .pdfBox-settingsFlyout-kvpInput { + margin-top: 20px; + display: grid; + grid-template-columns: 47.5% 5% 47.5%; + } + } + } + + .pdfBox-overlayCont { + position: absolute; + width: calc(100% - 40px); + height: 20px; + background: #121721; + bottom: 0; + display: flex; + justify-content: center; + align-items: center; + overflow: hidden; + transition: left .5s; + pointer-events: all; + + .pdfBox-searchBar { + width: 70%; + font-size: 14px; + } + } + } + .pdfBox-title-outer { width: 150%; height: 100%; position: relative; display: grid; + z-index: 0; + background: lightslategray; + transform-origin: top left; .pdfBox-title { color:lightgray; @@ -38,7 +166,7 @@ transform-origin: 42% 15%; width: 100%; transform: rotate(55deg); - font-size: 72; + font-size: 200; padding: 5%; overflow: hidden; display: inline-block; @@ -49,8 +177,7 @@ } } - -.pdfBox-cont { +.pdfBox { pointer-events: none; .collectionFreeFormView-none { pointer-events: none; @@ -64,7 +191,7 @@ } } -.pdfBox-cont-interactive { +.pdfBox-interactive { pointer-events: all; .pdfViewer-text { .textLayer { @@ -73,129 +200,4 @@ } } } -} - - -.pdfBox-settingsCont { - position: absolute; - right: 0; - top: 3; - pointer-events: all; - - .pdfBox-settingsButton { - border-bottom-left-radius: 50%; - display: flex; - justify-content: space-evenly; - align-items: center; - height: 30px; - background: none; - padding: 0; - - .pdfBox-settingsButton-arrow { - width: 0; - height: 0; - border-top: 15px solid transparent; - border-bottom: 15px solid transparent; - border-right: 15px solid #121721; - transition: all 0.5s; - } - - .pdfBox-settingsButton-iconCont { - background: #121721; - height: 30px; - width: 70px; - display: flex; - justify-content: center; - align-items: center; - margin-left: -2px; - border-radius: 3px; - } - } - - .pdfBox-settingsButton:hover { - background: none; - } - - .pdfBox-settingsFlyout { - position: absolute; - background: #323232; - box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.25); - right: 20px; - border-radius: 7px; - padding: 20px; - display: flex; - flex-direction: column; - font-size: 14px; - transition: all 0.5s; - - .pdfBox-settingsFlyout-title { - color: white; - } - - .pdfBox-settingsFlyout-kvpInput { - margin-top: 20px; - display: grid; - grid-template-columns: 47.5% 5% 47.5%; - } - } -} - -.pdfBox-overlayCont { - position: absolute; - width: calc(100% - 40px); - height: 30px; - background: #121721; - bottom: 0; - display: flex; - justify-content: center; - align-items: center; - overflow: hidden; - transition: left .5s; - pointer-events: all; - - .pdfBox-searchBar { - width: 70%; - font-size: 14px; - } -} - -.pdfBox-overlayButton { - border-bottom-left-radius: 50%; - display: flex; - justify-content: space-evenly; - align-items: center; - height: 30px; - background: none; - padding: 0; - position: absolute; - pointer-events: all; - - .pdfBox-overlayButton-arrow { - width: 0; - height: 0; - border-top: 15px solid transparent; - border-bottom: 15px solid transparent; - border-right: 15px solid #121721; - transition: all 0.5s; - } -} - -.pdfBox-overlayButton-iconCont, -.pdfBox-nextIcon, -.pdfBox-prevIcon { - padding: 0; - background: #121721; - height: 30px; - width: 25px; - display: inline-block; - position: relative; - justify-content: center; - align-items: center; - margin-left: -2px; - border-radius: 3px; - pointer-events: all; -} - -.pdfBox-overlayButton:hover { - background: none; -} +}
\ No newline at end of file diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 23512543a..0f5cc0fa9 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -6,7 +6,7 @@ import "pdfjs-dist/web/pdf_viewer.css"; import { Opt, WidthSym, Doc } from "../../../new_fields/Doc"; import { makeInterface } from "../../../new_fields/Schema"; import { ScriptField } from '../../../new_fields/ScriptField'; -import { Cast } from "../../../new_fields/Types"; +import { Cast, NumCast } from "../../../new_fields/Types"; import { PdfField } from "../../../new_fields/URLField"; import { Utils } from '../../../Utils'; import { KeyCodes } from '../../northstar/utils/KeyCodes'; @@ -95,7 +95,7 @@ export class PDFBox extends DocAnnotatableComponent<FieldViewProps, PdfDocument> @undoBatch @action private applyFilter = () => { - let scriptText = this._scriptValue ? this._scriptValue : + const scriptText = this._scriptValue ? this._scriptValue : this._keyValue && this._valueValue ? `this.${this._keyValue} === ${this._valueValue}` : "true"; this.props.Document.filterScript = ScriptField.MakeFunction(scriptText); } @@ -116,7 +116,7 @@ export class PDFBox extends DocAnnotatableComponent<FieldViewProps, PdfDocument> searchStringChanged = (e: React.ChangeEvent<HTMLInputElement>) => this._searchString = e.currentTarget.value; settingsPanel() { - let pageBtns = <> + const pageBtns = <> <button className="pdfBox-overlayButton-iconCont" key="back" title="Page Back" onPointerDown={e => e.stopPropagation()} onClick={e => this.backPage()} style={{ left: 45, top: 5 }}> <FontAwesomeIcon style={{ color: "white" }} icon={"arrow-left"} size="sm" /> @@ -135,27 +135,27 @@ export class PDFBox extends DocAnnotatableComponent<FieldViewProps, PdfDocument> <button title="Search" onClick={e => this.search(this._searchString, !e.shiftKey)}> <FontAwesomeIcon icon="search" size="sm" color="white" /></button> <button className="pdfBox-prevIcon " title="Previous Annotation" onClick={this.prevAnnotation} > - <FontAwesomeIcon style={{ color: "white" }} icon={"arrow-up"} size="sm" /> + <FontAwesomeIcon style={{ color: "white" }} icon={"arrow-up"} size="lg" /> </button> <button className="pdfBox-nextIcon" title="Next Annotation" onClick={this.nextAnnotation} > - <FontAwesomeIcon style={{ color: "white" }} icon={"arrow-down"} size="sm" /> + <FontAwesomeIcon style={{ color: "white" }} icon={"arrow-down"} size="lg" /> </button> </div> <button className="pdfBox-overlayButton" key="search" onClick={action(() => this._searching = !this._searching)} title="Open Search Bar" style={{ bottom: 0, right: 0 }}> <div className="pdfBox-overlayButton-arrow" onPointerDown={(e) => e.stopPropagation()}></div> <div className="pdfBox-overlayButton-iconCont" onPointerDown={(e) => e.stopPropagation()}> - <FontAwesomeIcon style={{ color: "white", padding: 5 }} icon={this._searching ? "times" : "search"} size="3x" /></div> + <FontAwesomeIcon style={{ color: "white" }} icon={this._searching ? "times" : "search"} size="lg" /></div> </button> <input value={`${(this.Document.curPage || 1)}`} onChange={e => this.gotoPage(Number(e.currentTarget.value))} - style={{ left: 5, top: 5, height: "30px", width: "30px", position: "absolute", pointerEvents: "all" }} + style={{ left: 5, top: 5, height: "20px", width: "20px", position: "absolute", pointerEvents: "all" }} onClick={action(() => this._pageControls = !this._pageControls)} /> {this._pageControls ? pageBtns : (null)} <div className="pdfBox-settingsCont" key="settings" onPointerDown={(e) => e.stopPropagation()}> <button className="pdfBox-settingsButton" onClick={action(() => this._flyout = !this._flyout)} title="Open Annotation Settings" > <div className="pdfBox-settingsButton-arrow" style={{ transform: `scaleX(${this._flyout ? -1 : 1})` }} /> <div className="pdfBox-settingsButton-iconCont"> - <FontAwesomeIcon style={{ color: "white", padding: 5 }} icon="cog" size="3x" /> + <FontAwesomeIcon style={{ color: "white" }} icon="cog" size="lg" /> </div> </button> <div className="pdfBox-settingsFlyout" style={{ right: `${this._flyout ? 20 : -600}px` }} > @@ -186,17 +186,22 @@ export class PDFBox extends DocAnnotatableComponent<FieldViewProps, PdfDocument> specificContextMenu = (e: React.MouseEvent): void => { const pdfUrl = Cast(this.dataDoc[this.props.fieldKey], PdfField); - let funcs: ContextMenuProps[] = []; + 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" }); ContextMenu.Instance.addItem({ description: "Pdf Funcs...", subitems: funcs, icon: "asterisk" }); } + @computed get contentScaling() { return this.props.ContentScaling(); } @computed get renderTitleBox() { - let classname = "pdfBox-cont" + (this.active() ? "-interactive" : ""); - return <div className="pdfBox-title-outer" > - <div className={classname} > + const classname = "pdfBox" + (this.active() ? "-interactive" : ""); + return <div className={classname} style={{ + width: !this.props.Document.fitWidth ? NumCast(this.props.Document.nativeWidth) : `${100 / this.contentScaling}%`, + height: !this.props.Document.fitWidth ? NumCast(this.props.Document.nativeHeight) : `${100 / this.contentScaling}%`, + transform: `scale(${this.props.ContentScaling()})` + }} > + <div className="pdfBox-title-outer"> <strong className="pdfBox-title" >{this.props.Document.title}</strong> </div> </div>; @@ -205,7 +210,7 @@ export class PDFBox extends DocAnnotatableComponent<FieldViewProps, PdfDocument> isChildActive = (outsideReaction?: boolean) => this._isChildActive; @computed get renderPdfView() { const pdfUrl = Cast(this.dataDoc[this.props.fieldKey], PdfField); - return <div className={"pdfBox-cont"} onContextMenu={this.specificContextMenu}> + return <div className={"pdfBox"} onContextMenu={this.specificContextMenu}> <PDFViewer {...this.props} pdf={this._pdf!} url={pdfUrl!.url.pathname} active={this.props.active} loaded={this.loaded} setPdfViewer={this.setPdfViewer} ContainingCollectionView={this.props.ContainingCollectionView} renderDepth={this.props.renderDepth} PanelHeight={this.props.PanelHeight} PanelWidth={this.props.PanelWidth} diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx index cbb83b511..1e6894f37 100644 --- a/src/client/views/nodes/PresBox.tsx +++ b/src/client/views/nodes/PresBox.tsx @@ -42,7 +42,7 @@ export class PresBox extends React.Component<FieldViewProps> { if (value) { value.forEach((item, i) => { if (item instanceof Doc && item.type !== DocumentType.PRESELEMENT) { - let pinDoc = Docs.Create.PresElementBoxDocument({ backgroundColor: "transparent" }); + 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); @@ -61,9 +61,9 @@ export class PresBox extends React.Component<FieldViewProps> { next = async () => { const current = NumCast(this.props.Document.selectedDoc); //asking to get document at current index - let docAtCurrentNext = await this.getDocAtIndex(current + 1); + const docAtCurrentNext = await this.getDocAtIndex(current + 1); if (docAtCurrentNext !== undefined) { - let presDocs = DocListCast(this.props.Document[this.props.fieldKey]); + const presDocs = DocListCast(this.props.Document[this.props.fieldKey]); let nextSelected = current + 1; for (; nextSelected < presDocs.length - 1; nextSelected++) { @@ -78,15 +78,15 @@ export class PresBox extends React.Component<FieldViewProps> { back = async () => { const current = NumCast(this.props.Document.selectedDoc); //requesting for the doc at current index - let docAtCurrent = await this.getDocAtIndex(current); + const docAtCurrent = await this.getDocAtIndex(current); if (docAtCurrent !== undefined) { //asking for its presentation id. let prevSelected = current; let zoomOut: boolean = false; - let presDocs = await DocListCastAsync(this.props.Document[this.props.fieldKey]); - let currentsArray: Doc[] = []; + const presDocs = await DocListCastAsync(this.props.Document[this.props.fieldKey]); + const currentsArray: Doc[] = []; for (; presDocs && prevSelected > 0 && presDocs[prevSelected].groupButton; prevSelected--) { currentsArray.push(presDocs[prevSelected]); } @@ -104,8 +104,8 @@ export class PresBox extends React.Component<FieldViewProps> { //If so making sure to zoom out, which goes back to state before zooming action if (current > 0) { if (zoomOut || docAtCurrent.showButton) { - let prevScale = NumCast(this.childDocs[prevSelected].viewScale, null); - let curScale = DocumentManager.Instance.getScaleOfDocView(this.childDocs[current]); + const prevScale = NumCast(this.childDocs[prevSelected].viewScale, null); + const curScale = DocumentManager.Instance.getScaleOfDocView(this.childDocs[current]); if (prevScale !== undefined && prevScale !== curScale) { DocumentManager.Instance.zoomIntoScale(docAtCurrent, prevScale); } @@ -162,13 +162,13 @@ export class PresBox extends React.Component<FieldViewProps> { * te option open, navigates to that element. */ navigateToElement = async (curDoc: Doc, fromDocIndex: number) => { - let fromDoc = this.childDocs[fromDocIndex].presentationTargetDoc as Doc; + const fromDoc = this.childDocs[fromDocIndex].presentationTargetDoc as Doc; let docToJump = curDoc; let willZoom = false; - let presDocs = DocListCast(this.props.Document[this.props.fieldKey]); + const presDocs = DocListCast(this.props.Document[this.props.fieldKey]); let nextSelected = presDocs.indexOf(curDoc); - let currentDocGroups: Doc[] = []; + const currentDocGroups: Doc[] = []; for (; nextSelected < presDocs.length - 1; nextSelected++) { if (!presDocs[nextSelected + 1].groupButton) { break; @@ -190,11 +190,11 @@ 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 if (docToJump === curDoc) { //checking if curDoc has navigation open - let target = await curDoc.presentationTargetDoc as Doc; + const target = await curDoc.presentationTargetDoc as Doc; if (curDoc.navButton) { DocumentManager.Instance.jumpToDocument(target, false); } else if (curDoc.showButton) { - let curScale = DocumentManager.Instance.getScaleOfDocView(fromDoc); + 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); curDoc.viewScale = DocumentManager.Instance.getScaleOfDocView(target); @@ -207,11 +207,11 @@ export class PresBox extends React.Component<FieldViewProps> { } return; } - let curScale = DocumentManager.Instance.getScaleOfDocView(fromDoc); + 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); - let newScale = DocumentManager.Instance.getScaleOfDocView(await curDoc.presentationTargetDoc as Doc); + const newScale = DocumentManager.Instance.getScaleOfDocView(await curDoc.presentationTargetDoc as Doc); curDoc.viewScale = newScale; //saving the scale that user was on if (curScale !== 1) { @@ -238,7 +238,7 @@ export class PresBox extends React.Component<FieldViewProps> { public removeDocument = (doc: Doc) => { const value = FieldValue(Cast(this.props.Document[this.props.fieldKey], listSpec(Doc))); if (value) { - let indexOfDoc = value.indexOf(doc); + const indexOfDoc = value.indexOf(doc); if (indexOfDoc !== - 1) { value.splice(indexOfDoc, 1)[0]; return true; @@ -337,13 +337,13 @@ export class PresBox extends React.Component<FieldViewProps> { @action initializeScaleViews = (docList: Doc[], viewtype: number) => { this.props.Document.chromeStatus = "disabled"; - let hgt = (viewtype === CollectionViewType.Tree) ? 50 : 72; + const hgt = (viewtype === CollectionViewType.Tree) ? 50 : 72; 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)"); - let curScale = NumCast(doc.viewScale, null); + const curScale = NumCast(doc.viewScale, null); if (curScale === undefined) { doc.viewScale = 1; } @@ -352,7 +352,7 @@ export class PresBox extends React.Component<FieldViewProps> { selectElement = (doc: Doc) => { - let index = DocListCast(this.props.Document[this.props.fieldKey]).indexOf(doc); + const index = DocListCast(this.props.Document[this.props.fieldKey]).indexOf(doc); index !== -1 && this.gotoDocument(index, NumCast(this.props.Document.selectedDoc)); } diff --git a/src/client/views/nodes/VideoBox.scss b/src/client/views/nodes/VideoBox.scss index 0a4c650a8..fabbf5196 100644 --- a/src/client/views/nodes/VideoBox.scss +++ b/src/client/views/nodes/VideoBox.scss @@ -1,6 +1,9 @@ -.videoBox-container { +.videoBox { pointer-events: all; transform-origin: top left; + .videoBox-viewer { + opacity: 0.99; // hack! overcomes some kind of Chrome weirdness where buttons (e.g., snapshot) disappear at some point as the video is resized larger + } .inkingCanvas-paths-markers { opacity : 0.4; // we shouldn't have to do this, but since chrome crawls to a halt with z-index unset in videoBox-content, this is a workaround } diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index bd5bd918f..376d27380 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -9,10 +9,9 @@ import { Doc } from "../../../new_fields/Doc"; import { InkTool } from "../../../new_fields/InkField"; import { createSchema, makeInterface } from "../../../new_fields/Schema"; import { ScriptField } from "../../../new_fields/ScriptField"; -import { Cast, StrCast } from "../../../new_fields/Types"; +import { Cast, StrCast, NumCast } from "../../../new_fields/Types"; import { VideoField } from "../../../new_fields/URLField"; -import { RouteStore } from "../../../server/RouteStore"; -import { emptyFunction, returnOne, Utils } from "../../../Utils"; +import { Utils, emptyFunction, returnOne } from "../../../Utils"; import { Docs, DocUtils } from "../../documents/Documents"; import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView"; import { ContextMenu } from "../ContextMenu"; @@ -23,7 +22,7 @@ import { InkingControl } from "../InkingControl"; import { FieldView, FieldViewProps } from './FieldView'; import "./VideoBox.scss"; import { documentSchema, positionSchema } from "../../../new_fields/documentSchemas"; -var path = require('path'); +const path = require('path'); export const timeSchema = createSchema({ currentTimecode: "number", // the current time of a video or other linear, time-based document. Note, should really get set on an extension field, but that's more complicated when it needs to be set since the extension doc needs to be found first @@ -55,9 +54,9 @@ export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocum } videoLoad = () => { - let aspect = this.player!.videoWidth / this.player!.videoHeight; - var nativeWidth = (this.Document.nativeWidth || 0); - var nativeHeight = (this.Document.nativeHeight || 0); + const aspect = this.player!.videoWidth / this.player!.videoHeight; + 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; @@ -102,12 +101,12 @@ export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocum } @action public Snapshot() { - let width = this.Document.width || 0; - let height = this.Document.height || 0; - var canvas = document.createElement('canvas'); + 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); - var ctx = canvas.getContext('2d');//draw image to canvas. scale to target dimensions + const ctx = canvas.getContext('2d');//draw image to canvas. scale to target dimensions if (ctx) { ctx.rect(0, 0, canvas.width, canvas.height); ctx.fillStyle = "blue"; @@ -116,20 +115,20 @@ export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocum } if (!this._videoRef) { // can't find a way to take snapshots of videos - let b = Docs.Create.ButtonDocument({ + 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() }); b.onClick = ScriptField.MakeScript(`this.currentTimecode = ${(this.Document.currentTimecode || 0)}`); } else { //convert to desired file format - var dataUrl = canvas.toDataURL('image/png'); // can also use 'image/png' + const dataUrl = canvas.toDataURL('image/png'); // can also use 'image/png' // if you want to preview the captured image, - let filename = path.basename(encodeURIComponent("snapshot" + StrCast(this.Document.title).replace(/\..*$/, "") + "_" + (this.Document.currentTimecode || 0).toString().replace(/\./, "_"))); + const filename = path.basename(encodeURIComponent("snapshot" + StrCast(this.Document.title).replace(/\..*$/, "") + "_" + (this.Document.currentTimecode || 0).toString().replace(/\./, "_"))); VideoBox.convertDataUri(dataUrl, filename).then(returnedFilename => { if (returnedFilename) { - let url = this.choosePath(Utils.prepend(returnedFilename)); - let imageSummary = Docs.Create.ImageDocument(url, { + 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-" }); @@ -151,9 +150,9 @@ export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocum if (this.props.setVideoBox) this.props.setVideoBox(this); if (this.youtubeVideoId) { - let youtubeaspect = 400 / 315; - var nativeWidth = (this.Document.nativeWidth || 0); - var nativeHeight = (this.Document.nativeHeight || 0); + const youtubeaspect = 400 / 315; + 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; @@ -182,7 +181,7 @@ export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocum public static async convertDataUri(imageUri: string, returnedFilename: string) { try { - let posting = Utils.prepend(RouteStore.dataUriToImage); + const posting = Utils.prepend("/uploadURI"); const returnedUri = await rp.post(posting, { body: { uri: imageUri, @@ -197,10 +196,10 @@ export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocum } } specificContextMenu = (e: React.MouseEvent): void => { - let field = Cast(this.dataDoc[this.props.fieldKey], VideoField); + const field = Cast(this.dataDoc[this.props.fieldKey], VideoField); if (field) { - let url = field.url.href; - let subitems: ContextMenuProps[] = []; + const url = field.url.href; + const subitems: ContextMenuProps[] = []; subitems.push({ description: "Copy path", event: () => { Utils.CopyText(url); }, icon: "expand-arrows-alt" }); subitems.push({ description: "Toggle Show Controls", event: action(() => VideoBox._showControls = !VideoBox._showControls), icon: "expand-arrows-alt" }); subitems.push({ description: "Take Snapshot", event: () => this.Snapshot(), icon: "expand-arrows-alt" }); @@ -209,9 +208,9 @@ export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocum } @computed get content() { - let field = Cast(this.dataDoc[this.props.fieldKey], VideoField); - let interactive = InkingControl.Instance.selectedTool || !this.props.isSelected() ? "" : "-interactive"; - let style = "videoBox-content" + (this._fullScreen ? "-fullScreen" : "") + interactive; + const field = Cast(this.dataDoc[this.props.fieldKey], VideoField); + const interactive = InkingControl.Instance.selectedTool || !this.props.isSelected() ? "" : "-interactive"; + const style = "videoBox-content" + (this._fullScreen ? "-fullScreen" : "") + interactive; return !field ? <div>Loading</div> : <video className={`${style}`} key="video" ref={this.setVideoRef} onCanPlay={this.videoLoad} controls={VideoBox._showControls} onPlay={() => this.Play()} onSeeked={this.updateTimecode} onPause={() => this.Pause()} onClick={e => e.preventDefault()}> @@ -221,7 +220,7 @@ export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocum } @computed get youtubeVideoId() { - let field = Cast(this.dataDoc[this.props.fieldKey], VideoField); + const field = Cast(this.dataDoc[this.props.fieldKey], VideoField); return field && field.url.href.indexOf("youtube") !== -1 ? ((arr: string[]) => arr[arr.length - 1])(field.url.href.split("/")) : ""; } @@ -232,9 +231,9 @@ export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocum } else this._youtubeContentCreated = false; - let iframe = e.target; + const iframe = e.target; let started = true; - let onYoutubePlayerStateChange = (event: any) => runInAction(() => { + const onYoutubePlayerStateChange = (event: any) => runInAction(() => { if (started && event.data === YT.PlayerState.PLAYING) { started = false; this._youtubePlayer && this._youtubePlayer.unMute(); @@ -244,12 +243,12 @@ export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocum if (event.data === YT.PlayerState.PLAYING && !this._playing) this.Play(false); if (event.data === YT.PlayerState.PAUSED && this._playing) this.Pause(false); }); - let onYoutubePlayerReady = (event: any) => { + const onYoutubePlayerReady = (event: any) => { this._reactionDisposer && this._reactionDisposer(); this._youtubeReactionDisposer && this._youtubeReactionDisposer(); this._reactionDisposer = reaction(() => this.Document.currentTimecode, () => !this._playing && this.Seek(this.Document.currentTimecode || 0)); this._youtubeReactionDisposer = reaction(() => [this.props.isSelected(), DocumentDecorations.Instance.Interacting, InkingControl.Instance.selectedTool], () => { - let interactive = InkingControl.Instance.selectedTool === InkTool.None && this.props.isSelected(true) && !DocumentDecorations.Instance.Interacting; + const interactive = InkingControl.Instance.selectedTool === InkTool.None && this.props.isSelected(true) && !DocumentDecorations.Instance.Interacting; iframe.style.pointerEvents = interactive ? "all" : "none"; }, { fireImmediately: true }); }; @@ -262,20 +261,20 @@ export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocum } private get uIButtons() { - let scaling = Math.min(1.8, this.props.ScreenToLocalTransform().Scale); - let curTime = (this.Document.currentTimecode || 0); - return ([<div className="videoBox-time" key="time" onPointerDown={this.onResetDown} style={{ transform: `scale(${scaling})` }}> + const scaling = Math.min(1.8, this.props.ScreenToLocalTransform().Scale); + const curTime = (this.Document.currentTimecode || 0); + return ([<div className="videoBox-time" key="time" onPointerDown={this.onResetDown} > <span>{"" + Math.round(curTime)}</span> <span style={{ fontSize: 8 }}>{" " + Math.round((curTime - Math.trunc(curTime)) * 100)}</span> </div>, - <div className="videoBox-snapshot" key="snap" onPointerDown={this.onSnapshot} style={{ transform: `scale(${scaling})` }}> + <div className="videoBox-snapshot" key="snap" onPointerDown={this.onSnapshot} > <FontAwesomeIcon icon="camera" size="lg" /> </div>, VideoBox._showControls ? (null) : [ - <div className="videoBox-play" key="play" onPointerDown={this.onPlayDown} style={{ transform: `scale(${scaling})` }}> + <div className="videoBox-play" key="play" onPointerDown={this.onPlayDown} > <FontAwesomeIcon icon={this._playing ? "pause" : "play"} size="lg" /> </div>, - <div className="videoBox-full" key="full" onPointerDown={this.onFullDown} style={{ transform: `scale(${scaling})` }}> + <div className="videoBox-full" key="full" onPointerDown={this.onFullDown} > F </div> ]]); @@ -319,8 +318,8 @@ export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocum @computed get youtubeContent() { this._youtubeIframeId = VideoBox._youtubeIframeCounter++; this._youtubeContentCreated = this._forceCreateYouTubeIFrame ? true : true; - let style = "videoBox-content-YouTube" + (this._fullScreen ? "-fullScreen" : ""); - let start = untracked(() => Math.round(this.Document.currentTimecode || 0)); + 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)} 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}`} />; @@ -328,37 +327,39 @@ export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocum @action.bound addDocumentWithTimestamp(doc: Doc): boolean { - var curTime = (this.Document.currentTimecode || -1); + const curTime = (this.Document.currentTimecode || -1); curTime !== -1 && (doc.displayTimecode = curTime); return this.addDocument(doc); } contentFunc = () => [this.youtubeVideoId ? this.youtubeContent : this.content]; render() { - return (<div className={"videoBox-container"} onContextMenu={this.specificContextMenu} + return (<div className="videoBox" onContextMenu={this.specificContextMenu} style={{ transform: `scale(${this.props.ContentScaling()})`, width: `${100 / this.props.ContentScaling()}%`, height: `${100 / this.props.ContentScaling()}%` }} > - <CollectionFreeFormView {...this.props} - PanelHeight={this.props.PanelHeight} - PanelWidth={this.props.PanelWidth} - annotationsKey={this.annotationsKey} - focus={this.props.focus} - isSelected={this.props.isSelected} - isAnnotationOverlay={true} - select={emptyFunction} - active={this.annotationsActive} - ContentScaling={returnOne} - whenActiveChanged={this.whenActiveChanged} - removeDocument={this.removeDocument} - moveDocument={this.moveDocument} - addDocument={this.addDocumentWithTimestamp} - CollectionView={undefined} - ScreenToLocalTransform={this.props.ScreenToLocalTransform} - ruleProvider={undefined} - renderDepth={this.props.renderDepth + 1} - ContainingCollectionDoc={this.props.ContainingCollectionDoc} - chromeCollapsed={true}> - {this.contentFunc} - </CollectionFreeFormView> + <div className="videoBox-viewer" > + <CollectionFreeFormView {...this.props} + PanelHeight={this.props.PanelHeight} + PanelWidth={this.props.PanelWidth} + annotationsKey={this.annotationsKey} + focus={this.props.focus} + isSelected={this.props.isSelected} + isAnnotationOverlay={true} + select={emptyFunction} + active={this.annotationsActive} + ContentScaling={returnOne} + whenActiveChanged={this.whenActiveChanged} + removeDocument={this.removeDocument} + moveDocument={this.moveDocument} + addDocument={this.addDocumentWithTimestamp} + CollectionView={undefined} + ScreenToLocalTransform={this.props.ScreenToLocalTransform} + ruleProvider={undefined} + renderDepth={this.props.renderDepth + 1} + ContainingCollectionDoc={this.props.ContainingCollectionDoc} + chromeCollapsed={true}> + {this.contentFunc} + </CollectionFreeFormView> + </div> {this.uIButtons} </div >); } diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index 5af743859..b0a93224d 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -36,11 +36,11 @@ export class WebBox extends DocAnnotatableComponent<FieldViewProps, WebDocument> componentWillMount() { - let field = Cast(this.props.Document[this.props.fieldKey], WebField); + const field = Cast(this.props.Document[this.props.fieldKey], WebField); if (field && field.url.href.indexOf("youtube") !== -1) { - let youtubeaspect = 400 / 315; - var nativeWidth = NumCast(this.layoutDoc.nativeWidth); - var nativeHeight = NumCast(this.layoutDoc.nativeHeight); + const youtubeaspect = 400 / 315; + const nativeWidth = NumCast(this.layoutDoc.nativeWidth); + const nativeHeight = NumCast(this.layoutDoc.nativeHeight); if (!nativeWidth || !nativeHeight || Math.abs(nativeWidth / nativeHeight - youtubeaspect) > 0.05) { if (!nativeWidth) this.layoutDoc.nativeWidth = 600; this.layoutDoc.nativeHeight = NumCast(this.layoutDoc.nativeWidth) / youtubeaspect; @@ -65,7 +65,7 @@ export class WebBox extends DocAnnotatableComponent<FieldViewProps, WebDocument> @action setURL() { - let urlField: FieldResult<WebField> = Cast(this.props.Document.data, WebField); + const urlField: FieldResult<WebField> = Cast(this.props.Document.data, WebField); if (urlField) this.url = urlField.url.toString(); else this.url = ""; } @@ -80,10 +80,10 @@ export class WebBox extends DocAnnotatableComponent<FieldViewProps, WebDocument> switchToText = () => { let url: string = ""; - let field = Cast(this.props.Document[this.props.fieldKey], WebField); + const field = Cast(this.props.Document[this.props.fieldKey], WebField); if (field) url = field.url.href; - let newBox = Docs.Create.TextDocument({ + const newBox = Docs.Create.TextDocument({ x: NumCast(this.props.Document.x), y: NumCast(this.props.Document.y), title: url, @@ -167,7 +167,7 @@ export class WebBox extends DocAnnotatableComponent<FieldViewProps, WebDocument> @computed get content() { - let field = this.dataDoc[this.props.fieldKey]; + const field = this.dataDoc[this.props.fieldKey]; let view; if (field instanceof HtmlField) { view = <span id="webBox-htmlSpan" dangerouslySetInnerHTML={{ __html: field.html }} />; @@ -176,15 +176,15 @@ export class WebBox extends DocAnnotatableComponent<FieldViewProps, WebDocument> } else { view = <iframe src={"https://crossorigin.me/https://cs.brown.edu"} style={{ position: "absolute", width: "100%", height: "100%", top: 0 }} />; } - let content = + const content = <div style={{ width: "100%", height: "100%", position: "absolute" }} onWheel={this.onPostWheel} onPointerDown={this.onPostPointer} onPointerMove={this.onPostPointer} onPointerUp={this.onPostPointer}> {this.urlEditor()} {view} </div>; - let frozen = !this.props.isSelected() || DocumentDecorations.Instance.Interacting; + const frozen = !this.props.isSelected() || DocumentDecorations.Instance.Interacting; - let classname = "webBox-cont" + (this.props.isSelected() && InkingControl.Instance.selectedTool === InkTool.None && !DocumentDecorations.Instance.Interacting ? "-interactive" : ""); + const classname = "webBox-cont" + (this.props.isSelected() && InkingControl.Instance.selectedTool === InkTool.None && !DocumentDecorations.Instance.Interacting ? "-interactive" : ""); return ( <> <div className={classname} > |
