diff options
Diffstat (limited to 'src/client/views/nodes')
42 files changed, 1180 insertions, 939 deletions
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx index 77e2df94c..2edb73cee 100644 --- a/src/client/views/nodes/AudioBox.tsx +++ b/src/client/views/nodes/AudioBox.tsx @@ -109,12 +109,14 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD this._linkPlayDisposer?.(); this._scrubbingDisposer?.(); } + + @action componentDidMount() { if (!this.dataDoc.markerAmount) { this.dataDoc.markerAmount = 0; } - runInAction(() => this.audioState = this.path ? "paused" : undefined); + this.audioState = this.path ? "paused" : undefined; this._linkPlayDisposer = reaction(() => this.layoutDoc.scrollToLinkID, scrollLinkId => { if (scrollLinkId) { @@ -169,11 +171,11 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD htmlEle.duration && htmlEle.duration !== Infinity && runInAction(() => this.dataDoc.duration = htmlEle.duration); DocListCast(this.dataDoc.links).map(l => { const { la1, linkTime } = this.getLinkData(l); - if (linkTime > NumCast(this.layoutDoc.currentTimecode) && linkTime < htmlEle.currentTime) { + if (linkTime > NumCast(this.layoutDoc._currentTimecode) && linkTime < htmlEle.currentTime) { Doc.linkFollowHighlight(la1); } }); - this.layoutDoc.currentTimecode = htmlEle.currentTime; + this.layoutDoc._currentTimecode = htmlEle.currentTime; } } @@ -223,7 +225,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD if (this._paused) { this._pausedTime += (new Date().getTime() - this._recordStart) / 1000; } else { - this.layoutDoc.currentTimecode = (new Date().getTime() - this._recordStart - this.pauseTime) / 1000; + this.layoutDoc._currentTimecode = (new Date().getTime() - this._recordStart - this.pauseTime) / 1000; } } } @@ -285,7 +287,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD // creates a text document for dictation onFile = (e: any) => { const newDoc = Docs.Create.TextDocument("", { - title: "", _chromeStatus: "disabled", + _showTitle: Doc.UserDoc().showTitle ? "title" : undefined, title: "", _chromeStatus: "disabled", x: NumCast(this.props.Document.x), y: NumCast(this.props.Document.y) + NumCast(this.props.Document._height) + 10, _width: NumCast(this.props.Document._width), _height: 2 * NumCast(this.props.Document._height) }); @@ -395,7 +397,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD this.changeMarker(this._currMarker, toTimeline(e.clientX - rect.x)); return false; }, - () => this._ele!.currentTime = this.layoutDoc.currentTimecode = toTimeline(e.clientX - rect.x), + () => this._ele!.currentTime = this.layoutDoc._currentTimecode = toTimeline(e.clientX - rect.x), emptyFunction); } @@ -463,7 +465,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD color={"darkblue"} height={this._waveHeight} barWidth={0.1} - // pos={this.layoutDoc.currentTimecode} need to correctly resize parent to make this work (not very necessary for function) + // pos={this.layoutDoc._currentTimecode} need to correctly resize parent to make this work (not very necessary for function) pos={this.audioDuration} duration={this.audioDuration} peaks={this._buckets.length === 100 ? this._buckets : undefined} @@ -544,7 +546,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD <div className="buttons" onClick={this._paused ? this.recordPlay : this.recordPause}> <FontAwesomeIcon style={{ width: "100%" }} icon={this._paused ? "play" : "pause"} size={this.props.PanelHeight() < 36 ? "1x" : "2x"} /> </div> - <div className="time">{formatTime(Math.round(NumCast(this.layoutDoc.currentTimecode)))}</div> + <div className="time">{formatTime(Math.round(NumCast(this.layoutDoc._currentTimecode)))}</div> </div> : <button className={`audiobox-record${interactive}`} style={{ backgroundColor: "black" }}> @@ -562,7 +564,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD if (e.target !== this._audioRef.current) { const wasPaused = this.audioState === "paused"; - this._ele!.currentTime = this.layoutDoc.currentTimecode = (e.clientX - rect.x) / rect.width * this.audioDuration; + this._ele!.currentTime = this.layoutDoc._currentTimecode = (e.clientX - rect.x) / rect.width * this.audioDuration; wasPaused && this.pause(); } @@ -624,11 +626,11 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD })} {this._visible ? this.selectionContainer : null} - <div className="audiobox-current" ref={this._audioRef} onClick={e => { e.stopPropagation(); e.preventDefault(); }} style={{ left: `${NumCast(this.layoutDoc.currentTimecode) / this.audioDuration * 100}%`, pointerEvents: "none" }} /> + <div className="audiobox-current" ref={this._audioRef} onClick={e => { e.stopPropagation(); e.preventDefault(); }} style={{ left: `${NumCast(this.layoutDoc._currentTimecode) / this.audioDuration * 100}%`, pointerEvents: "none" }} /> {this.audio} </div> <div className="current-time"> - {formatTime(Math.round(NumCast(this.layoutDoc.currentTimecode)))} + {formatTime(Math.round(NumCast(this.layoutDoc._currentTimecode)))} </div> <div className="total-time"> {formatTime(Math.round(this.audioDuration))} diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index 52f6a66c8..16b2f7c2a 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -58,21 +58,6 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF @computed get nativeWidth() { return NumCast(this.layoutDoc._nativeWidth, this.props.NativeWidth() || (this.freezeDimensions ? this.layoutDoc[WidthSym]() : 0)); } @computed get nativeHeight() { return NumCast(this.layoutDoc._nativeHeight, this.props.NativeHeight() || (this.freezeDimensions ? this.layoutDoc[HeightSym]() : 0)); } - @computed get renderScriptDim() { - if (this.Document.renderScript) { - const someView = Cast(this.props.Document.someView, Doc); - const minimap = Cast(this.props.Document.minimap, Doc); - if (someView instanceof Doc && minimap instanceof Doc) { - const x = (NumCast(someView._panX) - NumCast(someView._width) / 2 / NumCast(someView._viewScale) - (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._viewScale) - (NumCast(minimap.fitY) - NumCast(minimap.fitH) / 2)) / NumCast(minimap.fitH) * NumCast(minimap._height) - NumCast(minimap._height) / 2; - const w = NumCast(someView._width) / NumCast(someView._viewScale) / NumCast(minimap.fitW) * NumCast(minimap.width); - const h = NumCast(someView._height) / NumCast(someView._viewScale) / NumCast(minimap.fitH) * NumCast(minimap.height); - return { x: x, y: y, width: w, height: h }; - } - } - return undefined; - } - public static getValues(doc: Doc, time: number) { const timecode = Math.round(time); return ({ @@ -123,11 +108,11 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF setTimeout(() => doc.dataTransition = "inherit", 1010); } - public static setupScroll(doc: Doc, timecode: number, scrollProgressivize: boolean = false) { + public static setupScroll(doc: Doc, timecode: number) { const scrollList = new List<number>(); scrollList[timecode] = NumCast(doc._scrollTop); doc["scroll-indexed"] = scrollList; - doc.activeFrame = ComputedField.MakeFunction("self.currentFrame"); + doc.activeFrame = ComputedField.MakeFunction("self._currentFrame"); doc._scrollTop = ComputedField.MakeInterpolated("scroll", "activeFrame"); } @@ -165,7 +150,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF } - public static setupZoom(doc: Doc, targDoc: Doc, zoomProgressivize: boolean = false) { + public static setupZoom(doc: Doc, targDoc: Doc) { const width = new List<number>(); const height = new List<number>(); const top = new List<number>(); @@ -180,32 +165,25 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF doc["viewfinder-left-indexed"] = left; } - public static setupKeyframes(docs: Doc[], timecode: number, progressivize: boolean = false) { - docs.forEach((doc, i) => { - if (doc.appearFrame === undefined) doc.appearFrame = i; - const curTimecode = progressivize ? i : timecode; - const xlist = new List<number>(numberRange(timecode + 1).map(i => undefined) as any as number[]); - const ylist = new List<number>(numberRange(timecode + 1).map(i => undefined) as any as number[]); - const wlist = new List<number>(numberRange(timecode + 1).map(i => undefined) as any as number[]); - const hlist = new List<number>(numberRange(timecode + 1).map(i => undefined) as any as number[]); - const olist = new List<number>(numberRange(timecode + 1).map(t => progressivize && t < (doc.appearFrame ? doc.appearFrame : i) ? 0 : 1)); - const oarray = olist; - oarray.fill(0, 0, NumCast(doc.appearFrame) - 1); - oarray.fill(1, NumCast(doc.appearFrame), timecode); - // oarray.fill(0, 0, NumCast(doc.appearFrame) - 1); - // oarray.fill(1, NumCast(doc.appearFrame), timecode);\ + public static setupKeyframes(docs: Doc[], currTimecode: number, makeAppear: boolean = false) { + docs.forEach(doc => { + if (doc.appearFrame === undefined) doc.appearFrame = currTimecode; + const curTimecode = currTimecode; + const xlist = new List<number>(numberRange(currTimecode + 1).map(i => undefined) as any as number[]); + const ylist = new List<number>(numberRange(currTimecode + 1).map(i => undefined) as any as number[]); + const wlist = new List<number>(numberRange(currTimecode + 1).map(i => undefined) as any as number[]); + const hlist = new List<number>(numberRange(currTimecode + 1).map(i => undefined) as any as number[]); + const olist = new List<number>(numberRange(currTimecode + 1).map(t => !doc.z && makeAppear && t < NumCast(doc.appearFrame) ? 0 : 1)); wlist[curTimecode] = NumCast(doc._width); hlist[curTimecode] = NumCast(doc._height); xlist[curTimecode] = NumCast(doc.x); ylist[curTimecode] = NumCast(doc.y); - doc.xArray = xlist; - doc.yArray = ylist; doc["x-indexed"] = xlist; doc["y-indexed"] = ylist; doc["w-indexed"] = wlist; doc["h-indexed"] = hlist; - doc["opacity-indexed"] = oarray; - doc.activeFrame = ComputedField.MakeFunction("self.context?.currentFrame||0"); + doc["opacity-indexed"] = olist; + doc.activeFrame = ComputedField.MakeFunction("self.context?._currentFrame||0"); doc._height = ComputedField.MakeInterpolated("h", "activeFrame"); doc._width = ComputedField.MakeInterpolated("w", "activeFrame"); doc.x = ComputedField.MakeInterpolated("x", "activeFrame"); @@ -274,8 +252,8 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF boxShadow: this.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.props.backgroundHalo?.() && this.props.Document.type !== DocumentType.INK ? (`${this.props.backgroundColor?.(this.props.Document, this.props.renderDepth)} ${StrCast(this.layoutDoc.boxShadow, `0vw 0vw ${(this.layoutDoc.isBackground ? 100 : 50) / this.props.ContentScaling()}px`)}`) : // if it's just in a cluster, make the shadown roughly match the cluster border extent - this.layoutDoc.isBackground ? undefined : // if it's a background & has a cluster color, make the shadow spread really big + this.props.backgroundHalo?.() && this.props.Document.type !== DocumentType.INK ? (`${this.props.backgroundColor?.(this.props.Document, this.props.renderDepth)} ${StrCast(this.layoutDoc.boxShadow, `0vw 0vw ${(this.layoutDoc._isBackground ? 100 : 50) / this.props.ContentScaling()}px`)}`) : // if it's just in a cluster, make the shadown roughly match the cluster border extent + this.layoutDoc._isBackground ? undefined : // if it's a background & has a cluster color, make the shadow spread really big StrCast(this.layoutDoc.boxShadow, ""), borderRadius: StrCast(Doc.Layout(this.layoutDoc).borderRounding), outline: this.Highlight ? "orange solid 2px" : "", @@ -286,7 +264,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF zIndex: this.ZInd, mixBlendMode: StrCast(this.layoutDoc.mixBlendMode) as any, display: this.ZInd === -99 ? "none" : undefined, - pointerEvents: this.props.Document.isBackground || this.Opacity === 0 || this.props.Document.type === DocumentType.INK || this.props.Document.isInkMask ? "none" : this.props.pointerEvents ? "all" : undefined + pointerEvents: this.props.Document._isBackground || this.Opacity === 0 || this.props.Document.type === DocumentType.INK || this.props.Document.isInkMask ? "none" : this.props.pointerEvents ? "all" : undefined }} > {Doc.UserDoc().renderStyle !== "comic" ? (null) : diff --git a/src/client/views/nodes/DocHolderBox.tsx b/src/client/views/nodes/DocHolderBox.tsx index 88eb48f51..b3b7cc4f3 100644 --- a/src/client/views/nodes/DocHolderBox.tsx +++ b/src/client/views/nodes/DocHolderBox.tsx @@ -120,6 +120,7 @@ export class DocHolderBox extends ViewBoxAnnotatableComponent<FieldViewProps, Do DataDoc={undefined} LibraryPath={emptyPath} docFilters={this.props.docFilters} + searchFilterDocs={this.props.searchFilterDocs} ContainingCollectionView={this as any} // bcz: hack! need to pass a prop that can be used to select the container (ie, 'this') when the up selector in document decorations is clicked. currently, the up selector allows only a containing collection to be selected ContainingCollectionDoc={undefined} fitToBox={true} @@ -149,6 +150,7 @@ export class DocHolderBox extends ViewBoxAnnotatableComponent<FieldViewProps, Do DataDoc={undefined} LibraryPath={emptyPath} docFilters={this.props.docFilters} + searchFilterDocs={this.props.searchFilterDocs} ContainingCollectionView={this as any} // bcz: hack! need to pass a prop that can be used to select the container (ie, 'this') when the up selector in document decorations is clicked. currently, the up selector allows only a containing collection to be selected ContainingCollectionDoc={undefined} fitToBox={true} diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index 2408b3906..1b2070c0f 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -18,14 +18,14 @@ import { DocHolderBox } from "./DocHolderBox"; import { DocumentViewProps } from "./DocumentView"; import "./DocumentView.scss"; import { FontIconBox } from "./FontIconBox"; -import { MenuIconBox } from "./MenuIconBox"; import { FieldView, FieldViewProps } from "./FieldView"; -import { FormattedTextBox } from "./formattedText/FormattedTextBox"; +import { FormattedTextBox, FormattedTextBoxProps } from "./formattedText/FormattedTextBox"; import { ImageBox } from "./ImageBox"; import { KeyValueBox } from "./KeyValueBox"; import { PDFBox } from "./PDFBox"; import { PresBox } from "./PresBox"; import { SearchBox } from "../search/SearchBox"; +import { FilterBox } from "./FilterBox"; import { ColorBox } from "./ColorBox"; import { DashWebRTCVideo } from "../webcam/DashWebRTCVideo"; import { LinkAnchorBox } from "./LinkAnchorBox"; @@ -93,7 +93,7 @@ export class HTMLtag extends React.Component<HTMLtagProps> { } @observer -export class DocumentContentsView extends React.Component<DocumentViewProps & { +export class DocumentContentsView extends React.Component<DocumentViewProps & FormattedTextBoxProps & { isSelected: (outsideReaction: boolean) => boolean, select: (ctrl: boolean) => void, layoutKey: string, @@ -190,9 +190,9 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & { blacklistedAttrs={[]} renderInWrapper={false} components={{ - FormattedTextBox, ImageBox, DirectoryImportBox, FontIconBox, MenuIconBox, LabelBox, SliderBox, FieldView, + FormattedTextBox, ImageBox, DirectoryImportBox, FontIconBox, LabelBox, SliderBox, FieldView, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, WebBox, KeyValueBox, - PDFBox, VideoBox, AudioBox, PresBox, YoutubeBox, PresElementBox, SearchBox, + PDFBox, VideoBox, AudioBox, PresBox, YoutubeBox, PresElementBox, SearchBox, FilterBox, ColorBox, DashWebRTCVideo, LinkAnchorBox, InkingStroke, DocHolderBox, LinkBox, ScriptingBox, ScreenshotBox, HTMLtag, ComparisonBox }} diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx index cf8645e4c..ab6cae0ad 100644 --- a/src/client/views/nodes/DocumentLinksButton.tsx +++ b/src/client/views/nodes/DocumentLinksButton.tsx @@ -25,7 +25,7 @@ export const Flyout = higflyout.default; interface DocumentLinksButtonProps { View: DocumentView; - Offset?: number[]; + Offset?: (number | undefined)[]; AlwaysOn?: boolean; InMenu?: boolean; StartLink?: boolean; @@ -36,6 +36,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp private _linkButton = React.createRef<HTMLDivElement>(); @observable public static StartLink: Doc | undefined; + @observable public static StartLinkView: DocumentView | undefined; @observable public static AnnotationId: string | undefined; @observable public static AnnotationUri: string | undefined; @@ -79,8 +80,10 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp //action(() => Doc.BrushDoc(this.props.View.Document)); if (DocumentLinksButton.StartLink === this.props.View.props.Document) { DocumentLinksButton.StartLink = undefined; + DocumentLinksButton.StartLinkView = undefined; } else { DocumentLinksButton.StartLink = this.props.View.props.Document; + DocumentLinksButton.StartLinkView = this.props.View; } } else if (!this.props.InMenu) { DocumentLinksButton.EditLink = this.props.View; @@ -95,8 +98,10 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp DocumentLinksButton.AnnotationUri = undefined; if (DocumentLinksButton.StartLink === this.props.View.props.Document) { DocumentLinksButton.StartLink = undefined; + DocumentLinksButton.StartLinkView = undefined; } else { DocumentLinksButton.StartLink = this.props.View.props.Document; + DocumentLinksButton.StartLinkView = this.props.View; } //action(() => Doc.BrushDoc(this.props.View.Document)); @@ -110,6 +115,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp if (doubleTap && !this.props.StartLink) { if (DocumentLinksButton.StartLink === this.props.View.props.Document) { DocumentLinksButton.StartLink = undefined; + DocumentLinksButton.StartLinkView = undefined; DocumentLinksButton.AnnotationId = undefined; } else if (DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== this.props.View.props.Document) { const sourceDoc = DocumentLinksButton.StartLink; @@ -129,9 +135,15 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp LinkDescriptionPopup.popupY = e.screenY - 100; LinkDescriptionPopup.descriptionPopup = true; - LinkDescriptionPopup.popupX = e.screenX; - LinkDescriptionPopup.popupY = e.screenY - 100; - LinkDescriptionPopup.descriptionPopup = true; + const rect = document.body.getBoundingClientRect(); + if (LinkDescriptionPopup.popupX + 200 > rect.width) { + LinkDescriptionPopup.popupX -= 190; + TaskCompletionBox.popupX -= 40; + } + if (LinkDescriptionPopup.popupY + 100 > rect.height) { + LinkDescriptionPopup.popupY -= 40; + TaskCompletionBox.popupY -= 40; + } setTimeout(action(() => TaskCompletionBox.taskCompleted = false), 2500); } @@ -144,6 +156,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp public static finishLinkClick = undoBatch(action((screenX: number, screenY: number, startLink: Doc, endLink: Doc, startIsAnnotation: boolean, endLinkView?: DocumentView,) => { if (startLink === endLink) { DocumentLinksButton.StartLink = undefined; + DocumentLinksButton.StartLinkView = undefined; DocumentLinksButton.AnnotationId = undefined; DocumentLinksButton.AnnotationUri = undefined; //!this.props.StartLink @@ -151,8 +164,12 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp const linkDoc = DocUtils.MakeLink({ doc: startLink }, { doc: endLink }, DocumentLinksButton.AnnotationId ? "hypothes.is annotation" : "long drag"); // this notifies any of the subviews that a document is made so that they can make finer-grained hyperlinks (). see note above in onLInkButtonMoved if (endLinkView) { - startLink._link = endLinkView._link = linkDoc; - setTimeout(action(() => startLink._link = endLinkView._link = undefined), 0); + endLinkView._link = linkDoc; + DocumentLinksButton.StartLinkView && (DocumentLinksButton.StartLinkView._link = linkDoc); + setTimeout(action(() => { + DocumentLinksButton.StartLinkView && (DocumentLinksButton.StartLinkView._link = undefined); + endLinkView._link = undefined; + }), 0); } LinkManager.currentLink = linkDoc; @@ -176,6 +193,17 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp LinkDescriptionPopup.popupY = screenY - 100; LinkDescriptionPopup.descriptionPopup = true; } + + const rect = document.body.getBoundingClientRect(); + if (LinkDescriptionPopup.popupX + 200 > rect.width) { + LinkDescriptionPopup.popupX -= 190; + TaskCompletionBox.popupX -= 40; + } + if (LinkDescriptionPopup.popupY + 100 > rect.height) { + LinkDescriptionPopup.popupY -= 40; + TaskCompletionBox.popupY -= 40; + } + setTimeout(action(() => { TaskCompletionBox.taskCompleted = false; }), 2500); } } @@ -186,12 +214,13 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp @action clearLinks() { DocumentLinksButton.StartLink = undefined; + DocumentLinksButton.StartLinkView = undefined; } @computed get linkButton() { TraceMobx(); - const links = this.props.links; + const links = new Set<Doc>(this.props.links); const menuTitle = this.props.StartLink ? "Drag or tap to start link" : "Tap to complete link"; const buttonTitle = "Tap to view links"; @@ -213,7 +242,10 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp id={"link-icon"} src={`/assets/${"link.png"}`} />; - const linkButton = <div ref={this._linkButton} style={{ minWidth: 20, minHeight: 20, position: "absolute", left: this.props.Offset?.[0] }}> + const linkButton = <div className="documentLinksButton-cont" ref={this._linkButton} style={{ + minWidth: 20, minHeight: 20, position: "absolute", + left: this.props.Offset?.[0], top: this.props.Offset?.[1], right: this.props.Offset?.[2], bottom: this.props.Offset?.[3] + }}> <div className={"documentLinksButton"} style={{ backgroundColor: this.props.InMenu ? "" : "#add8e6", color: this.props.InMenu ? "white" : "black", @@ -233,7 +265,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp <FontAwesomeIcon className="documentdecorations-icon" icon="hand-paper" size="sm" /> : links.length} */} {this.props.InMenu ? this.props.StartLink ? <FontAwesomeIcon className="documentdecorations-icon" icon="link" size="sm" /> : - link : links.length} + link : Array.from(links).length} </div> {this.props.InMenu && !this.props.StartLink && DocumentLinksButton.StartLink !== this.props.View.props.Document ? @@ -245,7 +277,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp border: DocumentLinksButton.StartLink ? "" : "none" }} onPointerDown={DocumentLinksButton.StartLink ? this.completeLink : emptyFunction} - onClick={e => DocumentLinksButton.StartLink ? DocumentLinksButton.finishLinkClick(e.screenX, e.screenY, DocumentLinksButton.StartLink, this.props.View.props.Document, true, this.props.View) : emptyFunction} /> : (null) + onClick={e => DocumentLinksButton.StartLink ? DocumentLinksButton.finishLinkClick(e.clientX, e.clientY, DocumentLinksButton.StartLink, this.props.View.props.Document, true, this.props.View) : emptyFunction} /> : (null) } { DocumentLinksButton.StartLink === this.props.View.props.Document && this.props.InMenu && this.props.StartLink ? <div className={"documentLinksButton-startLink"} @@ -255,7 +287,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp } </div >; - return (!links.length) && !this.props.AlwaysOn ? (null) : + return (!Array.from(links).length) && !this.props.AlwaysOn ? (null) : this.props.InMenu && (DocumentLinksButton.StartLink || this.props.StartLink) ? <Tooltip title={<><div className="dash-tooltip">{title}</div></>}> {linkButton} diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss index e6b8928d4..2dd3bba91 100644 --- a/src/client/views/nodes/DocumentView.scss +++ b/src/client/views/nodes/DocumentView.scss @@ -133,6 +133,8 @@ bottom: 0; width: 100%; transform-origin: bottom left; + opacity: 0.1; + transition: opacity 0.5s; } } @@ -144,4 +146,9 @@ display:inline-block; } } + > .documentView-styleWrapper { + > .documentView-captionWrapper { + opacity: 1; + } + } }
\ No newline at end of file diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index d92dc0ec2..f6360fc87 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -21,7 +21,7 @@ import { InteractionUtils } from '../../util/InteractionUtils'; import { LinkManager } from '../../util/LinkManager'; import { Scripting } from '../../util/Scripting'; import { SelectionManager } from "../../util/SelectionManager"; -import SharingManager from '../../util/SharingManager'; +import { SharingManager } from '../../util/SharingManager'; import { SnappingManager } from '../../util/SnappingManager'; import { Transform } from "../../util/Transform"; import { undoBatch, UndoManager } from "../../util/UndoManager"; @@ -30,6 +30,7 @@ import { ContextMenu } from "../ContextMenu"; import { ContextMenuProps } from '../ContextMenuItem'; import { DocComponent } from "../DocComponent"; import { EditableView } from '../EditableView'; +import { InkStrokeProperties } from '../InkStrokeProperties'; import { DocumentContentsView } from "./DocumentContentsView"; import { DocumentLinksButton } from './DocumentLinksButton'; import "./DocumentView.scss"; @@ -38,6 +39,7 @@ import { LinkDescriptionPopup } from './LinkDescriptionPopup'; import { RadialMenu } from './RadialMenu'; import { TaskCompletionBox } from './TaskCompletedBox'; import React = require("react"); +import { CurrentUserUtils } from '../../util/CurrentUserUtils'; export type DocFocusFunc = () => boolean; @@ -45,11 +47,13 @@ export interface DocumentViewProps { ContainingCollectionView: Opt<CollectionView>; ContainingCollectionDoc: Opt<Doc>; docFilters: () => string[]; + searchFilterDocs: () => Doc[]; FreezeDimensions?: boolean; NativeWidth: () => number; NativeHeight: () => number; Document: Doc; DataDoc?: Doc; + getView?: (view: DocumentView) => any; LayoutTemplateString?: string; LayoutTemplate?: () => Opt<Doc>; LibraryPath: Doc[]; @@ -69,7 +73,7 @@ export interface DocumentViewProps { removeDocument?: (doc: Doc | Doc[]) => boolean; moveDocument?: (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[]) => boolean) => boolean; ScreenToLocalTransform: () => Transform; - setupDragLines?: () => void; + setupDragLines?: (snapToDraggedDoc: boolean) => void; renderDepth: number; ContentScaling: () => number; PanelWidth: () => number; @@ -104,17 +108,15 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu private _lastTap: number = 0; private _doubleTap = false; private _mainCont = React.createRef<HTMLDivElement>(); - private _dropDisposer?: DragManager.DragDropDisposer; - private _showKPQuery: boolean = false; - private _queries: string = ""; private _titleRef = React.createRef<EditableView>(); + private _dropDisposer?: DragManager.DragDropDisposer; private _gestureEventDisposer?: GestureUtils.GestureEventDisposer; private _holdDisposer?: InteractionUtils.MultiTouchEventDisposer; protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer; + private get active() { return SelectionManager.IsSelected(this, true) || this.props.parentActive(true); } public get displayName() { return "DocumentView(" + this.props.Document.title + ")"; } // this makes mobx trace() statements more descriptive public get ContentDiv() { return this._mainCont.current; } - private get active() { return SelectionManager.IsSelected(this, true) || this.props.parentActive(true); } @computed get topMost() { return this.props.renderDepth === 0; } @computed get freezeDimensions() { return this.props.FreezeDimensions; } @computed get nativeWidth() { return NumCast(this.layoutDoc._nativeWidth, this.props.NativeWidth() || (this.freezeDimensions ? this.layoutDoc[WidthSym]() : 0)); } @@ -128,6 +130,11 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu onClickFunc = () => this.onClickHandler; onDoubleClickFunc = () => this.onDoubleClickHandler; + constructor(props: any) { + super(props); + props.getView?.(this); + } + handle1PointerHoldStart = (e: Event, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>): any => { this.removeMoveListeners(); this.removeEndListeners(); @@ -174,10 +181,10 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu const pt = me.touchEvent.touches[me.touchEvent.touches.length - 1]; RadialMenu.Instance.openMenu(pt.pageX - 15, pt.pageY - 15); - // RadialMenu.Instance.addItem({ description: "Open Fields", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), "onRight"), icon: "map-pin", selected: -1 }); + // RadialMenu.Instance.addItem({ description: "Open Fields", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), "add:right"), icon: "map-pin", selected: -1 }); const effectiveAcl = GetEffectiveAcl(this.props.Document[DataSym]); (effectiveAcl === AclEdit || effectiveAcl === AclAdmin) && RadialMenu.Instance.addItem({ description: "Delete", event: () => { this.props.ContainingCollectionView?.removeDocument(this.props.Document), RadialMenu.Instance.closeMenu(); }, icon: "external-link-square-alt", selected: -1 }); - // RadialMenu.Instance.addItem({ description: "Open in a new tab", event: () => this.props.addDocTab(this.props.Document, "onRight"), icon: "trash", selected: -1 }); + // RadialMenu.Instance.addItem({ description: "Open in a new tab", event: () => this.props.addDocTab(this.props.Document, "add:right"), icon: "trash", selected: -1 }); RadialMenu.Instance.addItem({ description: "Pin", event: () => this.props.pinToPres(this.props.Document), icon: "map-pin", selected: -1 }); RadialMenu.Instance.addItem({ description: "Open", event: () => MobileInterface.Instance.handleClick(this.props.Document), icon: "trash", selected: -1 }); @@ -191,7 +198,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu this._mainCont.current && (this._multiTouchDisposer = InteractionUtils.MakeMultiTouchTarget(this._mainCont.current, this.onTouchStart.bind(this))); // this._mainCont.current && (this.holdDisposer = InteractionUtils.MakeHoldTouchTarget(this._mainCont.current, this.handle1PointerHoldStart.bind(this))); - if (!this.props.dontRegisterView) { + if (!BoolCast(this.rootDoc.dontRegisterView, this.props.dontRegisterView)) { DocumentManager.Instance.DocumentViews.push(this); } } @@ -285,13 +292,19 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu } } + _timeout: NodeJS.Timeout | undefined; + onClick = action((e: React.MouseEvent | React.PointerEvent) => { if (!e.nativeEvent.cancelBubble && !this.Document.ignoreClick && this.props.renderDepth >= 0 && (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD)) { let stopPropagate = true; let preventDefault = true; - !this.props.Document.isBackground && this.props.bringToFront(this.props.Document); - 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 + !this.props.Document._isBackground && this.props.bringToFront(this.props.Document); + if (this._doubleTap && ((this.props.renderDepth && this.props.Document.type !== DocumentType.FONTICON) || this.onDoubleClickHandler)) {// && !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 + if (this._timeout) { + clearTimeout(this._timeout); + this._timeout = undefined; + } if (!(e.nativeEvent as any).formattedHandled) { if (this.onDoubleClickHandler?.script && !StrCast(Doc.LayoutField(this.layoutDoc))?.includes("ScriptingBox")) { // bcz: hack? don't execute script if you're clicking on a scripting box itself const func = () => this.onDoubleClickHandler.script.run({ @@ -301,40 +314,53 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu shiftKey: e.shiftKey }, console.log); func(); - } else { - UndoManager.RunInBatch(() => { - let fullScreenDoc = this.props.Document; - if (StrCast(this.props.Document.layoutKey) !== "layout_fullScreen" && this.props.Document.layout_fullScreen) { - fullScreenDoc = Doc.MakeAlias(this.props.Document); - fullScreenDoc.layoutKey = "layout_fullScreen"; - } - this.props.addDocTab(fullScreenDoc, "inTab"); - }, "double tap"); - SelectionManager.DeselectAll(); + } else if (!Doc.IsSystem(this.props.Document)) { + if (this.props.Document.type === DocumentType.INK) { + InkStrokeProperties.Instance && (InkStrokeProperties.Instance._controlBtn = true); + } else { + UndoManager.RunInBatch(() => { + let fullScreenDoc = this.props.Document; + if (StrCast(this.props.Document.layoutKey) !== "layout_fullScreen" && this.props.Document.layout_fullScreen) { + fullScreenDoc = Doc.MakeAlias(this.props.Document); + fullScreenDoc.layoutKey = "layout_fullScreen"; + } + this.props.addDocTab(fullScreenDoc, "add"); + }, "double tap"); + SelectionManager.DeselectAll(); + } Doc.UnBrushDoc(this.props.Document); } } } else if (this.onClickHandler?.script && !StrCast(Doc.LayoutField(this.layoutDoc))?.includes("ScriptingBox")) { // bcz: hack? don't execute script if you're clicking on a scripting box itself + const shiftKey = e.shiftKey; const func = () => this.onClickHandler.script.run({ this: this.layoutDoc, self: this.rootDoc, scriptContext: this.props.scriptContext, thisContainer: this.props.ContainingCollectionDoc, documentView: this, - shiftKey: e.shiftKey + shiftKey }, console.log); - if (!Doc.AreProtosEqual(this.props.Document, Doc.UserDoc()["dockedBtn-undo"] as Doc) && !Doc.AreProtosEqual(this.props.Document, Doc.UserDoc()["dockedBtn-redo"] as Doc)) { - UndoManager.RunInBatch(func, "on click"); - } else func(); + const clickFunc = () => { + if (!Doc.AreProtosEqual(this.props.Document, Doc.UserDoc()["dockedBtn-undo"] as Doc) && + !Doc.AreProtosEqual(this.props.Document, Doc.UserDoc()["dockedBtn-redo"] as Doc) && + !this.onClickHandler.script.originalScript.includes("selectMainMenu")) { + UndoManager.RunInBatch(func, "on click"); + } else func(); + }; + if (this.onDoubleClickHandler) { + this._timeout = setTimeout(() => { this._timeout = undefined; clickFunc(); }, 500); + } else clickFunc(); } else if (this.Document["onClick-rawScript"] && !StrCast(Doc.LayoutField(this.layoutDoc))?.includes("ScriptingBox")) {// bcz: hack? don't edit a script if you're clicking on a scripting box itself - this.props.addDocTab(DocUtils.makeCustomViewClicked(Doc.MakeAlias(this.props.Document), undefined, "onClick"), "onRight"); + this.props.addDocTab(DocUtils.makeCustomViewClicked(Doc.MakeAlias(this.props.Document), undefined, "onClick"), "add:right"); } else if (this.allLinks && this.Document.isLinkButton && !e.shiftKey && !e.ctrlKey) { this.allLinks.length && this.followLinkClick(e.altKey, e.ctrlKey, e.shiftKey); } else { if ((this.layoutDoc.onDragStart || this.props.Document.rootDocument) && !(e.ctrlKey || e.button > 0)) { // onDragStart implies a button doc that we don't want to select when clicking. RootDocument & isTemplaetForField implies we're clicking on part of a template instance and we want to select the whole template, not the part stopPropagate = false; // don't stop propagation for field templates -- want the selection to propagate up to the root document of the template } else { - SelectionManager.SelectDoc(this, e.ctrlKey || e.shiftKey); + this.select(e.ctrlKey || e.shiftKey); + //SelectionManager.SelectDoc(this, e.ctrlKey || e.shiftKey); } preventDefault = false; } @@ -564,8 +590,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu @undoBatch @action deleteClicked = (): void => { - if (Doc.UserDoc().activeWorkspace === this.props.Document) { - alert("Can't delete the active workspace"); + if (CurrentUserUtils.ActiveDashboard === this.props.Document) { + alert("Can't delete the active dashboard"); } else { const selected = SelectionManager.SelectedDocuments().slice(); SelectionManager.DeselectAll(); @@ -601,8 +627,11 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu @undoBatch @action drop = async (e: Event, de: DragManager.DropEvent) => { - if (this.props.Document === Doc.UserDoc().activeWorkspace) { - alert("linking to document tabs not yet supported. Drop link on document content."); + if (this.props.Document === CurrentUserUtils.ActiveDashboard) { + if ((e.target as any)?.closest?.("*.lm_content")) { + alert("You can't perform this move most likely because you don't have permission to modify the destination."); + } + else alert("linking to document tabs not yet supported. Drop link on document content."); return; } const makeLink = action((linkDoc: Doc) => { @@ -617,15 +646,23 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu LinkDescriptionPopup.popupY = de.y; LinkDescriptionPopup.descriptionPopup = true; + const rect = document.body.getBoundingClientRect(); + if (LinkDescriptionPopup.popupX + 200 > rect.width) { + LinkDescriptionPopup.popupX -= 190; + TaskCompletionBox.popupX -= 40; + } + if (LinkDescriptionPopup.popupY + 100 > rect.height) { + LinkDescriptionPopup.popupY -= 40; + TaskCompletionBox.popupY -= 40; + } + setTimeout(action(() => TaskCompletionBox.taskCompleted = false), 2500); }); if (de.complete.annoDragData) { /// this whole section for handling PDF annotations looks weird. Need to rethink this to make it cleaner e.stopPropagation(); - de.complete.annoDragData.linkedToDoc = true; - - const linkDoc = DocUtils.MakeLink({ doc: de.complete.annoDragData.annotationDocument }, { doc: this.props.Document }, "link"); - linkDoc && makeLink(linkDoc); + de.complete.annoDragData.linkDocument = DocUtils.MakeLink({ doc: de.complete.annoDragData.annotationDocument }, { doc: this.props.Document }, "link"); + de.complete.annoDragData.linkDocument && makeLink(de.complete.annoDragData.linkDocument); } if (de.complete.linkDragData) { e.stopPropagation(); @@ -666,12 +703,14 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu @undoBatch @action toggleBackground = () => { - this.Document.isBackground = (this.Document.isBackground ? undefined : true); - this.Document._overflow = this.Document.isBackground ? "visible" : undefined; - if (this.Document.isBackground) { + this.Document._isBackground = (this.Document._isBackground ? undefined : true); + this.Document._overflow = this.Document._isBackground ? "visible" : undefined; + if (this.Document._isBackground) { this.props.bringToFront(this.props.Document, true); - this.props.Document[DataSym][Doc.LayoutFieldKey(this.Document) + "-nativeWidth"] = this.Document[WidthSym](); - this.props.Document[DataSym][Doc.LayoutFieldKey(this.Document) + "-nativeHeight"] = this.Document[HeightSym](); + const wid = this.Document[WidthSym](); // change the nativewidth and height if the background is to be a collection that aggregates stuff that is added to it. + const hgt = this.Document[HeightSym](); + this.props.Document[DataSym][Doc.LayoutFieldKey(this.Document) + "-nativeWidth"] = wid; + this.props.Document[DataSym][Doc.LayoutFieldKey(this.Document) + "-nativeHeight"] = hgt; } } @@ -684,7 +723,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu } @action - onContextMenu = async (e: React.MouseEvent | Touch): Promise<void> => { + onContextMenu = (e: React.MouseEvent | Touch) => { // the touch onContextMenu is button 0, the pointer onContextMenu is button 2 if (!(e instanceof Touch)) { if (e.button === 0 && !e.ctrlKey) { @@ -703,38 +742,41 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu } const cm = ContextMenu.Instance; - if (!cm) return; + if (!cm || (e as any)?.nativeEvent?.SchemaHandled) return; const customScripts = Cast(this.props.Document.contextMenuScripts, listSpec(ScriptField), []); Cast(this.props.Document.contextMenuLabels, listSpec("string"), []).forEach((label, i) => cm.addItem({ description: label, event: () => customScripts[i]?.script.run({ this: this.layoutDoc, self: this.rootDoc }), icon: "sticky-note" })); this.props.contextMenuItems?.().forEach(item => - cm.addItem({ description: item.label, event: () => item.script.script.run({ this: this.layoutDoc, self: this.rootDoc }), icon: "sticky-note" })); + item.label && cm.addItem({ description: item.label, event: () => item.script.script.run({ this: this.layoutDoc, self: this.rootDoc }), icon: "sticky-note" })); const templateDoc = Cast(this.props.Document[StrCast(this.props.Document.layoutKey)], Doc, null); const appearance = cm.findByDescription("UI Controls..."); const appearanceItems: ContextMenuProps[] = appearance && "subitems" in appearance ? appearance.subitems : []; - templateDoc && appearanceItems.push({ description: "Open Template ", event: () => this.props.addDocTab(templateDoc, "onRight"), icon: "eye" }); + templateDoc && appearanceItems.push({ description: "Open Template ", event: () => this.props.addDocTab(templateDoc, "add:right"), icon: "eye" }); //DocListCast(this.Document.links).length && appearanceItems.splice(0, 0, { description: `${this.layoutDoc.hideLinkButton ? "Show" : "Hide"} Link Button`, event: action(() => this.layoutDoc.hideLinkButton = !this.layoutDoc.hideLinkButton), icon: "eye" }); !appearance && cm.addItem({ description: "UI Controls...", subitems: appearanceItems, icon: "compass" }); const options = cm.findByDescription("Options..."); const optionItems: ContextMenuProps[] = options && "subitems" in options ? options.subitems : []; - optionItems.push({ description: this.Document.lockedPosition ? "Unlock Position" : "Lock Position", event: this.toggleLockPosition, icon: BoolCast(this.Document.lockedPosition) ? "unlock" : "lock" }); + optionItems.push({ description: "Bring to Front", event: () => this.props.bringToFront(this.rootDoc, false), icon: "expand-arrows-alt" }); + optionItems.push({ description: "Send to Back", event: () => this.props.bringToFront(this.rootDoc, true), icon: "expand-arrows-alt" }); + !this.props.treeViewDoc && this.props.ContainingCollectionDoc?._viewType === CollectionViewType.Freeform && optionItems.push({ description: this.Document.lockedPosition ? "Unlock Position" : "Lock Position", event: this.toggleLockPosition, icon: BoolCast(this.Document.lockedPosition) ? "unlock" : "lock" }); !options && cm.addItem({ description: "Options...", subitems: optionItems, icon: "compass" }); - - const existingOnClick = cm.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(self, "${this.Document.layoutKey}")`), icon: "concierge-bell" }); - onClicks.push({ description: this.Document.ignoreClick ? "Select" : "Do Nothing", event: () => this.Document.ignoreClick = !this.Document.ignoreClick, icon: this.Document.ignoreClick ? "unlock" : "lock" }); - onClicks.push({ description: this.Document.isLinkButton ? "Remove Follow Behavior" : "Follow Link in Place", event: () => this.toggleFollowLink("inPlace", true, false), icon: "link" }); - !this.Document.isLinkButton && onClicks.push({ description: "Follow Link on Right", event: () => this.toggleFollowLink("onRight", false, false), icon: "link" }); - onClicks.push({ description: this.Document.isLinkButton || this.onClickHandler ? "Remove Click Behavior" : "Follow Link", event: () => this.toggleFollowLink(undefined, false, false), icon: "link" }); - onClicks.push({ description: (this.Document.isPushpin ? "Remove" : "Make") + " Pushpin", event: () => this.toggleFollowLink(undefined, false, true), icon: "map-pin" }); - onClicks.push({ description: "Edit onClick Script", event: () => UndoManager.RunInBatch(() => DocUtils.makeCustomViewClicked(this.props.Document, undefined, "onClick"), "edit onClick"), icon: "terminal" }); - !existingOnClick && cm.addItem({ description: "OnClick...", noexpand: true, addDivider: true, subitems: onClicks, icon: "mouse-pointer" }); + if (!Doc.IsSystem(this.rootDoc) && this.props.ContainingCollectionDoc?._viewType !== CollectionViewType.Tree) { + const existingOnClick = cm.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(self, "${this.Document.layoutKey}")`), icon: "concierge-bell" }); + onClicks.push({ description: this.Document.ignoreClick ? "Select" : "Do Nothing", event: () => this.Document.ignoreClick = !this.Document.ignoreClick, icon: this.Document.ignoreClick ? "unlock" : "lock" }); + onClicks.push({ description: this.Document.isLinkButton ? "Remove Follow Behavior" : "Follow Link in Place", event: () => this.toggleFollowLink("inPlace", true, false), icon: "link" }); + !this.Document.isLinkButton && onClicks.push({ description: "Follow Link on Right", event: () => this.toggleFollowLink("add:right", false, false), icon: "link" }); + onClicks.push({ description: this.Document.isLinkButton || this.onClickHandler ? "Remove Click Behavior" : "Follow Link", event: () => this.toggleFollowLink(undefined, false, false), icon: "link" }); + onClicks.push({ description: (this.Document.isPushpin ? "Remove" : "Make") + " Pushpin", event: () => this.toggleFollowLink(undefined, false, true), icon: "map-pin" }); + onClicks.push({ description: "Edit onClick Script", event: () => UndoManager.RunInBatch(() => DocUtils.makeCustomViewClicked(this.props.Document, undefined, "onClick"), "edit onClick"), icon: "terminal" }); + !existingOnClick && cm.addItem({ description: "OnClick...", noexpand: true, addDivider: true, subitems: onClicks, icon: "mouse-pointer" }); + } const funcs: ContextMenuProps[] = []; if (this.layoutDoc.onDragStart) { @@ -760,18 +802,21 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu moreItems.push({ description: "Write Back Link to Album", event: () => GooglePhotos.Transactions.AddTextEnrichment(this.props.Document), icon: "caret-square-right" }); } moreItems.push({ description: "Copy ID", event: () => Utils.CopyText(Utils.prepend("/doc/" + this.props.Document[Id])), icon: "fingerprint" }); - Doc.AreProtosEqual(this.props.Document, Doc.UserDoc()) && moreItems.push({ description: "Toggle Always Show Link End", event: () => Doc.UserDoc()["documentLinksButton-hideEnd"] = !Doc.UserDoc()["documentLinksButton-hideEnd"], icon: "eye" }); + Doc.AreProtosEqual(this.props.Document, Cast(Doc.UserDoc().myUserDoc, Doc, null)) && moreItems.push({ description: "Toggle Alternate Button Bar", event: () => Doc.UserDoc()["documentLinksButton-hideEnd"] = !Doc.UserDoc()["documentLinksButton-hideEnd"], icon: "eye" }); } - moreItems.push({ description: "Close", event: this.deleteClicked, icon: "times" }); + const collectionAcl = GetEffectiveAcl(this.props.ContainingCollectionDoc?.[DataSym]); + if ((collectionAcl === AclAdmin || collectionAcl === AclEdit) && this.props.removeDocument) { + moreItems.push({ description: "Close", event: this.deleteClicked, icon: "times" }); + } !more && cm.addItem({ description: "More...", subitems: moreItems, icon: "hand-point-right" }); cm.moveAfter(cm.findByDescription("More...")!, cm.findByDescription("OnClick...")!); const help = cm.findByDescription("Help..."); const helpItems: ContextMenuProps[] = help && "subitems" in help ? help.subitems : []; - !Doc.UserDoc().novice && helpItems.push({ description: "Show Fields ", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), "onRight"), icon: "layer-group" }); - helpItems.push({ description: "Text Shortcuts Ctrl+/", event: () => this.props.addDocTab(Docs.Create.PdfDocument(Utils.prepend("/assets/cheat-sheet.pdf"), { _width: 300, _height: 300 }), "onRight"), icon: "keyboard" }); + !Doc.UserDoc().novice && helpItems.push({ description: "Show Fields ", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), "add:right"), icon: "layer-group" }); + helpItems.push({ description: "Text Shortcuts Ctrl+/", event: () => this.props.addDocTab(Docs.Create.PdfDocument(Utils.prepend("/assets/cheat-sheet.pdf"), { _width: 300, _height: 300 }), "add:right"), icon: "keyboard" }); helpItems.push({ description: "Print Document in Console", event: () => console.log(this.props.Document), icon: "hand-point-right" }); cm.addItem({ description: "Help...", noexpand: true, subitems: helpItems, icon: "question" }); @@ -811,13 +856,14 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu return this.isSelected(outsideReaction) || (this.props.Document.rootDocument && this.props.rootSelected?.(outsideReaction)) || false; } childScaling = () => (this.layoutDoc._fitWidth ? this.props.PanelWidth() / this.nativeWidth : this.props.ContentScaling()); - @computed.struct get linkOffset() { return [-15, 0]; } + @computed.struct get linkOffset() { return this.topMost ? [0, undefined, undefined, 10] : [-15, undefined, undefined, undefined]; } @computed get contents() { const pos = this.props.relative ? "relative " : "absolute"; TraceMobx(); return (<div className="documentView-contentsView" style={{ borderRadius: "inherit", width: "100%", height: "100%" }}> <DocumentContentsView key={1} docFilters={this.props.docFilters} + searchFilterDocs={this.props.searchFilterDocs} ContainingCollectionView={this.props.ContainingCollectionView} ContainingCollectionDoc={this.props.ContainingCollectionDoc} NativeWidth={this.NativeWidth} @@ -879,31 +925,34 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu anchorPanelWidth = () => this.props.PanelWidth() || 1; anchorPanelHeight = () => this.props.PanelHeight() || 1; - @computed.struct get directLinks() { return LinkManager.Instance.getAllDirectLinks(this.Document); } + @computed.struct get directLinks() { return LinkManager.Instance.getAllDirectLinks(this.rootDoc); } @computed.struct get allLinks() { return DocListCast(this.Document.links); } @computed.struct get allAnchors() { TraceMobx(); if (this.props.LayoutTemplateString?.includes("LinkAnchorBox")) return null; - return (this.props.treeViewDoc && this.props.LayoutTemplateString) || // render nothing for: tree view anchor dots + if ((this.props.treeViewDoc && this.props.LayoutTemplateString) || // render nothing for: tree view anchor dots this.layoutDoc.presBox || // presentationbox nodes this.rootDoc.type === DocumentType.LINK || - this.props.dontRegisterView ? (null) : // view that are not registered - DocUtils.FilterDocs(this.directLinks, this.props.docFilters(), []).filter(d => !d.hidden && this.isNonTemporalLink).map((d, i) => - <div className="documentView-anchorCont" key={i + 1}> - <DocumentView {...this.props} - Document={d} - ContainingCollectionView={this.props.ContainingCollectionView} - ContainingCollectionDoc={this.props.Document} // bcz: hack this.props.Document is not a collection Need a better prop for passing the containing document to the LinkAnchorBox - PanelWidth={this.anchorPanelWidth} - PanelHeight={this.anchorPanelHeight} - ContentScaling={returnOne} - dontRegisterView={false} - forcedBackgroundColor={returnTransparent} - removeDocument={this.hideLinkAnchor} - pointerEvents={false} - LayoutTemplate={undefined} - LayoutTemplateString={LinkAnchorBox.LayoutString(`anchor${Doc.LinkEndpoint(d, this.props.Document)}`)} /> - </div >); + this.props.dontRegisterView) {// view that are not registered + return (null); + } + const filtered = DocUtils.FilterDocs(this.directLinks, this.props.docFilters(), []).filter(d => !d.hidden && this.isNonTemporalLink(d)); + return filtered.map((d, i) => + <div className="documentView-anchorCont" key={i + 1}> + <DocumentView {...this.props} + Document={d} + ContainingCollectionView={this.props.ContainingCollectionView} + ContainingCollectionDoc={this.props.Document} // bcz: hack this.props.Document is not a collection Need a better prop for passing the containing document to the LinkAnchorBox + PanelWidth={this.anchorPanelWidth} + PanelHeight={this.anchorPanelHeight} + ContentScaling={returnOne} + dontRegisterView={false} + forcedBackgroundColor={returnTransparent} + removeDocument={this.hideLinkAnchor} + pointerEvents={false} + LayoutTemplate={undefined} + LayoutTemplateString={LinkAnchorBox.LayoutString(`anchor${Doc.LinkEndpoint(d, this.props.Document)}`)} /> + </div >); } @computed get innards() { TraceMobx(); @@ -925,6 +974,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu const captionView = (!showCaption ? (null) : <div className="documentView-captionWrapper" style={{ backgroundColor: StrCast(this.layoutDoc["caption-backgroundColor"]), color: StrCast(this.layoutDoc["caption-color"]) }}> <DocumentContentsView {...OmitKeys(this.props, ['children']).omit} + yMargin={10} + xMargin={10} hideOnLeave={true} LayoutTemplateString={`<FormattedTextBox {...props} fieldKey={'${showCaption}'}/>`} ContentScaling={returnOne} @@ -955,7 +1006,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu } @computed get ignorePointerEvents() { return this.props.pointerEvents === false || - (this.Document.isBackground && !this.isSelected() && !SnappingManager.GetIsDragging()) || + (this.Document._isBackground && !this.isSelected() && !SnappingManager.GetIsDragging()) || (this.Document.type === DocumentType.INK && Doc.GetSelectedTool() !== InkTool.None); } @undoBatch @@ -977,11 +1028,11 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu }); renderLock() { - return (this.Document.isBackground !== undefined || this.isSelected(false)) && + return (this.Document._isBackground !== undefined || this.isSelected(false)) && ((this.Document.type === DocumentType.COL && this.Document._viewType !== CollectionViewType.Pile) || this.Document.type === DocumentType.IMG) && this.props.renderDepth > 0 && !this.props.treeViewDoc ? <div className="documentView-lock" onClick={this.toggleBackground}> - <FontAwesomeIcon icon={this.Document.isBackground ? "unlock" : "lock"} style={{ color: this.Document.isBackground ? "red" : undefined }} size="lg" /> + <FontAwesomeIcon icon={this.Document._isBackground ? "unlock" : "lock"} style={{ color: this.Document._isBackground ? "red" : undefined }} size="lg" /> </div> : (null); } @@ -997,7 +1048,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu const fullDegree = Doc.isBrushedHighlightedDegree(this.props.Document); const borderRounding = this.layoutDoc.borderRounding; const localScale = fullDegree; - const highlightColors = Cast(Doc.UserDoc().activeWorkspace, Doc, null)?.darkScheme ? + const highlightColors = CurrentUserUtils.ActiveDashboard?.darkScheme ? ["transparent", "#65350c", "#65350c", "yellow", "magenta", "cyan", "orange"] : ["transparent", "maroon", "maroon", "yellow", "magenta", "cyan", "orange"]; const highlightStyles = ["solid", "dashed", "solid", "solid", "solid", "solid", "solid"]; diff --git a/src/client/views/nodes/FaceRectangles.tsx b/src/client/views/nodes/FaceRectangles.tsx index 92ca276cb..0d1e063af 100644 --- a/src/client/views/nodes/FaceRectangles.tsx +++ b/src/client/views/nodes/FaceRectangles.tsx @@ -17,7 +17,7 @@ export interface RectangleTemplate { } @observer -export default class FaceRectangles extends React.Component<FaceRectanglesProps> { +export class FaceRectangles extends React.Component<FaceRectanglesProps> { render() { const faces = DocListCast(this.props.document.faces); diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx index 9d61ec6d1..fceeced36 100644 --- a/src/client/views/nodes/FieldView.tsx +++ b/src/client/views/nodes/FieldView.tsx @@ -29,6 +29,7 @@ export interface FieldViewProps { dropAction: dropActionType; backgroundHalo?: () => boolean; docFilters: () => string[]; + searchFilterDocs: () => Doc[]; isSelected: (outsideReaction?: boolean) => boolean; select: (isCtrlPressed: boolean) => void; rootSelected: (outsideReaction?: boolean) => boolean; @@ -58,9 +59,6 @@ export interface FieldViewProps { ChromeHeight?: () => number; childLayoutTemplate?: () => Opt<Doc>; - highlighting?: string[]; - lines?: string[]; - doc?: Doc; // properties intended to be used from within layout strings (otherwise use the function equivalents that work more efficiently with React) height?: number; width?: number; @@ -133,7 +131,7 @@ export class FieldView extends React.Component<FieldViewProps> { // ); } else if (field instanceof List) { - return <div> {field.map(f => Field.toString(f)).join(", ")} </div>; + return <div> {field.length ? field.map(f => Field.toString(f)).join(", ") : "[]"} </div>; } // bcz: this belongs here, but it doesn't render well so taking it out for now else if (field instanceof WebField) { diff --git a/src/client/views/nodes/FilterBox.scss b/src/client/views/nodes/FilterBox.scss new file mode 100644 index 000000000..b39793f01 --- /dev/null +++ b/src/client/views/nodes/FilterBox.scss @@ -0,0 +1,54 @@ + + +.filterBox-flyout { + width: 400px; + display: block; + text-align: left; + .filterBox-flyout-facet { + background-color: lightgray; + text-align: left; + display: inline-block; + position: relative; + width: 100%; + } +} +.filterBox-treeView { + display: flex; + flex-direction: column; + width: 200px; + height: 100%; + position: absolute; + right: 0; + top: 0; + border-left: solid 1px; + z-index: 1; + + .filterBox-addfacet { + display: inline-block; + width: 200px; + height: 30px; + background: darkGray; + text-align: left; + + .filterBox-addFacetButton { + display: flex; + margin: auto; + + .filterBox-span { + margin-right: 15px; + } + } + + >div, + >div>div { + width: 100%; + height: 100%; + } + } + + .filterBox-tree { + display: inline-block; + width: 100%; + height: calc(100% - 30px); + } +}
\ No newline at end of file diff --git a/src/client/views/nodes/FilterBox.tsx b/src/client/views/nodes/FilterBox.tsx new file mode 100644 index 000000000..790901a29 --- /dev/null +++ b/src/client/views/nodes/FilterBox.tsx @@ -0,0 +1,215 @@ +import React = require("react"); +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { computed } from "mobx"; +import { observer } from "mobx-react"; +import { DataSym, Doc, DocListCast, Field, Opt } from "../../../fields/Doc"; +import { documentSchema } from "../../../fields/documentSchemas"; +import { List } from "../../../fields/List"; +import { RichTextField } from "../../../fields/RichTextField"; +import { listSpec, makeInterface } from "../../../fields/Schema"; +import { ComputedField, ScriptField } from "../../../fields/ScriptField"; +import { Cast } from "../../../fields/Types"; +import { emptyFunction, emptyPath, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnOne, returnZero } from "../../../Utils"; +import { Docs } from "../../documents/Documents"; +import { DocumentType } from "../../documents/DocumentTypes"; +import { CollectionDockingView } from "../collections/CollectionDockingView"; +import { CollectionTreeView } from "../collections/CollectionTreeView"; +import { ViewBoxBaseComponent } from "../DocComponent"; +import { SearchBox } from "../search/SearchBox"; +import { FieldView, FieldViewProps } from './FieldView'; +import './FilterBox.scss'; +import { Scripting } from "../../util/Scripting"; +const higflyout = require("@hig/flyout"); +export const { anchorPoints } = higflyout; +export const Flyout = higflyout.default; + +type FilterBoxDocument = makeInterface<[typeof documentSchema]>; +const FilterBoxDocument = makeInterface(documentSchema); + +@observer +export class FilterBox extends ViewBoxBaseComponent<FieldViewProps, FilterBoxDocument>(FilterBoxDocument) { + public static LayoutString(fieldKey: string) { return FieldView.LayoutString(FilterBox, fieldKey); } + + @computed get allDocs() { + const allDocs = new Set<Doc>(); + if (CollectionDockingView.Instance) { + const activeTabs = DocListCast(CollectionDockingView.Instance.props.Document.data); + SearchBox.foreachRecursiveDoc(activeTabs, (doc: Doc) => allDocs.add(doc)); + setTimeout(() => CollectionDockingView.Instance.props.Document.allDocuments = new List<Doc>(Array.from(allDocs))); + } + return allDocs; + } + + @computed get _allFacets() { + const noviceReqFields = ["author", "tags", "text", "type"]; + const noviceLayoutFields: string[] = [];//["_curPage"]; + const noviceFields = [...noviceReqFields, ...noviceLayoutFields]; + + const keys = new Set<string>(noviceFields); + this.allDocs.forEach(doc => SearchBox.documentKeys(doc).filter(key => keys.add(key))); + return Array.from(keys.keys()).filter(key => key[0] === "#" || key.indexOf("lastModified") !== -1 || (key[0] === key[0].toUpperCase() && !key.startsWith("_") && !key.startsWith("ACL")) || noviceFields.includes(key)).sort(); + } + /** + * Responds to clicking the check box in the flyout menu + */ + facetClick = (facetHeader: string) => { + const targetDoc = CollectionDockingView.Instance.props.Document; + const found = DocListCast(this.dataDoc[this.props.fieldKey]).findIndex(doc => doc.title === facetHeader); + if (found !== -1) { + (this.dataDoc[this.props.fieldKey] as List<Doc>).splice(found, 1); + const docFilter = Cast(targetDoc._docFilters, listSpec("string")); + if (docFilter) { + let index: number; + while ((index = docFilter.findIndex(item => item === facetHeader)) !== -1) { + docFilter.splice(index, 3); + } + } + const docRangeFilters = Cast(targetDoc._docRangeFilters, listSpec("string")); + if (docRangeFilters) { + let index: number; + while ((index = docRangeFilters.findIndex(item => item === facetHeader)) !== -1) { + docRangeFilters.splice(index, 3); + } + } + } else { + const allCollectionDocs = DocListCast((targetDoc.data as any)[0].data); + var rtfields = 0; + const facetValues = Array.from(allCollectionDocs.reduce((set, child) => { + const field = child[facetHeader] as Field; + const fieldStr = Field.toString(field); + if (field instanceof RichTextField || (typeof (field) === "string" && fieldStr.split(" ").length > 2)) rtfields++; + return set.add(fieldStr); + }, new Set<string>())); + + let nonNumbers = 0; + let minVal = Number.MAX_VALUE, maxVal = -Number.MAX_VALUE; + facetValues.map(val => { + const num = Number(val); + if (Number.isNaN(num)) { + nonNumbers++; + } else { + minVal = Math.min(num, minVal); + maxVal = Math.max(num, maxVal); + } + }); + let newFacet: Opt<Doc>; + if (facetHeader === "text" || rtfields / allCollectionDocs.length > 0.1) { + newFacet = Docs.Create.TextDocument("", { _width: 100, _height: 25, treeViewExpandedView: "layout", title: facetHeader, treeViewOpen: true, forceActive: true, ignoreClick: true }); + Doc.GetProto(newFacet).type = DocumentType.COL; // forces item to show an open/close button instead ofa checkbox + newFacet._textBoxPadding = 4; + const scriptText = `setDocFilter(this?.target, "${facetHeader}", text, "match")`; + newFacet.onTextChanged = ScriptField.MakeScript(scriptText, { this: Doc.name, text: "string" }); + } else if (facetHeader !== "tags" && nonNumbers / facetValues.length < .1) { + newFacet = Docs.Create.SliderDocument({ title: facetHeader, treeViewExpandedView: "layout", treeViewOpen: true }); + const newFacetField = Doc.LayoutFieldKey(newFacet); + const ranged = Doc.readDocRangeFilter(targetDoc, facetHeader); + Doc.GetProto(newFacet).type = DocumentType.COL; // forces item to show an open/close button instead ofa checkbox + const extendedMinVal = minVal - Math.min(1, Math.abs(maxVal - minVal) * .05); + const extendedMaxVal = maxVal + Math.min(1, Math.abs(maxVal - minVal) * .05); + newFacet[newFacetField + "-min"] = ranged === undefined ? extendedMinVal : ranged[0]; + newFacet[newFacetField + "-max"] = ranged === undefined ? extendedMaxVal : ranged[1]; + Doc.GetProto(newFacet)[newFacetField + "-minThumb"] = extendedMinVal; + Doc.GetProto(newFacet)[newFacetField + "-maxThumb"] = extendedMaxVal; + const scriptText = `setDocFilterRange(this?.target, "${facetHeader}", range)`; + newFacet.onThumbChanged = ScriptField.MakeScript(scriptText, { this: Doc.name, range: "number" }); + } else { + newFacet = new Doc(); + newFacet.sytem = true; + newFacet.title = facetHeader; + newFacet.treeViewOpen = true; + newFacet.type = DocumentType.COL; + const capturedVariables = { layoutDoc: targetDoc, dataDoc: (targetDoc.data as any)[0][DataSym] }; + newFacet.data = ComputedField.MakeFunction(`readFacetData(layoutDoc, "${facetHeader}")`, {}, capturedVariables); + } + newFacet && Doc.AddDocToList(this.dataDoc, this.props.fieldKey, newFacet); + } + } + filterBackground = () => "rgba(105, 105, 105, 0.432)"; + get ignoreFields() { return ["_docFilters", "_docRangeFilters"]; } // this makes the tree view collection ignore these filters (otherwise, the filters would filter themselves) + @computed get scriptField() { + const scriptText = "setDocFilter(this?.target, heading, this.title, checked)"; + const script = ScriptField.MakeScript(scriptText, { this: Doc.name, heading: "string", checked: "string", containingTreeView: Doc.name }); + return script ? () => script : undefined; + } + + render() { + const facetCollection = this.props.Document.proto as Doc; + const flyout = <div className="filterBox-flyout" style={{ width: `100%`, height: this.props.PanelHeight() - 30 }} onWheel={e => e.stopPropagation()}> + {this._allFacets.map(facet => <label className="filterBox-flyout-facet" key={`${facet}`} onClick={e => this.facetClick(facet)}> + <input type="checkbox" onChange={e => { }} checked={DocListCast(this.props.Document[this.props.fieldKey]).some(d => d.title === facet)} /> + <span className="checkmark" /> + {facet} + </label>)} + </div>; + + return this.props.dontRegisterView ? (null) : <div className="filterBox-treeView" style={{ width: "100%" }}> + <div className="filterBox-addFacet" style={{ width: "100%" }} onPointerDown={e => e.stopPropagation()}> + <Flyout anchorPoint={anchorPoints.LEFT_TOP} content={flyout}> + <div className="filterBox-addFacetButton"> + <FontAwesomeIcon icon={"edit"} size={"lg"} /> + <span className="filterBox-span">Choose Facets</span> + </div> + </Flyout> + </div> + <div className="filterBox-tree" key="tree"> + <CollectionTreeView + PanelPosition={""} + Document={facetCollection} + DataDoc={Doc.GetProto(facetCollection)} + fieldKey={`${this.props.fieldKey}`} + CollectionView={undefined} + docFilters={returnEmptyFilter} + searchFilterDocs={returnEmptyDoclist} + ContainingCollectionDoc={this.props.ContainingCollectionDoc} + ContainingCollectionView={this.props.ContainingCollectionView} + PanelWidth={this.props.PanelWidth} + PanelHeight={this.props.PanelHeight} + NativeHeight={returnZero} + NativeWidth={returnZero} + LibraryPath={emptyPath} + rootSelected={this.props.rootSelected} + renderDepth={1} + dropAction={this.props.dropAction} + ScreenToLocalTransform={this.props.ScreenToLocalTransform} + addDocTab={returnFalse} + pinToPres={returnFalse} + isSelected={returnFalse} + select={returnFalse} + bringToFront={emptyFunction} + active={this.props.active} + whenActiveChanged={returnFalse} + treeViewHideTitle={true} + ContentScaling={returnOne} + focus={returnFalse} + treeViewHideHeaderFields={true} + onCheckedClick={this.scriptField} + ignoreFields={this.ignoreFields} + annotationsKey={""} + dontRegisterView={true} + backgroundColor={this.filterBackground} + moveDocument={returnFalse} + removeDocument={returnFalse} + addDocument={returnFalse} /> + </div> + </div>; + } +} + +Scripting.addGlobal(function readFacetData(layoutDoc: Doc, facetHeader: string) { + const allCollectionDocs = DocListCast(CollectionDockingView.Instance?.props.Document.allDocuments); + const set = new Set<string>(); + if (facetHeader === "tags") allCollectionDocs.forEach(child => Field.toString(child[facetHeader] as Field).split(":").forEach(key => set.add(key))); + else allCollectionDocs.forEach(child => set.add(Field.toString(child[facetHeader] as Field))); + let facetValues = Array.from(set).filter(v => v); + + let nonNumbers = 0; + facetValues.map(val => Number.isNaN(Number(val)) && nonNumbers++); + const facetValueDocSet = (nonNumbers / facetValues.length > .1 ? facetValues.sort() : facetValues.sort((n1: string, n2: string) => Number(n1) - Number(n2))).map(facetValue => { + const doc = new Doc(); + doc.system = true; + doc.title = facetValue.toString(); + doc.treeViewChecked = ComputedField.MakeFunction("determineCheckedState(layoutDoc, facetHeader, facetValue)", {}, { layoutDoc, facetHeader, facetValue }); + return doc; + }); + return new List<Doc>(facetValueDocSet); +});
\ No newline at end of file diff --git a/src/client/views/nodes/FontIconBox.scss b/src/client/views/nodes/FontIconBox.scss index 75bc90d7a..33ac85a0e 100644 --- a/src/client/views/nodes/FontIconBox.scss +++ b/src/client/views/nodes/FontIconBox.scss @@ -34,12 +34,14 @@ } } +.menuButton-circle, .menuButton-round { border-radius: 100%; background-color: black; + padding: 0; .fontIconBox-label { - margin-left: -10px; // button padding is 10px; + //margin-left: -10px; // button padding is 10px; bottom: 0; position: absolute; } @@ -52,7 +54,6 @@ .menuButton-square { padding-top: 3px; padding-bottom: 3px; - padding-left: 5px; .fontIconBox-label { border-radius: 0px; @@ -62,9 +63,11 @@ } .menuButton, +.menuButton-circle, .menuButton-round, .menuButton-square { - width: 100%; + margin-left: -5%; + width: 110%; height: 100%; pointer-events: all; touch-action: none; @@ -72,11 +75,7 @@ .menuButton-wrap { touch-action: none; border-radius: 8px; - - // &:hover { - // background: rgb(61, 61, 61); - // cursor: pointer; - // } + width: 100%; } .menuButton-icon-square { @@ -89,4 +88,13 @@ width: 95% !important; height: 95%; } +} +.menuButton-round { + width: 100%; + svg { + width: 50% !important; + height: 50%; + position: relative; + bottom: 2px; + } }
\ No newline at end of file diff --git a/src/client/views/nodes/FontIconBox.tsx b/src/client/views/nodes/FontIconBox.tsx index fd71876b0..a34bf64b0 100644 --- a/src/client/views/nodes/FontIconBox.tsx +++ b/src/client/views/nodes/FontIconBox.tsx @@ -39,7 +39,7 @@ export class FontIconBox extends DocComponent<FieldViewProps, FontIconDocument>( showTemplate = (): void => { const dragFactory = Cast(this.layoutDoc.dragFactory, Doc, null); - dragFactory && this.props.addDocTab(dragFactory, "onRight"); + dragFactory && this.props.addDocTab(dragFactory, "add:right"); } dragAsTemplate = (): void => { this.layoutDoc.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory, true)'); @@ -63,15 +63,18 @@ export class FontIconBox extends DocComponent<FieldViewProps, FontIconDocument>( const label = StrCast(this.rootDoc.label, StrCast(this.rootDoc.title)); const color = StrCast(this.layoutDoc.color, this._foregroundColor); const backgroundColor = StrCast(this.layoutDoc._backgroundColor, StrCast(this.rootDoc.backgroundColor, this.props.backgroundColor?.(this.rootDoc, this.props.renderDepth))); - const shape = StrCast(this.layoutDoc.iconShape, "round"); - + const shape = StrCast(this.layoutDoc.iconShape, label ? "round" : "circle"); + const icon = StrCast(this.dataDoc.icon, "user") as any; + const presSize = shape === 'round' ? 25 : 30; + const presTrailsIcon = <img src={`/assets/${"presTrails.png"}`} + style={{ width: presSize, height: presSize, filter: `invert(${color === "white" ? "100%" : "0%"})`, marginBottom: "5px" }} />; const button = <button className={`menuButton-${shape}`} ref={this._ref} onContextMenu={this.specificContextMenu} style={{ boxShadow: this.layoutDoc.ischecked ? `4px 4px 12px black` : undefined, backgroundColor: this.layoutDoc.iconShape === "square" ? backgroundColor : "", }}> <div className="menuButton-wrap"> - {<FontAwesomeIcon className={`menuButton-icon-${shape}`} icon={StrCast(this.dataDoc.icon, "user") as any} color={color} + {icon === 'pres-trail' ? presTrailsIcon : <FontAwesomeIcon className={`menuButton-icon-${shape}`} icon={icon} color={color} size={this.layoutDoc.iconShape === "square" ? "sm" : "lg"} />} {!label ? (null) : <div className="fontIconBox-label" style={{ color, backgroundColor }}> {label} </div>} {this.props.Document.watchedDocuments ? <FontIconBadge collection={Cast(this.props.Document.watchedDocuments, Doc, null)} /> : (null)} diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 216c5f39e..688bac725 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -1,8 +1,5 @@ -import { library } from '@fortawesome/fontawesome-svg-core'; -import { faEye } from '@fortawesome/free-regular-svg-icons'; -import { faAsterisk, faBrain, faFileAudio, faImage, faPaintBrush } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { action, computed, observable, runInAction } from 'mobx'; +import { action, computed, observable, runInAction, reaction, IReactionDisposer } from 'mobx'; import { observer } from "mobx-react"; import { DataSym, Doc, DocListCast, HeightSym, WidthSym } from '../../../fields/Doc'; import { documentSchema } from '../../../fields/documentSchemas'; @@ -14,7 +11,8 @@ import { ComputedField } from '../../../fields/ScriptField'; import { Cast, NumCast, StrCast } from '../../../fields/Types'; import { AudioField, ImageField } from '../../../fields/URLField'; import { TraceMobx } from '../../../fields/util'; -import { emptyFunction, returnOne, Utils, returnZero } from '../../../Utils'; +import { emptyFunction, returnOne, returnZero, Utils } from '../../../Utils'; +import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils'; import { CognitiveServices, Confidence, Service, Tag } from '../../cognitive_services/CognitiveServices'; import { Docs } from '../../documents/Documents'; import { Networking } from '../../Network'; @@ -24,22 +22,16 @@ import { ContextMenu } from "../../views/ContextMenu"; import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView'; import { ContextMenuProps } from '../ContextMenuItem'; import { ViewBoxAnnotatableComponent } from '../DocComponent'; -import FaceRectangles from './FaceRectangles'; +import { FaceRectangles } from './FaceRectangles'; import { FieldView, FieldViewProps } from './FieldView'; import "./ImageBox.scss"; import React = require("react"); -import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils'; -const requestImageSize = require('../../util/request-image-size'); const path = require('path'); const { Howl } = require('howler'); -library.add(faImage, faEye as any, faPaintBrush, faBrain); -library.add(faFileAudio, faAsterisk); - - export const pageSchema = createSchema({ - curPage: "number", + _curPage: "number", fitWidth: "boolean", googlePhotosUrl: "string", googlePhotosTags: "string" @@ -70,15 +62,25 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD public static LayoutString(fieldKey: string) { return FieldView.LayoutString(ImageBox, fieldKey); } private _imgRef: React.RefObject<HTMLImageElement> = React.createRef(); private _dropDisposer?: DragManager.DragDropDisposer; + private _pathDisposer?: IReactionDisposer; @observable private _audioState = 0; @observable static _showControls: boolean; @observable uploadIcon = uploadIcons.idle; protected createDropTarget = (ele: HTMLDivElement) => { - this._dropDisposer && this._dropDisposer(); + this._dropDisposer?.(); ele && (this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this), this.props.Document)); } + componentDidMount() { + this._pathDisposer = reaction(() => this.paths.length && this.resize(this.paths[0]), + () => true, + { fireImmediately: true }); + } + componentWillUnmount() { + this._pathDisposer?.(); + } + @undoBatch @action drop = (e: Event, de: DragManager.DropEvent) => { @@ -111,23 +113,17 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD }).then(function (stream) { gumStream = stream; recorder = new MediaRecorder(stream); - recorder.ondataavailable = async function (e: any) { - const formData = new FormData(); - formData.append("file", e.data); - const res = await fetch(Utils.prepend("/uploadFormData"), { - method: 'POST', - body: formData - }); - const files = await res.json(); - const url = Utils.prepend(files[0].path); - // upload to server with known URL - const audioDoc = Docs.Create.AudioDocument(url, { title: "audio test", _width: 200, _height: 32 }); - audioDoc.treeViewExpandedView = "layout"; - const audioAnnos = Cast(this.dataDoc[this.fieldKey + "-audioAnnotations"], listSpec(Doc)); - if (audioAnnos === undefined) { - this.dataDoc[this.fieldKey + "-audioAnnotations"] = new List([audioDoc]); - } else { - audioAnnos.push(audioDoc); + recorder.ondataavailable = async (e: any) => { + const [{ result }] = await Networking.UploadFilesToServer(e.data); + if (!(result instanceof Error)) { + const audioDoc = Docs.Create.AudioDocument(Utils.prepend(result.accessPaths.agnostic.client), { title: "audio test", _width: 200, _height: 32 }); + audioDoc.treeViewExpandedView = "layout"; + const audioAnnos = Cast(self.dataDoc[self.fieldKey + "-audioAnnotations"], listSpec(Doc)); + if (audioAnnos === undefined) { + self.dataDoc[self.fieldKey + "-audioAnnotations"] = new List([audioDoc]); + } else { + audioAnnos.push(audioDoc); + } } }; runInAction(() => self._audioState = 2); @@ -158,7 +154,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD if (field) { const funcs: ContextMenuProps[] = []; funcs.push({ description: "Rotate Clockwise 90", event: this.rotate, icon: "expand-arrows-alt" }); - funcs.push({ description: "Make Background", event: () => { this.layoutDoc.isBackground = true; this.props.bringToFront?.(this.rootDoc); }, icon: "expand-arrows-alt" }); + funcs.push({ description: "Make Background", event: () => { this.layoutDoc._isBackground = true; this.props.bringToFront?.(this.rootDoc); }, icon: "expand-arrows-alt" }); if (!Doc.UserDoc().noviceMode) { funcs.push({ description: "Export to Google Photos", event: () => GooglePhotos.Transactions.UploadImages([this.props.Document]), icon: "caret-square-right" }); funcs.push({ description: "Copy path", event: () => Utils.CopyText(field.url.href), icon: "expand-arrows-alt" }); @@ -255,29 +251,29 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD resize = (imgPath: string) => { const basePath = imgPath.replace(/_[oms]./, ""); + const curPath = this.dataDoc[this.fieldKey + "-path"]; const cachedNativeSize = { - width: basePath === this.dataDoc[this.fieldKey + "-path"] ? NumCast(this.dataDoc[this.fieldKey + "-nativeWidth"]) : 0, - height: basePath === this.dataDoc[this.fieldKey + "-path"] ? NumCast(this.dataDoc[this.fieldKey + "-nativeHeight"]) : 0, + width: basePath === curPath || !curPath ? NumCast(this.dataDoc[this.fieldKey + "-nativeWidth"]) : 0, + height: basePath === curPath || !curPath ? NumCast(this.dataDoc[this.fieldKey + "-nativeHeight"]) : 0, }; const docAspect = this.layoutDoc[HeightSym]() / this.layoutDoc[WidthSym](); const cachedAspect = cachedNativeSize.height / cachedNativeSize.width; if (!cachedNativeSize.width || !cachedNativeSize.height || Math.abs(NumCast(this.layoutDoc._width) / NumCast(this.layoutDoc._height) - cachedNativeSize.width / cachedNativeSize.height) > 0.05) { if (!this.layoutDoc.isTemplateDoc || this.dataDoc !== this.layoutDoc) { - requestImageSize(imgPath).then(action((inquiredSize: any) => { - const rotation = NumCast(this.dataDoc[this.fieldKey + "-rotation"]) % 180; - const rotatedNativeSize = { width: inquiredSize.width, height: inquiredSize.height }; - if (inquiredSize.orientation === 6 || rotation === 90 || rotation === 270) { - rotatedNativeSize.width = inquiredSize.height; - rotatedNativeSize.height = inquiredSize.width; - } - const rotatedAspect = rotatedNativeSize.height / rotatedNativeSize.width; - if (this.layoutDoc[WidthSym]() && (!cachedNativeSize.width || !cachedNativeSize.height || Math.abs(1 - docAspect / rotatedAspect) > 0.1)) { - this.layoutDoc._height = this.layoutDoc[WidthSym]() * rotatedAspect; - this.dataDoc[this.fieldKey + "-nativeWidth"] = this.layoutDoc._nativeWidth = this.layoutDoc._width; - this.dataDoc[this.fieldKey + "-nativeHeight"] = this.layoutDoc._nativeHeight = this.layoutDoc._height; - this.dataDoc[this.fieldKey + "-path"] = basePath; - } - })).catch(console.log); + const rotation = NumCast(this.dataDoc[this.fieldKey + "-rotation"]) % 180; + const orientation = NumCast(this.dataDoc[this.fieldKey + "-nativeOrientation"]); + if (orientation === 6 || rotation === 90 || rotation === 270) { + this.layoutDoc._nativeWidth = NumCast(this.dataDoc[this.fieldKey + "-nativeHeight"]); + this.layoutDoc._nativeHeight = NumCast(this.dataDoc[this.fieldKey + "-nativeWidth"]); + } else { + this.layoutDoc._nativeWidth = NumCast(this.dataDoc[this.fieldKey + "-nativeWidth"]); + this.layoutDoc._nativeHeight = NumCast(this.dataDoc[this.fieldKey + "-nativeHeight"]); + } + const rotatedAspect = NumCast(this.layoutDoc._nativeHeight) / NumCast(this.layoutDoc._nativeWidth); + if (this.layoutDoc[WidthSym]() && (!cachedNativeSize.width || !cachedNativeSize.height || Math.abs(1 - docAspect / rotatedAspect) > 0.1)) { + this.layoutDoc._height = this.layoutDoc[WidthSym]() * rotatedAspect; + this.dataDoc[this.fieldKey + "-path"] = basePath; + } } else if (Math.abs(1 - docAspect / cachedAspect) > 0.1) { this.layoutDoc._width = this.layoutDoc[WidthSym]() || cachedNativeSize.width; this.layoutDoc._height = this.layoutDoc[WidthSym]() * cachedAspect; @@ -398,8 +394,6 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD const { nativeWidth, nativeHeight } = this.nativeSize; const rotation = NumCast(this.dataDoc[this.fieldKey + "-rotation"]); const aspect = (rotation % 180) ? nativeHeight / nativeWidth : 1; - const shift = (rotation % 180) ? (nativeHeight - nativeWidth) * (1 - 1 / aspect) : 0; - this.resize(srcpath); let transformOrigin = "center center"; let transform = `translate(0%, 0%) rotate(${rotation}deg) scale(${aspect})`; if (rotation === 90 || rotation === -270) { @@ -437,7 +431,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD > <FontAwesomeIcon className="imageBox-audioFont" style={{ color: [DocListCast(this.dataDoc[this.fieldKey + "-audioAnnotations"]).length ? "blue" : "gray", "green", "red"][this._audioState] }} - icon={!DocListCast(this.dataDoc[this.fieldKey + "-audioAnnotations"]).length ? "microphone" : faFileAudio} size="sm" /> + icon={!DocListCast(this.dataDoc[this.fieldKey + "-audioAnnotations"]).length ? "microphone" : "file-audio"} size="sm" /> </div>} {this.considerDownloadIcon} {this.considerGooglePhotosLink()} @@ -465,7 +459,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD transform: this.props.PanelWidth() ? undefined : `scale(${this.props.ContentScaling()})`, width: this.props.PanelWidth() ? undefined : `${100 / this.props.ContentScaling()}%`, height: this.props.PanelWidth() ? undefined : `${100 / this.props.ContentScaling()}%`, - pointerEvents: this.layoutDoc.isBackground ? "none" : undefined, + pointerEvents: this.layoutDoc._isBackground ? "none" : undefined, borderRadius: `${Number(StrCast(this.layoutDoc.borderRounding).replace("px", "")) / this.props.ContentScaling()}px` }} > <CollectionFreeFormView {...this.props} @@ -489,6 +483,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD ScreenToLocalTransform={this.screenToLocalTransform} renderDepth={this.props.renderDepth + 1} docFilters={this.props.docFilters} + searchFilterDocs={this.props.searchFilterDocs} ContainingCollectionDoc={this.props.ContainingCollectionDoc}> {this.contentFunc} </CollectionFreeFormView> diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx index b732f5f83..c5ff42a1a 100644 --- a/src/client/views/nodes/KeyValueBox.tsx +++ b/src/client/views/nodes/KeyValueBox.tsx @@ -230,7 +230,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> { openItems.push({ description: "Default Perspective", event: () => { this.props.addDocTab(this.props.Document, "close"); - this.props.addDocTab(this.fieldDocToLayout, "onRight"); + this.props.addDocTab(this.fieldDocToLayout, "add:right"); }, icon: "image" }); !open && cm.addItem({ description: "Change Perspective...", subitems: openItems, icon: "external-link-alt" }); diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx index 4568a6b16..74d10d087 100644 --- a/src/client/views/nodes/KeyValuePair.tsx +++ b/src/client/views/nodes/KeyValuePair.tsx @@ -1,7 +1,7 @@ import { action, observable } from 'mobx'; import { observer } from "mobx-react"; import { Doc, Field, Opt } from '../../../fields/Doc'; -import { emptyFunction, returnFalse, returnOne, returnZero, returnEmptyFilter } from '../../../Utils'; +import { emptyFunction, returnFalse, returnOne, returnZero, returnEmptyFilter, returnEmptyDoclist } from '../../../Utils'; import { Docs } from '../../documents/Documents'; import { Transform } from '../../util/Transform'; import { undoBatch } from '../../util/UndoManager'; @@ -46,7 +46,7 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> { if (value instanceof Doc) { e.stopPropagation(); e.preventDefault(); - ContextMenu.Instance.addItem({ description: "Open Fields", event: () => this.props.addDocTab(Docs.Create.KVPDocument(value, { _width: 300, _height: 300 }), "onRight"), icon: "layer-group" }); + ContextMenu.Instance.addItem({ description: "Open Fields", event: () => this.props.addDocTab(Docs.Create.KVPDocument(value, { _width: 300, _height: 300 }), "add:right"), icon: "layer-group" }); ContextMenu.Instance.displayMenu(e.clientX, e.clientY); } } @@ -56,7 +56,8 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> { Document: this.props.doc, DataDoc: this.props.doc, LibraryPath: [], - docFilters:returnEmptyFilter, + docFilters: returnEmptyFilter, + searchFilterDocs: returnEmptyDoclist, ContainingCollectionView: undefined, ContainingCollectionDoc: undefined, fieldKey: this.props.keyName, diff --git a/src/client/views/nodes/LabelBox.scss b/src/client/views/nodes/LabelBox.scss index b605df262..109a02df4 100644 --- a/src/client/views/nodes/LabelBox.scss +++ b/src/client/views/nodes/LabelBox.scss @@ -8,6 +8,7 @@ } .labelBox-mainButton { + max-width: 100%; width: fit-content; height: max-content; border-radius: inherit; diff --git a/src/client/views/nodes/LinkAnchorBox.scss b/src/client/views/nodes/LinkAnchorBox.scss index 42ef2958e..62ee9513c 100644 --- a/src/client/views/nodes/LinkAnchorBox.scss +++ b/src/client/views/nodes/LinkAnchorBox.scss @@ -22,6 +22,11 @@ padding-left: 2px; padding-top: 1px; } + .linkAnchorBox-button { + pointer-events: all; + position: relative; + display: inline-block; + } } .linkAnchorBox-cont-small { diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx index 50b2af0d7..10b6aa02e 100644 --- a/src/client/views/nodes/LinkAnchorBox.tsx +++ b/src/client/views/nodes/LinkAnchorBox.tsx @@ -73,7 +73,7 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps, LinkAnch anchorContainerDoc && this.props.bringToFront(anchorContainerDoc, false); if (anchorContainerDoc && !this.layoutDoc.onClick && !this._isOpen) { this._timeout = setTimeout(action(() => { - DocumentManager.Instance.FollowLink(this.rootDoc, anchorContainerDoc, document => this.props.addDocTab(document, StrCast(this.layoutDoc.linkOpenLocation, e.altKey ? "inTab" : "onRight")), false); + DocumentManager.Instance.FollowLink(this.rootDoc, anchorContainerDoc, document => this.props.addDocTab(document, StrCast(this.layoutDoc.linkOpenLocation, "add:right")), false); this._editing = false; }), 300 - (Date.now() - this._lastTap)); } @@ -87,14 +87,14 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps, LinkAnch } openLinkDocOnRight = (e: React.MouseEvent) => { - this.props.addDocTab(this.rootDoc, "onRight"); + this.props.addDocTab(this.rootDoc, "add:right"); } openLinkTargetOnRight = (e: React.MouseEvent) => { const alias = Doc.MakeAlias(Cast(this.layoutDoc[this.fieldKey], Doc, null)); alias.isLinkButton = undefined; - alias.isBackground = undefined; + alias._isBackground = undefined; alias.layoutKey = "layout"; - this.props.addDocTab(alias, "onRight"); + this.props.addDocTab(alias, "add:right"); } @action openLinkEditor = action((e: React.MouseEvent) => { @@ -148,7 +148,7 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps, LinkAnch }} > {!this._editing && !this._forceOpen ? (null) : <Flyout anchorPoint={anchorPoints.LEFT_TOP} content={flyout} open={this._forceOpen ? true : undefined} onOpen={() => this._isOpen = true} onClose={action(() => this._isOpen = this._forceOpen = this._editing = false)}> - <span className="parentDocumentSelector-button" > + <span className="linkAnchorBox-button" > <FontAwesomeIcon icon={"eye"} size={"lg"} /> </span> </Flyout>} diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx index 532e7dc15..a067f23af 100644 --- a/src/client/views/nodes/LinkBox.tsx +++ b/src/client/views/nodes/LinkBox.tsx @@ -26,6 +26,8 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps, LinkDocument>( NativeWidth={returnZero} ignoreFields={Cast(this.props.Document.linkBoxExcludedKeys, listSpec("string"), null)} annotationsKey={""} + dontRegisterView={true} + renderDepth={this.props.renderDepth + 1} CollectionView={undefined} addDocument={returnFalse} removeDocument={returnFalse} diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx index c4481b213..dddefc17f 100644 --- a/src/client/views/nodes/LinkDocPreview.tsx +++ b/src/client/views/nodes/LinkDocPreview.tsx @@ -3,7 +3,7 @@ import { observer } from "mobx-react"; import wiki from "wikijs"; import { Doc, DocCastAsync, HeightSym, Opt, WidthSym } from "../../../fields/Doc"; import { Cast, FieldValue, NumCast } from "../../../fields/Types"; -import { emptyFunction, emptyPath, returnEmptyFilter, returnFalse, returnOne, returnZero } from "../../../Utils"; +import { emptyFunction, emptyPath, returnEmptyFilter, returnFalse, returnOne, returnZero, returnEmptyDoclist } from "../../../Utils"; import { Docs } from "../../documents/Documents"; import { DocumentManager } from "../../util/DocumentManager"; import { Transform } from "../../util/Transform"; @@ -22,6 +22,7 @@ interface Props { } @observer export class LinkDocPreview extends React.Component<Props> { + static TargetDoc: Doc | undefined; @observable public static LinkInfo: Opt<{ linkDoc?: Doc; addDocTab: (document: Doc, where: string) => boolean, linkSrc: Doc; href?: string; Location: number[] }>; @observable _targetDoc: Opt<Doc>; @observable _toolTipText = ""; @@ -40,14 +41,16 @@ export class LinkDocPreview extends React.Component<Props> { async followDefault() { DocumentLinksButton.EditLink = undefined; LinkDocPreview.LinkInfo = undefined; - this._targetDoc ? DocumentManager.Instance.FollowLink(this.props.linkDoc, this._targetDoc, doc => this.props.addDocTab(doc, "onRight"), false) : null; + this._targetDoc ? DocumentManager.Instance.FollowLink(this.props.linkDoc, this._targetDoc, doc => this.props.addDocTab(doc, "add:right"), false) : null; } + componentWillUnmount() { LinkDocPreview.TargetDoc = undefined; } componentDidUpdate() { this.updatePreview(); } componentDidMount() { this.updatePreview(); } async updatePreview() { const linkDoc = this.props.linkDoc; const linkSrc = this.props.linkSrc; + LinkDocPreview.TargetDoc = undefined; if (this.props.href) { if (this.props.href.startsWith("https://en.wikipedia.org/wiki/")) { wiki().page(this.props.href.replace("https://en.wikipedia.org/wiki/", "")).then(page => page.summary().then(action(summary => this._toolTipText = summary.substring(0, 500)))); @@ -59,7 +62,7 @@ export class LinkDocPreview extends React.Component<Props> { const target = anchor?.annotationOn ? await DocCastAsync(anchor.annotationOn) : anchor; runInAction(() => { this._toolTipText = ""; - this._targetDoc = target; + LinkDocPreview.TargetDoc = this._targetDoc = target; if (anchor !== this._targetDoc && anchor && this._targetDoc) { this._targetDoc._scrollY = NumCast(anchor?.y); } @@ -69,13 +72,13 @@ export class LinkDocPreview extends React.Component<Props> { pointerDown = (e: React.PointerEvent) => { if (this.props.linkDoc && this.props.linkSrc) { DocumentManager.Instance.FollowLink(this.props.linkDoc, this.props.linkSrc, - (doc: Doc, followLinkLocation: string) => this.props.addDocTab(doc, e.ctrlKey ? "inTab" : followLinkLocation)); + (doc: Doc, followLinkLocation: string) => this.props.addDocTab(doc, e.ctrlKey ? "add" : followLinkLocation)); } else if (this.props.href) { - this.props.addDocTab(Docs.Create.WebDocument(this.props.href, { title: this.props.href, _width: 200, _height: 400, UseCors: true }), "onRight"); + this.props.addDocTab(Docs.Create.WebDocument(this.props.href, { title: this.props.href, _width: 200, _height: 400, useCors: true }), "add:right"); } } - width = () => Math.min(225, NumCast(this._targetDoc?.[WidthSym](), 225)); - height = () => Math.min(225, NumCast(this._targetDoc?.[HeightSym](), 225)); + width = () => Math.min(225, NumCast(this._targetDoc?.[WidthSym](), 225)) - 16; + height = () => Math.min(225, NumCast(this._targetDoc?.[HeightSym](), 225)) - 16 @computed get targetDocView() { return !this._targetDoc ? <div style={{ @@ -101,11 +104,12 @@ export class LinkDocPreview extends React.Component<Props> { pinToPres={returnFalse} dontRegisterView={true} docFilters={returnEmptyFilter} + searchFilterDocs={returnEmptyDoclist} ContainingCollectionDoc={undefined} ContainingCollectionView={undefined} renderDepth={0} - PanelWidth={() => this.width() - 16} //Math.min(350, NumCast(target._width, 350))} - PanelHeight={() => this.height() - 16} //Math.min(250, NumCast(target._height, 250))} + PanelWidth={this.width} //Math.min(350, NumCast(target._width, 350))} + PanelHeight={this.height} //Math.min(250, NumCast(target._height, 250))} focus={emptyFunction} whenActiveChanged={returnFalse} bringToFront={returnFalse} @@ -119,7 +123,7 @@ export class LinkDocPreview extends React.Component<Props> { return <div className="linkDocPreview" style={{ position: "absolute", left: this.props.location[0], - top: this.props.location[1], width: this.width(), height: this.height(), + top: this.props.location[1], width: this.width() + 16, height: this.height() + 16, zIndex: 1000, border: "8px solid white", borderRadius: "7px", boxShadow: "3px 3px 1.5px grey", diff --git a/src/client/views/nodes/MenuIconBox.scss b/src/client/views/nodes/MenuIconBox.scss deleted file mode 100644 index 1b72f5a8f..000000000 --- a/src/client/views/nodes/MenuIconBox.scss +++ /dev/null @@ -1,49 +0,0 @@ -.menuButton { - //padding: 7px; - padding-left: 7px; - width: 100%; - width: 60px; - height: 70px; - - .menuButton-wrap { - width: 45px; - /* padding: 5px; */ - touch-action: none; - background: black; - transform-origin: top left; - /* margin-bottom: 5px; */ - margin-top: 5px; - margin-right: 25px; - border-radius: 8px; - - &:hover { - background: rgb(61, 61, 61); - cursor: pointer; - } - } - - .menuButton-label { - color: white; - margin-right: 4px; - border-radius: 8px; - width: 42px; - position: relative; - text-align: center; - font-size: 8px; - margin-top: 1px; - letter-spacing: normal; - padding: 3px; - background-color: inherit; - } - - .menuButton-icon { - width: auto; - height: 35px; - padding: 5px; - } - - svg { - width: 95% !important; - height: 95%; - } -}
\ No newline at end of file diff --git a/src/client/views/nodes/MenuIconBox.tsx b/src/client/views/nodes/MenuIconBox.tsx deleted file mode 100644 index 5ed8a9b78..000000000 --- a/src/client/views/nodes/MenuIconBox.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { observer } from 'mobx-react'; -import * as React from 'react'; -import { createSchema, makeInterface } from '../../../fields/Schema'; -import { StrCast } from '../../../fields/Types'; -import { DocComponent } from '../DocComponent'; -import { FieldView, FieldViewProps } from './FieldView'; -import './MenuIconBox.scss'; -const MenuIconSchema = createSchema({ - icon: "string" -}); - -type MenuIconDocument = makeInterface<[typeof MenuIconSchema]>; -const MenuIconDocument = makeInterface(MenuIconSchema); -@observer -export class MenuIconBox extends DocComponent<FieldViewProps, MenuIconDocument>(MenuIconDocument) { - public static LayoutString(fieldKey: string) { return FieldView.LayoutString(MenuIconBox, fieldKey); } - _ref: React.RefObject<HTMLButtonElement> = React.createRef(); - - render() { - - const color = this.props.backgroundColor?.(this.props.Document, this.props.renderDepth) === "lightgrey" ? "black" : "white"; - const menuBTN = <div className="menuButton" style={{ backgroundColor: this.props.backgroundColor?.(this.props.Document, this.props.renderDepth) }}> - <div className="menuButton-wrap" - style={{ backgroundColor: this.props.backgroundColor?.(this.props.Document, this.props.renderDepth) }} > - <FontAwesomeIcon className="menuButton-icon" icon={StrCast(this.dataDoc.icon, "user") as any} color={color} size="lg" /> - <div className="menuButton-label" style={{ color: color }}> {this.dataDoc.title} </div> - </div> - </div>; - - return menuBTN; - } -}
\ No newline at end of file diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 255a1b2d0..a2b406c3f 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -21,6 +21,7 @@ import { KeyCodes } from '../../util/KeyCodes'; import "./PDFBox.scss"; import React = require("react"); import { documentSchema } from '../../../fields/documentSchemas'; +import { CollectionViewType } from '../collections/CollectionView'; type PdfDocument = makeInterface<[typeof documentSchema, typeof panZoomSchema, typeof pageSchema]>; const PdfDocument = makeInterface(documentSchema, panZoomSchema, pageSchema); @@ -99,20 +100,28 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps, PdfDocum public search = (string: string, fwd: boolean) => { this._pdfViewer?.search(string, fwd); }; public prevAnnotation = () => { this._pdfViewer?.prevAnnotation(); }; public nextAnnotation = () => { this._pdfViewer?.nextAnnotation(); }; - public backPage = () => { this._pdfViewer!.gotoPage((this.Document.curPage || 1) - 1); }; - public forwardPage = () => { this._pdfViewer!.gotoPage((this.Document.curPage || 1) + 1); }; - public gotoPage = (p: number) => { this._pdfViewer!.gotoPage(p); }; + public backPage = () => { this.Document._curPage = (this.Document._curPage || 1) - 1; return true; }; + public forwardPage = () => { this.Document._curPage = (this.Document._curPage || 1) + 1; return true; }; + public gotoPage = (p: number) => { this.Document._curPage = p; }; @undoBatch onKeyDown = action((e: KeyboardEvent) => { + let processed = false; if (e.key === "f" && e.ctrlKey) { this._searching = true; setTimeout(() => this._searchRef.current && this._searchRef.current.focus(), 100); + processed = true; + } + if (e.key === "PageDown") processed = this.forwardPage(); + if (e.key === "PageUp") processed = this.backPage(); + if (e.target instanceof HTMLInputElement || this.props.ContainingCollectionDoc?._viewType !== CollectionViewType.Freeform) { + if (e.key === "ArrowDown" || e.key === "ArrowRight") processed = this.forwardPage(); + if (e.key === "ArrowUp" || e.key === "ArrowLeft") processed = this.backPage(); + } + if (processed) { e.stopImmediatePropagation(); e.preventDefault(); } - if (e.key === "PageDown" || e.key === "ArrowDown" || e.key === "ArrowRight") this.forwardPage(); - if (e.key === "PageUp" || e.key === "ArrowUp" || e.key === "ArrowLeft") this.backPage(); }); @undoBatch @@ -150,7 +159,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps, PdfDocum </button> </>; const searchTitle = `${!this._searching ? "Open" : "Close"} Search Bar`; - const curPage = this.Document.curPage || 1; + const curPage = this.Document._curPage || 1; return !this.active() ? (null) : (<div className="pdfBox-ui" onKeyDown={e => e.keyCode === KeyCodes.BACKSPACE || e.keyCode === KeyCodes.DELETE ? e.stopPropagation() : true} onPointerDown={e => e.stopPropagation()} style={{ display: this.active() ? "flex" : "none" }}> @@ -177,7 +186,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps, PdfDocum <div className="pdfBox-pageNums"> <input value={curPage} - onChange={e => this.gotoPage(Number(e.currentTarget.value))} + onChange={e => this.Document._curPage = Number(e.currentTarget.value)} style={{ width: `${curPage > 99 ? 4 : 3}ch`, pointerEvents: "all" }} onClick={action(() => this._pageControls = !this._pageControls)} /> {this._pageControls ? pageBtns : (null)} @@ -246,7 +255,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps, PdfDocum <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} - addDocTab={this.props.addDocTab} focus={this.props.focus} docFilters={this.props.docFilters} + addDocTab={this.props.addDocTab} focus={this.props.focus} docFilters={this.props.docFilters} searchFilterDocs={this.props.searchFilterDocs} pinToPres={this.props.pinToPres} addDocument={this.addDocument} Document={this.props.Document} DataDoc={this.dataDoc} ContentScaling={this.props.ContentScaling} ScreenToLocalTransform={this.props.ScreenToLocalTransform} select={this.props.select} diff --git a/src/client/views/nodes/PresBox.scss b/src/client/views/nodes/PresBox.scss index c4d8f1a4f..08160a2f4 100644 --- a/src/client/views/nodes/PresBox.scss +++ b/src/client/views/nodes/PresBox.scss @@ -488,7 +488,7 @@ $light-background: #ececec; max-width: 200px; overflow: visible; - + .presBox-dropdownOption { cursor: pointer; font-size: 11; @@ -967,10 +967,10 @@ $light-background: #ececec; /* border: solid 1px; */ color: white; /* box-shadow: black 0.4vw 0.4vw 0.8vw; */ - + .miniPresOverlay { display: grid; - grid-template-columns: auto auto auto auto auto auto auto auto; + grid-template-columns: auto auto auto auto auto auto auto auto auto auto; grid-template-rows: 100%; height: 100%; justify-items: center; @@ -1028,4 +1028,4 @@ $light-background: #ececec; background-color: #5a5a5a; } } -} +}
\ No newline at end of file diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx index 5fc76223d..07b2d51d1 100644 --- a/src/client/views/nodes/PresBox.tsx +++ b/src/client/views/nodes/PresBox.tsx @@ -1,34 +1,33 @@ import React = require("react"); import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { Tooltip } from "@material-ui/core"; import { action, computed, observable, runInAction } from "mobx"; import { observer } from "mobx-react"; -import { Doc, DocListCast, DocCastAsync, WidthSym } from "../../../fields/Doc"; -import { InkTool } from "../../../fields/InkField"; -import { BoolCast, Cast, NumCast, StrCast, ScriptCast } from "../../../fields/Types"; -import { returnFalse, returnOne, numberRange, returnTrue } from "../../../Utils"; +import { ColorState, SketchPicker } from "react-color"; +import { Doc, DocCastAsync, DocListCast } from "../../../fields/Doc"; import { documentSchema } from "../../../fields/documentSchemas"; -import { DocumentManager } from "../../util/DocumentManager"; -import { undoBatch } from "../../util/UndoManager"; -import { CollectionDockingView, DockedFrameRenderer } from "../collections/CollectionDockingView"; -import { CollectionView, CollectionViewType } from "../collections/CollectionView"; -import { FieldView, FieldViewProps } from './FieldView'; -import { DocumentType } from "../../documents/DocumentTypes"; -import "./PresBox.scss"; -import { ViewBoxBaseComponent } from "../DocComponent"; -import { makeInterface, listSpec } from "../../../fields/Schema"; -import { Docs, DocUtils } from "../../documents/Documents"; +import { InkTool } from "../../../fields/InkField"; +import { List } from "../../../fields/List"; import { PrefetchProxy } from "../../../fields/Proxy"; +import { listSpec, makeInterface } from "../../../fields/Schema"; import { ScriptField } from "../../../fields/ScriptField"; +import { Cast, NumCast, StrCast } from "../../../fields/Types"; +import { returnFalse, returnOne } from "../../../Utils"; +import { Docs } from "../../documents/Documents"; +import { DocumentType } from "../../documents/DocumentTypes"; +import { CurrentUserUtils } from "../../util/CurrentUserUtils"; +import { DocumentManager } from "../../util/DocumentManager"; import { Scripting } from "../../util/Scripting"; -import { CollectionFreeFormDocumentView } from "./CollectionFreeFormDocumentView"; -import { List } from "../../../fields/List"; -import { Tooltip } from "@material-ui/core"; -import { actionAsync } from "mobx-utils"; import { SelectionManager } from "../../util/SelectionManager"; +import { undoBatch } from "../../util/UndoManager"; +import { CollectionDockingView } from "../collections/CollectionDockingView"; +import { CollectionView, CollectionViewType } from "../collections/CollectionView"; +import { TabDocView } from "../collections/TabDocView"; +import { ViewBoxBaseComponent } from "../DocComponent"; import { AudioBox } from "./AudioBox"; -import { DocumentView } from "./DocumentView"; -import { SketchPicker, ColorState } from "react-color"; -import { CurrentUserUtils } from "../../util/CurrentUserUtils"; +import { CollectionFreeFormDocumentView } from "./CollectionFreeFormDocumentView"; +import { FieldView, FieldViewProps } from './FieldView'; +import "./PresBox.scss"; type PresBoxSchema = makeInterface<[typeof documentSchema]>; const PresBoxDocument = makeInterface(documentSchema); @@ -58,9 +57,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> @observable private expandBoolean: boolean = false; @observable private openMovementDropdown: boolean = false; @observable private openEffectDropdown: boolean = false; - @computed get childDocs() { return DocListCast(this.dataDoc[this.fieldKey]); } @computed get itemIndex() { return NumCast(this.rootDoc._itemIndex); } + @computed get activeItem() { return Cast(this.childDocs[this.itemIndex], Doc, null); } + @computed get targetDoc() { return Cast(this.activeItem?.presentationTargetDoc, Doc, null); } @computed get presElement() { return Cast(Doc.UserDoc().presElement, Doc, null); } constructor(props: any) { super(props); @@ -86,14 +86,12 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> } else { return undefined; } } @computed get isPres(): boolean { + document.removeEventListener("keydown", this.keyEvents, true); if (this.selectedDoc?.type === DocumentType.PRES) { - document.removeEventListener("keydown", this.keyEvents, true); document.addEventListener("keydown", this.keyEvents, true); return true; - } else { - document.removeEventListener("keydown", this.keyEvents, true); - return false; } + return false; } @computed get selectedDoc() { return this.selectedDocumentView?.rootDoc; } @@ -108,6 +106,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> this.rootDoc._replacedChrome = "replaced"; this.layoutDoc.presStatus = "edit"; this.layoutDoc._gridGap = 5; + if (!DocListCast((Doc.UserDoc().myPresentations as Doc).data).includes(this.rootDoc)) { + Doc.AddDocToList(Doc.UserDoc().myPresentations as Doc, "data", this.rootDoc); + } } updateCurrentPresentation = () => { @@ -123,37 +124,40 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> next = () => { this.updateCurrentPresentation(); const activeNext = Cast(this.childDocs[this.itemIndex + 1], Doc, null); - const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); - const presTargetDoc = Cast(activeItem.presentationTargetDoc, Doc, null); - const childDocs = DocListCast(presTargetDoc[Doc.LayoutFieldKey(presTargetDoc)]); - const currentFrame = Cast(presTargetDoc.currentFrame, "number", null); - const lastFrame = Cast(presTargetDoc.lastFrame, "number", null); - const curFrame = NumCast(presTargetDoc.currentFrame); + const activeItem: Doc = this.activeItem; + const targetDoc: Doc = this.targetDoc; + const childDocs = DocListCast(targetDoc[Doc.LayoutFieldKey(targetDoc)]); + const currentFrame = Cast(targetDoc._currentFrame, "number", null); + const lastFrame = Cast(targetDoc.lastFrame, "number", null); + const curFrame = NumCast(targetDoc._currentFrame); let internalFrames: boolean = false; - if (presTargetDoc.presProgressivize || activeItem.zoomProgressivize || presTargetDoc.scrollProgressivize) internalFrames = true; + if (targetDoc.presProgressivize || activeItem.zoomProgressivize || targetDoc.scrollProgressivize) internalFrames = true; // Case 1: There are still other frames and should go through all frames before going to next slide if (internalFrames && lastFrame !== undefined && curFrame < lastFrame) { - presTargetDoc._viewTransition = "all 1s"; - setTimeout(() => presTargetDoc._viewTransition = undefined, 1010); - presTargetDoc.currentFrame = curFrame + 1; - if (presTargetDoc.scrollProgressivize) CollectionFreeFormDocumentView.updateScrollframe(presTargetDoc, currentFrame); - if (presTargetDoc.presProgressivize) CollectionFreeFormDocumentView.updateKeyframe(childDocs, currentFrame || 0, presTargetDoc); - else presTargetDoc.editing = true; - if (activeItem.zoomProgressivize) this.zoomProgressivizeNext(presTargetDoc); + targetDoc._viewTransition = "all 1s"; + setTimeout(() => targetDoc._viewTransition = undefined, 1010); + targetDoc._currentFrame = curFrame + 1; + if (targetDoc.scrollProgressivize) CollectionFreeFormDocumentView.updateScrollframe(targetDoc, currentFrame); + if (targetDoc.presProgressivize) CollectionFreeFormDocumentView.updateKeyframe(childDocs, currentFrame || 0, targetDoc); + else targetDoc.editing = true; + if (activeItem.zoomProgressivize) this.zoomProgressivizeNext(targetDoc); // Case 2: Audio or video therefore wait to play the audio or video before moving on - } else if ((presTargetDoc.type === DocumentType.AUDIO) && !this._moveOnFromAudio && this.layoutDoc.presStatus !== 'auto') { + } else if ((targetDoc.type === DocumentType.AUDIO) && !this._moveOnFromAudio && this.layoutDoc.presStatus !== 'auto') { AudioBox.Instance.playFrom(0); this._moveOnFromAudio = true; // Case 3: No more frames in current doc and next slide is defined, therefore move to next slide } else if (this.childDocs[this.itemIndex + 1] !== undefined) { const nextSelected = this.itemIndex + 1; - if (presTargetDoc.type === DocumentType.AUDIO) AudioBox.Instance.pause(); + if (targetDoc.type === DocumentType.AUDIO) AudioBox.Instance.pause(); this.gotoDocument(nextSelected, this.itemIndex); const targetNext = Cast(activeNext.presentationTargetDoc, Doc, null); if (activeNext && targetNext.type === DocumentType.AUDIO && activeNext.playAuto) { AudioBox.Instance.playFrom(0); this._moveOnFromAudio = true; } else this._moveOnFromAudio = false; + } else if (this.childDocs[this.itemIndex + 1] === undefined && this.layoutDoc.presLoop) { + const nextSelected = 0; + this.gotoDocument(0, this.itemIndex); } } @@ -166,19 +170,19 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> @action back = () => { this.updateCurrentPresentation(); - const docAtCurrent = this.childDocs[this.itemIndex]; - const targetDoc = Cast(docAtCurrent.presentationTargetDoc, Doc, null); + const activeItem: Doc = this.activeItem; + const targetDoc: Doc = this.targetDoc; const prevItem = Cast(this.childDocs[Math.max(0, this.itemIndex - 1)], Doc, null); const prevTargetDoc = Cast(prevItem.presentationTargetDoc, Doc, null); const lastFrame = Cast(targetDoc.lastFrame, "number", null); - const curFrame = NumCast(targetDoc.currentFrame); + const curFrame = NumCast(targetDoc._currentFrame); if (lastFrame !== undefined && curFrame >= 1) { - this.prevKeyframe(targetDoc, docAtCurrent); - } else if (docAtCurrent) { + this.prevKeyframe(targetDoc, activeItem); + } else if (activeItem) { let prevSelected = this.itemIndex; prevSelected = Math.max(0, prevSelected - 1); this.gotoDocument(prevSelected, this.itemIndex); - if (NumCast(prevTargetDoc.lastFrame) > 0) prevTargetDoc.currentFrame = NumCast(prevTargetDoc.lastFrame); + if (NumCast(prevTargetDoc.lastFrame) > 0) prevTargetDoc._currentFrame = NumCast(prevTargetDoc.lastFrame); } } @@ -209,8 +213,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> * on the right. */ navigateToElement = async (curDoc: Doc) => { - const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); - const targetDoc = Cast(activeItem.presentationTargetDoc, Doc, null); + const activeItem: Doc = this.activeItem; + const targetDoc: Doc = this.targetDoc; const srcContext = await DocCastAsync(targetDoc.context); const presCollection = Cast(this.layoutDoc.presCollection, Doc, null); const collectionDocView = presCollection ? await DocumentManager.Instance.getDocumentView(presCollection) : undefined; @@ -233,7 +237,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> // If openDocument is selected then it should open the document for the user if (activeItem.openDocument) { - this.props.addDocTab(activeItem, "replace"); + this.props.addDocTab(activeItem, "replace:right"); } else //docToJump stayed same meaning, it was not in the group or was the last element in the group if (activeItem.zoomProgressivize && this.rootDoc.presStatus !== 'edit') { @@ -254,9 +258,15 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> // adjust the pan and scale to that of the pinView when it was added. // TODO: Add option to remove presPinView if (activeItem.presPinView) { - targetDoc._panX = activeItem.presPinViewX; - targetDoc._panY = activeItem.presPinViewY; - targetDoc._viewScale = activeItem.presPinViewScale; + // if targetDoc is not displayed but one of its aliases is, then we need to modify that alias, not the original target + const bestTarget = DocumentManager.Instance.getFirstDocumentView(targetDoc)?.props.Document; + bestTarget && runInAction(() => { + bestTarget._viewTransition = "all 1s"; + bestTarget._panX = activeItem.presPinViewX; + bestTarget._panY = activeItem.presPinViewY; + bestTarget._viewScale = activeItem.presPinViewScale; + }); + //setTimeout(() => targetDoc._viewTransition = undefined, 1010); } // If website and has presWebsite data associated then on click it should // go back to that specific website @@ -271,7 +281,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> * @param presTargetDoc: document for which internal zoom is used */ zoomProgressivizeNext = (activeItem: Doc) => { - const targetDoc = Cast(activeItem.presentationTargetDoc, Doc, null); + const targetDoc: Doc = this.targetDoc; const srcContext = Cast(targetDoc?.context, Doc, null); const docView = DocumentManager.Instance.getDocumentView(targetDoc); const vfLeft = this.checkList(targetDoc, activeItem["viewfinder-left-indexed"]); @@ -346,8 +356,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> @action startAutoPres = (startSlide: number) => { this.updateCurrentPresentation(); - let activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); - let targetDoc = Cast(activeItem.presentationTargetDoc, Doc, null); + let activeItem: Doc = this.activeItem; + let targetDoc: Doc = this.targetDoc; let duration = NumCast(targetDoc.presDuration) + NumCast(targetDoc.presTransition); const timer = (ms: number) => new Promise(res => this._presTimer = setTimeout(res, ms)); const load = async () => { // Wrap the loop into an async function for this to work @@ -363,17 +373,27 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> } } await timer(duration); this.next(); // then the created Promise can be awaited - if (i === this.childDocs.length - 1) setTimeout(() => { clearTimeout(this._presTimer); if (this.layoutDoc.presStatus === 'auto') this.layoutDoc.presStatus = "manual"; }, duration); + if (i === this.childDocs.length - 1) { + setTimeout(() => { + clearTimeout(this._presTimer); + if (this.layoutDoc.presStatus === 'auto' && !this.layoutDoc.presLoop) this.layoutDoc.presStatus = "manual"; + else if (this.layoutDoc.presLoop) this.startAutoPres(0); + }, duration); + } } }; + this.layoutDoc.presStatus = "auto"; + this.startPresentation(startSlide); + this.gotoDocument(startSlide, this.itemIndex); + load(); + } + + @action + pauseAutoPres = () => { if (this.layoutDoc.presStatus === "auto") { if (this._presTimer) clearTimeout(this._presTimer); this.layoutDoc.presStatus = "manual"; - } else { - this.layoutDoc.presStatus = "auto"; - this.startPresentation(startSlide); - this.gotoDocument(startSlide, this.itemIndex); - load(); + this.layoutDoc.presLoop = false; } } @@ -401,12 +421,12 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> startPresentation = (startIndex: number) => { this.updateCurrentPresentation(); this.childDocs.map(doc => { - const presTargetDoc = doc.presentationTargetDoc as Doc; + const tagDoc = doc.presentationTargetDoc as Doc; if (doc.presHideTillShownButton && this.childDocs.indexOf(doc) > startIndex) { - presTargetDoc.opacity = 0; + tagDoc.opacity = 0; } if (doc.presHideAfterButton && this.childDocs.indexOf(doc) < startIndex) { - presTargetDoc.opacity = 0; + tagDoc.opacity = 0; } }); } @@ -418,11 +438,21 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> @undoBatch @action updateMinimize = () => { + const docView = DocumentManager.Instance.getDocumentView(this.layoutDoc); if (this.layoutDoc.inOverlay) { this.layoutDoc.presStatus = 'edit'; - Doc.RemoveDocFromList((Doc.UserDoc().myOverlayDocuments as Doc), undefined, this.rootDoc); - CollectionDockingView.AddRightSplit(this.rootDoc); + Doc.RemoveDocFromList((Doc.UserDoc().myOverlayDocs as Doc), undefined, this.rootDoc); + CollectionDockingView.AddSplit(this.rootDoc, "right"); this.layoutDoc.inOverlay = false; + } else if (this.layoutDoc.context && docView) { + this.layoutDoc.presStatus = 'manual'; + const pt = this.props.ScreenToLocalTransform().inverse().transformPoint(0, 0); + this.rootDoc.x = pt[0] + (this.props.PanelWidth() - 250); + this.rootDoc.y = pt[1] + 10; + this.rootDoc._height = 35; + this.rootDoc._width = 250; + docView.props.removeDocument?.(this.layoutDoc); + Doc.AddDocToList((Doc.UserDoc().myOverlayDocs as Doc), undefined, this.rootDoc); } else { this.layoutDoc.presStatus = 'manual'; const pt = this.props.ScreenToLocalTransform().inverse().transformPoint(0, 0); @@ -431,7 +461,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> this.rootDoc._height = 35; this.rootDoc._width = 250; this.props.addDocTab?.(this.rootDoc, "close"); - Doc.AddDocToList((Doc.UserDoc().myOverlayDocuments as Doc), undefined, this.rootDoc); + Doc.AddDocToList((Doc.UserDoc().myOverlayDocs as Doc), undefined, this.rootDoc); } } @@ -453,31 +483,32 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> * When the movement dropdown is changes */ @undoBatch - movementChanged = action((movement: string) => { - const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); - const targetDoc = Cast(activeItem.presentationTargetDoc, Doc, null); + updateMovement = action((movement: any, activeItem: Doc, targetDoc: Doc) => { switch (movement) { case 'zoom': //Pan and zoom - activeItem.presZoomButton = !activeItem.presZoomButton; - if (activeItem.presZoomButton) activeItem.presMovement = 'Zoom'; - else activeItem.presMovement = 'None'; activeItem.presNavButton = false; + activeItem.presZoomButton = !activeItem.presZoomButton; + targetDoc.presTransition = activeItem.presTransition; + if (activeItem.presZoomButton) activeItem.presMovement = 'zoom'; + else activeItem.presMovement = 'none'; break; case 'pan': //Pan activeItem.presZoomButton = false; activeItem.presNavButton = !activeItem.presNavButton; - if (activeItem.presNavButton) activeItem.presMovement = 'Pan'; - else activeItem.presMovement = 'None'; + targetDoc.presTransition = activeItem.presTransition; + if (activeItem.presNavButton) activeItem.presMovement = 'pan'; + else activeItem.presMovement = 'none'; break; case 'jump': //Jump Cut + activeItem.presTransition = targetDoc.presTransition; targetDoc.presTransition = 0; activeItem.presZoomButton = true; activeItem.presSwitchButton = !activeItem.presSwitchButton; - if (activeItem.presSwitchButton) activeItem.presMovement = 'Jump cut'; - else activeItem.presMovement = 'None'; + if (activeItem.presSwitchButton) activeItem.presMovement = 'jump'; + else activeItem.presMovement = 'none'; break; case 'none': default: - activeItem.presMovement = 'None'; + activeItem.presMovement = 'none'; activeItem.presZoomButton = false; activeItem.presNavButton = false; activeItem.presSwitchButton = false; @@ -485,6 +516,18 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> } }); + setMovementName = action((movement: any, activeItem: Doc): string => { + let output: string = 'none'; + switch (movement) { + case 'zoom': output = 'Zoom'; break; //Pan and zoom + case 'pan': output = 'Pan'; break; //Pan + case 'jump': output = 'Jump cut'; break; //Jump Cut + case 'none': output = 'None'; break; //None + default: output = 'Zoom'; activeItem.presMovement = 'zoom'; break; //default set as zoom + } + return output; + }); + whenActiveChanged = action((isActive: boolean) => this.props.whenActiveChanged(this._isChildActive = isActive)); // For dragging documents into the presentation trail addDocumentFilter = (doc: Doc | Doc[]) => { @@ -501,10 +544,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> return true; } childLayoutTemplate = () => this.rootDoc._viewType !== CollectionViewType.Stacking ? undefined : this.presElement; - removeDocument = (doc: Doc) => Doc.RemoveDocFromList(this.dataDoc, this.fieldKey, doc); + removeDocument = (doc: Doc) => { Doc.RemoveDocFromList(this.dataDoc, this.fieldKey, doc); }; getTransform = () => this.props.ScreenToLocalTransform().translate(-5, -65);// listBox padding-left and pres-box-cont minHeight panelHeight = () => this.props.PanelHeight() - 40; - active = (outsideReaction?: boolean) => ((Doc.GetSelectedTool() === InkTool.None && !this.layoutDoc.isBackground) && + active = (outsideReaction?: boolean) => ((Doc.GetSelectedTool() === InkTool.None && !this.layoutDoc._isBackground) && (this.layoutDoc.forceActive || this.props.isSelected(outsideReaction) || this._isChildActive || this.props.renderDepth === 0) ? true : false) /** @@ -526,10 +569,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> */ @computed get listOfSelected() { const list = this._selectedArray.map((doc: Doc, index: any) => { - const activeItem = Cast(doc, Doc, null); - const targetDoc = Cast(activeItem.presentationTargetDoc!, Doc, null); + const curDoc = Cast(doc, Doc, null); + const tagDoc = Cast(curDoc.presentationTargetDoc!, Doc, null); return ( - <div className="selectedList-items">{index + 1}. {targetDoc.title}</div> + <div className="selectedList-items">{index + 1}. {tagDoc.title}</div> ); }); return list; @@ -563,8 +606,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> @action shiftSelect = (doc: Doc, ref: HTMLElement, drag: HTMLElement) => { this._selectedArray = []; - const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); - if (activeItem) { + // const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); + if (this.activeItem) { for (let i = Math.min(this.itemIndex, this.childDocs.indexOf(doc)); i <= Math.max(this.itemIndex, this.childDocs.indexOf(doc)); i++) { this._selectedArray.push(this.childDocs[i]); this._eleArray.push(ref); @@ -575,8 +618,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> } // Key for when the presentaiton is active - @action - keyEvents = (e: KeyboardEvent) => { + @undoBatch + keyEvents = action((e: KeyboardEvent) => { + if (e.target instanceof HTMLInputElement) return; let handled = false; const anchorNode = document.activeElement as HTMLDivElement; if (anchorNode && anchorNode.className?.includes("lm_title")) return; @@ -592,29 +636,44 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> handled = true; } } if (e.keyCode === 37 || e.keyCode === 38) { // left(37) / a(65) / up(38) to go back - this.back(); if (this._presTimer) clearTimeout(this._presTimer); + this.back(); + if (this._presTimer) clearTimeout(this._presTimer); handled = true; } if (e.keyCode === 39 || e.keyCode === 40) { // right (39) / d(68) / down(40) to go to next - this.next(); if (this._presTimer) clearTimeout(this._presTimer); + this.next(); + if (this._presTimer) clearTimeout(this._presTimer); handled = true; } if (e.keyCode === 32) { // spacebar to 'present' or autoplay if (this.layoutDoc.presStatus !== "edit") this.startAutoPres(0); else this.layoutDoc.presStatus = "manual"; if (this._presTimer) clearTimeout(this._presTimer); handled = true; - } - if (e.keyCode === 8) { // delete selected items + } if (e.keyCode === 8) { // delete selected items if (this.layoutDoc.presStatus === "edit") { - this._selectedArray.forEach((doc, i) => { - this.removeDocument(doc); - }); + this._selectedArray.forEach((doc, i) => this.removeDocument(doc)); + this._selectedArray = []; + this._eleArray = []; + this._dragArray = []; handled = true; } - } - if (handled) { + } if (handled) { e.stopPropagation(); e.preventDefault(); + } if ((e.keyCode === 37 || e.keyCode === 38) && e.shiftKey) { // left(37) / a(65) / up(38) to go back + if (this.layoutDoc.presStatus === "edit" && this._selectedArray.length > 0) { + const index = this.childDocs.indexOf(this._selectedArray[this._selectedArray.length]); + if ((index - 1) > 0) this._selectedArray.push(this.childDocs[index - 1]); + } + handled = true; + } if ((e.keyCode === 39 || e.keyCode === 40) && e.shiftKey) { // left(37) / a(65) / up(38) to go back + if (this.layoutDoc.presStatus === "edit" && this._selectedArray.length > 0) { + const index = this.childDocs.indexOf(this._selectedArray[this._selectedArray.length]); + if ((index - 1) > 0) { + this._selectedArray.push(this.childDocs[index - 1]); + } + } + handled = true; } - } + }); /** * @@ -636,12 +695,12 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> @computed get order() { const order: JSX.Element[] = []; this.childDocs.forEach((doc, index) => { - const targetDoc = Cast(doc.presentationTargetDoc, Doc, null); - const srcContext = Cast(targetDoc?.context, Doc, null); + const tagDoc = Cast(doc.presentationTargetDoc, Doc, null); + const srcContext = Cast(tagDoc?.context, Doc, null); // Case A: Document is contained within the colleciton if (this.rootDoc.presCollection === srcContext) { order.push( - <div className="pathOrder" style={{ top: NumCast(targetDoc.y), left: NumCast(targetDoc.x) }}> + <div className="pathOrder" style={{ top: NumCast(tagDoc.y), left: NumCast(tagDoc.x) }}> <div className="pathOrder-frame">{index + 1}</div> </div>); // Case B: Document is not inside of the collection @@ -666,11 +725,11 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> @computed get paths() { let pathPoints = ""; this.childDocs.forEach((doc, index) => { - const targetDoc = Cast(doc.presentationTargetDoc, Doc, null); - const srcContext = Cast(targetDoc?.context, Doc, null); - if (targetDoc && this.rootDoc.presCollection === srcContext) { - const n1x = NumCast(targetDoc.x) + (NumCast(targetDoc._width) / 2); - const n1y = NumCast(targetDoc.y) + (NumCast(targetDoc._height) / 2); + const tagDoc = Cast(doc.presentationTargetDoc, Doc, null); + const srcContext = Cast(tagDoc?.context, Doc, null); + if (tagDoc && this.rootDoc.presCollection === srcContext) { + const n1x = NumCast(tagDoc.x) + (NumCast(tagDoc._width) / 2); + const n1y = NumCast(tagDoc.y) + (NumCast(tagDoc._height) / 2); if (index = 0) pathPoints = n1x + "," + n1y; else pathPoints = pathPoints + " " + n1x + "," + n1y; } else { @@ -701,8 +760,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> @action onFadeDocumentAfterPresentedClick = (e: React.MouseEvent) => { e.stopPropagation(); - const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); - const targetDoc = Cast(activeItem.presentationTargetDoc, Doc, null); + const activeItem: Doc = this.activeItem; + const targetDoc: Doc = this.targetDoc; activeItem.presFadeButton = !activeItem.presFadeButton; if (!activeItem.presFadeButton) { if (targetDoc) { @@ -722,9 +781,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> if (change) timeInMS += change; if (timeInMS < 100) timeInMS = 100; if (timeInMS > 10000) timeInMS = 10000; - const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); - const targetDoc = Cast(activeItem.presentationTargetDoc, Doc, null); - if (targetDoc) targetDoc.presTransition = timeInMS; + if (this.targetDoc) this.targetDoc.presTransition = timeInMS; } // Converts seconds to ms and updates presDuration @@ -733,15 +790,13 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> if (change) timeInMS += change; if (timeInMS < 100) timeInMS = 100; if (timeInMS > 20000) timeInMS = 20000; - const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); - const targetDoc = Cast(activeItem.presentationTargetDoc, Doc, null); - if (targetDoc) targetDoc.presDuration = timeInMS; + if (this.targetDoc) this.targetDoc.presDuration = timeInMS; } @computed get transitionDropdown() { - const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); - const targetDoc = Cast(activeItem?.presentationTargetDoc, Doc, null); + const activeItem: Doc = this.activeItem; + const targetDoc: Doc = this.targetDoc; if (activeItem && targetDoc) { const transitionSpeed = targetDoc.presTransition ? NumCast(targetDoc.presTransition) / 1000 : 0.5; let duration = targetDoc.presDuration ? NumCast(targetDoc.presDuration) / 1000 : 2; @@ -753,21 +808,21 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> <div className="ribbon-box"> Movement <div className="presBox-dropdown" onClick={action(e => { e.stopPropagation(); this.openMovementDropdown = !this.openMovementDropdown; })} style={{ borderBottomLeftRadius: this.openMovementDropdown ? 0 : 5, border: this.openMovementDropdown ? 'solid 2px #5B9FDD' : 'solid 1px black' }}> - {activeItem.presMovement} + {this.setMovementName(activeItem.presMovement, activeItem)} <FontAwesomeIcon className='presBox-dropdownIcon' style={{ gridColumn: 2, color: this.openMovementDropdown ? '#5B9FDD' : 'black' }} icon={"angle-down"} /> <div className={'presBox-dropdownOptions'} id={'presBoxMovementDropdown'} onPointerDown={e => e.stopPropagation()} style={{ display: this.openMovementDropdown ? "grid" : "none" }}> - <div className={`presBox-dropdownOption ${activeItem.presMovement === 'None' ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.movementChanged('none')}>None</div> - <div className={`presBox-dropdownOption ${activeItem.presMovement === 'Zoom' ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.movementChanged('zoom')}>Pan and Zoom</div> - <div className={`presBox-dropdownOption ${activeItem.presMovement === 'Pan' ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.movementChanged('pan')}>Pan</div> - <div className={`presBox-dropdownOption ${activeItem.presMovement === 'Jump cut' ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.movementChanged('jump')}>Jump cut</div> + <div className={`presBox-dropdownOption ${activeItem.presMovement === 'none' ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateMovement('none', activeItem, targetDoc)}>None</div> + <div className={`presBox-dropdownOption ${activeItem.presMovement === 'zoom' ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateMovement('zoom', activeItem, targetDoc)}>Pan and Zoom</div> + <div className={`presBox-dropdownOption ${activeItem.presMovement === 'pan' ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateMovement('pan', activeItem, targetDoc)}>Pan</div> + <div className={`presBox-dropdownOption ${activeItem.presMovement === 'jump' ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateMovement('jump', activeItem, targetDoc)}>Jump cut</div> </div> </div> - <div className="ribbon-doubleButton" style={{ display: activeItem.presMovement === 'Pan' || activeItem.presMovement === 'Zoom' ? "inline-flex" : "none" }}> + <div className="ribbon-doubleButton" style={{ display: activeItem.presMovement === 'pan' || activeItem.presMovement === 'zoom' ? "inline-flex" : "none" }}> <div className="presBox-subheading">Transition Speed</div> <div className="ribbon-property"> <input className="presBox-input" type="number" value={transitionSpeed} - onFocus={() => { document.removeEventListener("keydown", this.keyEvents, true); }} + onFocus={() => document.removeEventListener("keydown", this.keyEvents, true)} onChange={action((e) => this.setTransitionTime(e.target.value))} /> s </div> <div className="ribbon-propertyUpDown"> @@ -779,8 +834,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> </div> </div> </div> - <input type="range" step="0.1" min="0.1" max="10" value={transitionSpeed} className={`toolbar-slider ${activeItem.presMovement === 'Pan' || activeItem.presMovement === 'Zoom' ? "" : "none"}`} id="toolbar-slider" onChange={(e: React.ChangeEvent<HTMLInputElement>) => { e.stopPropagation(); this.setTransitionTime(e.target.value); }} /> - <div className={`slider-headers ${activeItem.presMovement === 'Pan' || activeItem.presMovement === 'Zoom' ? "" : "none"}`}> + <input type="range" step="0.1" min="0.1" max="10" value={transitionSpeed} className={`toolbar-slider ${activeItem.presMovement === 'pan' || activeItem.presMovement === 'zoom' ? "" : "none"}`} id="toolbar-slider" onChange={(e: React.ChangeEvent<HTMLInputElement>) => { e.stopPropagation(); this.setTransitionTime(e.target.value); }} /> + <div className={`slider-headers ${activeItem.presMovement === 'pan' || activeItem.presMovement === 'zoom' ? "" : "none"}`}> <div className="slider-text">Fast</div> <div className="slider-text">Medium</div> <div className="slider-text">Slow</div> @@ -797,7 +852,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> <div className="ribbon-property"> <input className="presBox-input" type="number" value={duration} - onFocus={() => { document.removeEventListener("keydown", this.keyEvents, true); }} + onFocus={() => document.removeEventListener("keydown", this.keyEvents, true)} onChange={action((e) => this.setDurationTime(e.target.value))} /> s </div> <div className="ribbon-propertyUpDown"> @@ -858,10 +913,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> } @computed get effectDirection(): string { - const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); - const targetDoc = Cast(activeItem?.presentationTargetDoc, Doc, null); let effect = ''; - switch (targetDoc.presEffectDirection) { + switch (this.targetDoc.presEffectDirection) { case 'left': effect = "Enter from left"; break; case 'right': effect = "Enter from right"; break; case 'top': effect = "Enter from top"; break; @@ -874,8 +927,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> @undoBatch @action applyTo = (array: Doc[]) => { - const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); - const targetDoc = Cast(activeItem?.presentationTargetDoc, Doc, null); + const activeItem: Doc = this.activeItem; + const targetDoc: Doc = this.targetDoc; array.forEach((doc, index) => { const curDoc = Cast(doc, Doc, null); const tagDoc = Cast(curDoc.presentationTargetDoc, Doc, null); @@ -885,14 +938,15 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> tagDoc.presEffect = targetDoc.presEffect; tagDoc.presEffectDirection = targetDoc.presEffectDirection; curDoc.presMovement = activeItem.presMovement; + this.updateMovement(activeItem.presMovement, curDoc, tagDoc); curDoc.presHideTillShownButton = activeItem.presHideTillShownButton; curDoc.presHideAfterButton = activeItem.presHideAfterButton; } }); } @computed get optionsDropdown() { - const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); - const targetDoc = Cast(activeItem?.presentationTargetDoc, Doc, null); + const activeItem: Doc = this.activeItem; + const targetDoc: Doc = this.targetDoc; if (activeItem && targetDoc) { return ( <div> @@ -909,6 +963,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> <div className="ribbon-button" style={{ backgroundColor: activeItem.presPinView ? "#aedef8" : "" }} onClick={() => { activeItem.presPinView = !activeItem.presPinView; + targetDoc.presPinView = activeItem.presPinView; if (activeItem.presPinView) { const x = targetDoc._panX; const y = targetDoc._panY; @@ -926,7 +981,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> <input className="presBox-input" style={{ textAlign: 'left', width: 50 }} type="number" value={NumCast(activeItem.presPinViewX)} - onFocus={() => { document.removeEventListener("keydown", this.keyEvents, true); }} + onFocus={() => document.removeEventListener("keydown", this.keyEvents, true)} onChange={action((e: React.ChangeEvent<HTMLInputElement>) => { const val = e.target.value; activeItem.presPinViewX = Number(val); })} /> </div> </div> @@ -936,7 +991,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> <input className="presBox-input" style={{ textAlign: 'left', width: 50 }} type="number" value={NumCast(activeItem.presPinViewY)} - onFocus={() => { document.removeEventListener("keydown", this.keyEvents, true); }} + onFocus={() => document.removeEventListener("keydown", this.keyEvents, true)} onChange={action((e: React.ChangeEvent<HTMLInputElement>) => { const val = e.target.value; activeItem.presPinViewY = Number(val); })} /> </div> </div> @@ -946,7 +1001,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> <input className="presBox-input" style={{ textAlign: 'left', width: 50 }} type="number" value={NumCast(activeItem.presPinViewScale)} - onFocus={() => { document.removeEventListener("keydown", this.keyEvents, true); }} + onFocus={() => document.removeEventListener("keydown", this.keyEvents, true)} onChange={action((e: React.ChangeEvent<HTMLInputElement>) => { const val = e.target.value; activeItem.presPinViewScale = Number(val); })} /> </div> </div> @@ -996,9 +1051,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> <div className="ribbon-box"> Slide Title: <br></br> <input className="ribbon-textInput" placeholder="..." type="text" name="fname" - onFocus={() => { - document.removeEventListener("keydown", this.keyEvents, true); - }} + onFocus={() => document.removeEventListener("keydown", this.keyEvents, true)} onChange={(e) => { e.stopPropagation(); e.preventDefault(); @@ -1052,21 +1105,23 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> if (layout) doc = this.createTemplate(layout); if (freeform && layout) doc = this.createTemplate(layout, title); if (!freeform && !layout) doc = Docs.Create.TextDocument("", { _nativeWidth: 400, _width: 225, title: title }); - const presCollection = Cast(this.layoutDoc.presCollection, Doc, null); - const data = Cast(presCollection?.data, listSpec(Doc)); - const presData = Cast(this.rootDoc.data, listSpec(Doc)); - if (data && doc && presData) { - data.push(doc); - DockedFrameRenderer.PinDoc(doc, false); - this.gotoDocument(this.childDocs.length, this.itemIndex); - } else { - this.props.addDocTab(doc as Doc, "onRight"); + if (doc) { + const presCollection = Cast(this.layoutDoc.presCollection, Doc, null); + const data = Cast(presCollection?.data, listSpec(Doc)); + const presData = Cast(this.rootDoc.data, listSpec(Doc)); + if (data && presData) { + data.push(doc); + TabDocView.PinDoc(doc, false); + this.gotoDocument(this.childDocs.length, this.itemIndex); + } else { + this.props.addDocTab(doc, "add:right"); + } } } createTemplate = (layout: string, input?: string) => { - const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); - const targetDoc = Cast(activeItem?.presentationTargetDoc, Doc, null); + const activeItem: Doc = this.activeItem; + const targetDoc: Doc = this.targetDoc; let x = 0; let y = 0; if (activeItem && targetDoc) { @@ -1120,9 +1175,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> // Case in which the document has keyframes to navigate to next key frame @undoBatch @action - nextKeyframe = (tagDoc: Doc, activeItem: Doc): void => { + nextKeyframe = (tagDoc: Doc, curDoc: Doc): void => { const childDocs = DocListCast(tagDoc[Doc.LayoutFieldKey(tagDoc)]); - const currentFrame = Cast(tagDoc.currentFrame, "number", null); + const currentFrame = Cast(tagDoc._currentFrame, "number", null); if (currentFrame === undefined) { tagDoc.currentFrame = 0; CollectionFreeFormDocumentView.setupScroll(tagDoc, 0); @@ -1130,37 +1185,37 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> } CollectionFreeFormDocumentView.updateScrollframe(tagDoc, currentFrame); CollectionFreeFormDocumentView.updateKeyframe(childDocs, currentFrame || 0, tagDoc); - tagDoc.currentFrame = Math.max(0, (currentFrame || 0) + 1); - tagDoc.lastFrame = Math.max(NumCast(tagDoc.currentFrame), NumCast(tagDoc.lastFrame)); - if (activeItem.zoomProgressivize) { + tagDoc._currentFrame = Math.max(0, (currentFrame || 0) + 1); + tagDoc.lastFrame = Math.max(NumCast(tagDoc._currentFrame), NumCast(tagDoc.lastFrame)); + if (curDoc.zoomProgressivize) { const resize = document.getElementById('resizable'); if (resize) { - resize.style.width = this.checkList(tagDoc, activeItem["viewfinder-width-indexed"]) + 'px'; - resize.style.height = this.checkList(tagDoc, activeItem["viewfinder-height-indexed"]) + 'px'; - resize.style.top = this.checkList(tagDoc, activeItem["viewfinder-top-indexed"]) + 'px'; - resize.style.left = this.checkList(tagDoc, activeItem["viewfinder-left-indexed"]) + 'px'; + resize.style.width = this.checkList(tagDoc, curDoc["viewfinder-width-indexed"]) + 'px'; + resize.style.height = this.checkList(tagDoc, curDoc["viewfinder-height-indexed"]) + 'px'; + resize.style.top = this.checkList(tagDoc, curDoc["viewfinder-top-indexed"]) + 'px'; + resize.style.left = this.checkList(tagDoc, curDoc["viewfinder-left-indexed"]) + 'px'; } } } @undoBatch @action - prevKeyframe = (tagDoc: Doc, activeItem: Doc): void => { + prevKeyframe = (tagDoc: Doc, actItem: Doc): void => { const childDocs = DocListCast(tagDoc[Doc.LayoutFieldKey(tagDoc)]); - const currentFrame = Cast(tagDoc.currentFrame, "number", null); + const currentFrame = Cast(tagDoc._currentFrame, "number", null); if (currentFrame === undefined) { - tagDoc.currentFrame = 0; + tagDoc._currentFrame = 0; CollectionFreeFormDocumentView.setupKeyframes(childDocs, 0); } CollectionFreeFormDocumentView.gotoKeyframe(childDocs.slice()); - tagDoc.currentFrame = Math.max(0, (currentFrame || 0) - 1); - if (activeItem.zoomProgressivize) { + tagDoc._currentFrame = Math.max(0, (currentFrame || 0) - 1); + if (actItem.zoomProgressivize) { const resize = document.getElementById('resizable'); if (resize) { - resize.style.width = this.checkList(tagDoc, activeItem["viewfinder-width-indexed"]) + 'px'; - resize.style.height = this.checkList(tagDoc, activeItem["viewfinder-height-indexed"]) + 'px'; - resize.style.top = this.checkList(tagDoc, activeItem["viewfinder-top-indexed"]) + 'px'; - resize.style.left = this.checkList(tagDoc, activeItem["viewfinder-left-indexed"]) + 'px'; + resize.style.width = this.checkList(tagDoc, actItem["viewfinder-width-indexed"]) + 'px'; + resize.style.height = this.checkList(tagDoc, actItem["viewfinder-height-indexed"]) + 'px'; + resize.style.top = this.checkList(tagDoc, actItem["viewfinder-top-indexed"]) + 'px'; + resize.style.left = this.checkList(tagDoc, actItem["viewfinder-left-indexed"]) + 'px'; } } } @@ -1169,8 +1224,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> * Returns the collection type as a string for headers */ @computed get stringType(): string { - const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); - const targetDoc = Cast(activeItem?.presentationTargetDoc, Doc, null); + const activeItem: Doc = this.activeItem; + const targetDoc: Doc = this.targetDoc; let type: string = ''; if (activeItem) { switch (targetDoc.type) { @@ -1193,11 +1248,11 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> @computed get progressivizeDropdown() { - const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); - const targetDoc = Cast(activeItem?.presentationTargetDoc, Doc, null); - const activeFontColor = targetDoc["pres-text-color"] ? StrCast(targetDoc["pres-text-color"]) : "Black"; - const viewedFontColor = targetDoc["pres-text-viewed-color"] ? StrCast(targetDoc["pres-text-viewed-color"]) : "Black"; + const activeItem: Doc = this.activeItem; + const targetDoc: Doc = this.targetDoc; if (activeItem && targetDoc) { + const activeFontColor = targetDoc["pres-text-color"] ? StrCast(targetDoc["pres-text-color"]) : "Black"; + const viewedFontColor = targetDoc["pres-text-viewed-color"] ? StrCast(targetDoc["pres-text-viewed-color"]) : "Black"; return ( <div> <div className={`presBox-ribbon ${this.progressivizeTools && this.layoutDoc.presStatus === "edit" ? "active" : ""}`} onClick={e => e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}> @@ -1209,7 +1264,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> </div> <div className="ribbon-doubleButton" style={{ display: activeItem.presProgressivize ? "inline-flex" : "none" }}> <div className="presBox-subheading">Active text color</div> - <div className="ribbon-property" style={{ backgroundColor: activeFontColor }} onClick={action(() => { console.log("hi"); this.openActiveColorPicker = !this.openActiveColorPicker; })}> + <div className="ribbon-property" style={{ backgroundColor: activeFontColor }} onClick={action(() => { this.openActiveColorPicker = !this.openActiveColorPicker; })}> </div> </div> {this.activeColorPicker} @@ -1237,7 +1292,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> </div> <div key="num" title="toggle view all" className="numKeyframe" style={{ color: targetDoc.editing ? "white" : "black", backgroundColor: targetDoc.editing ? "#5B9FDD" : "#AEDDF8" }} onClick={action(() => targetDoc.editing = !targetDoc.editing)} > - {NumCast(targetDoc.currentFrame)} + {NumCast(targetDoc._currentFrame)} </div> <div key="fwd" title="forward frame" className="fwdKeyframe" onClick={e => { e.stopPropagation(); this.nextKeyframe(targetDoc, activeItem); }}> <FontAwesomeIcon icon={"caret-right"} size={"lg"} /> @@ -1256,8 +1311,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> @undoBatch @action switchActive = (color: ColorState) => { - const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); - const targetDoc = Cast(activeItem?.presentationTargetDoc, Doc, null); + const activeItem: Doc = this.activeItem; + const targetDoc: Doc = this.targetDoc; const val = String(color.hex); targetDoc["pres-text-color"] = val; return true; @@ -1265,16 +1320,16 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> @undoBatch @action switchPresented = (color: ColorState) => { - const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); - const targetDoc = Cast(activeItem?.presentationTargetDoc, Doc, null); + const activeItem: Doc = this.activeItem; + const targetDoc: Doc = this.targetDoc; const val = String(color.hex); targetDoc["pres-text-viewed-color"] = val; return true; } @computed get activeColorPicker() { - const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); - const targetDoc = Cast(activeItem?.presentationTargetDoc, Doc, null); + const activeItem: Doc = this.activeItem; + const targetDoc: Doc = this.targetDoc; return !this.openActiveColorPicker ? (null) : <SketchPicker onChange={this.switchActive} presetColors={['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505', '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B', @@ -1283,8 +1338,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> } @computed get viewedColorPicker() { - const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); - const targetDoc = Cast(activeItem?.presentationTargetDoc, Doc, null); + const activeItem: Doc = this.activeItem; + const targetDoc: Doc = this.targetDoc; return !this.openViewedColorPicker ? (null) : <SketchPicker onChange={this.switchPresented} presetColors={['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505', '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B', @@ -1308,8 +1363,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> //Toggle whether the user edits or not @action editZoomProgressivize = (e: React.MouseEvent) => { - const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); - const targetDoc = Cast(activeItem.presentationTargetDoc, Doc, null); + const activeItem: Doc = this.activeItem; + const targetDoc: Doc = this.targetDoc; if (!targetDoc.editZoomProgressivize) { if (!activeItem.zoomProgressivize) activeItem.zoomProgressivize = true; targetDoc.zoomProgressivize = true; targetDoc.editZoomProgressivize = true; @@ -1323,8 +1378,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> //Toggle whether the user edits or not @action editScrollProgressivize = (e: React.MouseEvent) => { - const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); - const targetDoc = Cast(activeItem.presentationTargetDoc, Doc, null); + const activeItem: Doc = this.activeItem; + const targetDoc: Doc = this.targetDoc; if (!targetDoc.editScrollProgressivize) { if (!targetDoc.scrollProgressivize) { targetDoc.scrollProgressivize = true; activeItem.scrollProgressivize = true; } targetDoc.editScrollProgressivize = true; @@ -1337,14 +1392,14 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> @action progressivizeScroll = (e: React.MouseEvent) => { e.stopPropagation(); - const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); + const activeItem: Doc = this.activeItem; activeItem.scrollProgressivize = !activeItem.scrollProgressivize; - const targetDoc = Cast(activeItem.presentationTargetDoc, Doc, null); + const targetDoc: Doc = this.targetDoc; targetDoc.scrollProgressivize = !targetDoc.scrollProgressivize; - CollectionFreeFormDocumentView.setupScroll(targetDoc, NumCast(targetDoc.currentFrame), true); + CollectionFreeFormDocumentView.setupScroll(targetDoc, NumCast(targetDoc._currentFrame)); if (targetDoc.editScrollProgressivize) { targetDoc.editScrollProgressivize = false; - targetDoc.currentFrame = 0; + targetDoc._currentFrame = 0; targetDoc.lastFrame = 0; } } @@ -1353,14 +1408,14 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> @action progressivizeZoom = (e: React.MouseEvent) => { e.stopPropagation(); - const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); + const activeItem: Doc = this.activeItem; activeItem.zoomProgressivize = !activeItem.zoomProgressivize; - const targetDoc = Cast(activeItem.presentationTargetDoc, Doc, null); + const targetDoc: Doc = this.targetDoc; targetDoc.zoomProgressivize = !targetDoc.zoomProgressivize; - CollectionFreeFormDocumentView.setupZoom(activeItem, targetDoc, true); + CollectionFreeFormDocumentView.setupZoom(activeItem, targetDoc); if (activeItem.editZoomProgressivize) { activeItem.editZoomProgressivize = false; - targetDoc.currentFrame = 0; + targetDoc._currentFrame = 0; targetDoc.lastFrame = 0; } } @@ -1368,9 +1423,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> //Progressivize Child Docs @action editProgressivize = (e: React.MouseEvent) => { - const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); - const targetDoc = Cast(activeItem.presentationTargetDoc, Doc, null); - targetDoc.currentFrame = targetDoc.lastFrame; + const activeItem: Doc = this.activeItem; + const targetDoc: Doc = this.targetDoc; + targetDoc._currentFrame = targetDoc.lastFrame; if (!targetDoc.editProgressivize) { if (!activeItem.presProgressivize) { activeItem.presProgressivize = true; targetDoc.presProgressivize = true; } targetDoc.editProgressivize = true; @@ -1382,21 +1437,21 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> @action progressivizeChild = (e: React.MouseEvent) => { e.stopPropagation(); - const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); - const targetDoc = Cast(activeItem.presentationTargetDoc, Doc, null); + const activeItem: Doc = this.activeItem; + const targetDoc: Doc = this.targetDoc; const docs = DocListCast(targetDoc[Doc.LayoutFieldKey(targetDoc)]); if (!activeItem.presProgressivize) { targetDoc.editing = false; activeItem.presProgressivize = true; targetDoc.presProgressivize = true; - targetDoc.currentFrame = 0; - CollectionFreeFormDocumentView.setupKeyframes(docs, docs.length, true); + targetDoc._currentFrame = 0; + docs.forEach((doc, i) => CollectionFreeFormDocumentView.setupKeyframes([doc], i, true)); targetDoc.lastFrame = docs.length - 1; } else { targetDoc.editProgressivize = false; activeItem.presProgressivize = false; targetDoc.presProgressivize = false; - targetDoc.currentFrame = 0; + targetDoc._currentFrame = 0; targetDoc.lastFrame = 0; targetDoc.editing = true; } @@ -1438,18 +1493,17 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> @action checkList = (doc: Doc, list: any): number => { const x: List<number> = list; - if (x && x.length >= NumCast(doc.currentFrame) + 1) { - return x[NumCast(doc.currentFrame)]; + if (x && x.length >= NumCast(doc._currentFrame) + 1) { + return x[NumCast(doc._currentFrame)]; } else if (x) { - x.length = NumCast(doc.currentFrame) + 1; - x[NumCast(doc.currentFrame)] = x[NumCast(doc.currentFrame) - 1]; - return x[NumCast(doc.currentFrame)]; + x.length = NumCast(doc._currentFrame) + 1; + x[NumCast(doc._currentFrame)] = x[NumCast(doc._currentFrame) - 1]; + return x[NumCast(doc._currentFrame)]; } else return 100; } @computed get progressivizeChildDocs() { - const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); - const targetDoc = Cast(activeItem?.presentationTargetDoc, Doc, null); + const targetDoc: Doc = this.targetDoc; const docs = DocListCast(targetDoc[Doc.LayoutFieldKey(targetDoc)]); const tags: JSX.Element[] = []; docs.forEach((doc, index) => { @@ -1457,7 +1511,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> tags.push(<div style={{ position: 'absolute', display: doc.displayMovement ? "block" : "none" }}>{this.checkMovementLists(doc, doc["x-indexed"], doc["y-indexed"])}</div>); } tags.push( - <div className="progressivizeButton" onPointerLeave={() => { if (NumCast(targetDoc.currentFrame) < NumCast(doc.appearFrame)) doc.opacity = 0; }} onPointerOver={() => { if (NumCast(targetDoc.currentFrame) < NumCast(doc.appearFrame)) doc.opacity = 0.5; }} onClick={e => { this.toggleDisplayMovement(doc); e.stopPropagation(); }} style={{ backgroundColor: doc.displayMovement ? "#aedff8" : "#c8c8c8", top: NumCast(doc.y), left: NumCast(doc.x) }}> + <div className="progressivizeButton" key={index} onPointerLeave={() => { if (NumCast(targetDoc._currentFrame) < NumCast(doc.appearFrame)) doc.opacity = 0; }} onPointerOver={() => { if (NumCast(targetDoc._currentFrame) < NumCast(doc.appearFrame)) doc.opacity = 0.5; }} onClick={e => { this.toggleDisplayMovement(doc); e.stopPropagation(); }} style={{ backgroundColor: doc.displayMovement ? "#aedff8" : "#c8c8c8", top: NumCast(doc.y), left: NumCast(doc.x) }}> <div className="progressivizeButton-prev"><FontAwesomeIcon icon={"caret-left"} size={"lg"} onClick={e => { e.stopPropagation(); this.prevAppearFrame(doc, index); }} /></div> <div className="progressivizeButton-frame">{doc.appearFrame}</div> <div className="progressivizeButton-next"><FontAwesomeIcon icon={"caret-right"} size={"lg"} onClick={e => { e.stopPropagation(); this.nextAppearFrame(doc, index); }} /></div> @@ -1469,8 +1523,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> @undoBatch @action nextAppearFrame = (doc: Doc, i: number): void => { - const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); - const targetDoc = Cast(activeItem?.presentationTargetDoc, Doc, null); + // const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); + // const targetDoc = Cast(activeItem?.presentationTargetDoc, Doc, null); const appearFrame = Cast(doc.appearFrame, "number", null); if (appearFrame === undefined) { doc.appearFrame = 0; @@ -1482,8 +1536,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> @undoBatch @action prevAppearFrame = (doc: Doc, i: number): void => { - const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); - const targetDoc = Cast(activeItem?.presentationTargetDoc, Doc, null); + // const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); + // const targetDoc = Cast(activeItem?.presentationTargetDoc, Doc, null); const appearFrame = Cast(doc.appearFrame, "number", null); if (appearFrame === undefined) { doc.appearFrame = 0; @@ -1604,12 +1658,11 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> } @computed get playButtonFrames() { - const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); - const targetDoc = Cast(activeItem?.presentationTargetDoc, Doc, null); + const targetDoc: Doc = this.targetDoc; return ( <> - {targetDoc ? <div className="miniPres-button-frame" style={{ display: targetDoc.lastFrame !== undefined && targetDoc.lastFrame >= 0 ? "inline-flex" : "none" }}> - <div>{targetDoc.currentFrame}</div> + {this.targetDoc ? <div className="miniPres-button-frame" style={{ display: targetDoc.lastFrame !== undefined && targetDoc.lastFrame >= 0 ? "inline-flex" : "none" }}> + <div>{targetDoc._currentFrame}</div> <div className="miniPres-divider" style={{ border: 'solid 0.5px white', height: '60%' }}></div> <div>{targetDoc.lastFrame}</div> </div> : null} @@ -1620,8 +1673,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> @computed get playButtons() { // Case 1: There are still other frames and should go through all frames before going to next slide return (<div className="presPanelOverlay" style={{ display: this.layoutDoc.presStatus !== "edit" ? "inline-flex" : "none" }}> + <Tooltip title={<><div className="dash-tooltip">{"Loop"}</div></>}><div className="presPanel-button" style={{ color: this.layoutDoc.presLoop ? '#AEDDF8' : 'white' }} onClick={() => this.layoutDoc.presLoop = !this.layoutDoc.presLoop}><FontAwesomeIcon icon={"redo-alt"} /></div></Tooltip> + <div className="presPanel-divider"></div> <div className="presPanel-button" onClick={this.back}><FontAwesomeIcon icon={"arrow-left"} /></div> - <div className="presPanel-button" onClick={() => this.startAutoPres(this.itemIndex)}><FontAwesomeIcon icon={this.layoutDoc.presStatus === "auto" ? "pause" : "play"} /></div> + <Tooltip title={<><div className="dash-tooltip">{this.layoutDoc.presStatus === "auto" ? "Pause" : "Autoplay"}</div></>}><div className="presPanel-button" onClick={this.startOrPause}><FontAwesomeIcon icon={this.layoutDoc.presStatus === "auto" ? "pause" : "play"} /></div></Tooltip> <div className="presPanel-button" onClick={this.next}><FontAwesomeIcon icon={"arrow-right"} /></div> <div className="presPanel-divider"></div> <div className="presPanel-button-text" style={{ display: this.props.PanelWidth() > 250 ? "inline-flex" : "none" }}> @@ -1636,6 +1691,12 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> </div>); } + @action + startOrPause = () => { + if (this.layoutDoc.presStatus === "manual") this.startAutoPres(this.itemIndex); + else this.pauseAutoPres(); + } + render() { // calling this method for keyEvents this.isPres; @@ -1645,8 +1706,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> return this.layoutDoc.inOverlay ? <div className="miniPres" style={{ width: 250, height: 35, background: '#323232', top: 0, zIndex: 3000000 }}> {<div className="miniPresOverlay"> + <Tooltip title={<><div className="dash-tooltip">{"Loop"}</div></>}><div className="miniPres-button" style={{ color: this.layoutDoc.presLoop ? '#AEDDF8' : undefined }} onClick={() => this.layoutDoc.presLoop = !this.layoutDoc.presLoop}><FontAwesomeIcon icon={"redo-alt"} /></div></Tooltip> + <div className="miniPres-divider"></div> <div className="miniPres-button" onClick={this.back}><FontAwesomeIcon icon={"arrow-left"} /></div> - <div className="miniPres-button" onClick={() => this.startAutoPres(this.itemIndex)}><FontAwesomeIcon icon={this.layoutDoc.presStatus === "auto" ? "pause" : "play"} /></div> + <Tooltip title={<><div className="dash-tooltip">{this.layoutDoc.presStatus === "auto" ? "Pause" : "Autoplay"}</div></>}><div className="miniPres-button" onClick={this.startOrPause}><FontAwesomeIcon icon={this.layoutDoc.presStatus === "auto" ? "pause" : "play"} /></div></Tooltip> <div className="miniPres-button" onClick={this.next}><FontAwesomeIcon icon={"arrow-right"} /></div> <div className="miniPres-divider"></div> <div className="miniPres-button-text"> diff --git a/src/client/views/nodes/RadialMenuItem.tsx b/src/client/views/nodes/RadialMenuItem.tsx index bd5b3bff4..8876b4879 100644 --- a/src/client/views/nodes/RadialMenuItem.tsx +++ b/src/client/views/nodes/RadialMenuItem.tsx @@ -1,13 +1,9 @@ import React = require("react"); -import { observable, action } from "mobx"; -import { observer } from "mobx-react"; -import { IconProp, library } from '@fortawesome/fontawesome-svg-core'; -import { faAngleRight } from '@fortawesome/free-solid-svg-icons'; +import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { observer } from "mobx-react"; import { UndoManager } from "../../util/UndoManager"; -library.add(faAngleRight); - export interface RadialMenuProps { description: string; event: (stuff?: any) => void; diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx index 1cd29d795..5d51c420b 100644 --- a/src/client/views/nodes/ScreenshotBox.tsx +++ b/src/client/views/nodes/ScreenshotBox.tsx @@ -1,12 +1,11 @@ import React = require("react"); -import { library } from "@fortawesome/fontawesome-svg-core"; -import { faVideo } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { action, computed, IReactionDisposer, observable, runInAction } from "mobx"; import { observer } from "mobx-react"; import * as rp from 'request-promise'; import { Doc } from "../../../fields/Doc"; import { documentSchema } from "../../../fields/documentSchemas"; +import { InkTool } from "../../../fields/InkField"; import { listSpec, makeInterface } from "../../../fields/Schema"; import { Cast, NumCast } from "../../../fields/Types"; import { VideoField } from "../../../fields/URLField"; @@ -18,14 +17,11 @@ import { ContextMenuProps } from "../ContextMenuItem"; import { ViewBoxBaseComponent } from "../DocComponent"; import { FieldView, FieldViewProps } from './FieldView'; import "./ScreenshotBox.scss"; -import { InkTool } from "../../../fields/InkField"; const path = require('path'); type ScreenshotDocument = makeInterface<[typeof documentSchema]>; const ScreenshotDocument = makeInterface(documentSchema); -library.add(faVideo); - @observer export class ScreenshotBox extends ViewBoxBaseComponent<FieldViewProps, ScreenshotDocument>(ScreenshotDocument) { private _reactionDisposer?: IReactionDisposer; @@ -77,7 +73,7 @@ export class ScreenshotBox extends ViewBoxBaseComponent<FieldViewProps, Screensh const spt = this.props.ScreenToLocalTransform().inverse().transformPoint(0, 0); imageSummary.x = spt[0]; imageSummary.y = spt[1]; - Cast(Cast(Doc.UserDoc().myOverlayDocuments, Doc, null)?.data, listSpec(Doc), []).push(imageSummary); + Cast(Cast(Doc.UserDoc().myOverlayDocs, Doc, null)?.data, listSpec(Doc), []).push(imageSummary); } else { this.props.addDocument?.(imageSummary); } diff --git a/src/client/views/nodes/SliderBox.tsx b/src/client/views/nodes/SliderBox.tsx index 45cdfc5ad..d13680046 100644 --- a/src/client/views/nodes/SliderBox.tsx +++ b/src/client/views/nodes/SliderBox.tsx @@ -1,5 +1,3 @@ -import { library } from '@fortawesome/fontawesome-svg-core'; -import { faEdit } from '@fortawesome/free-regular-svg-icons'; import { runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; @@ -16,9 +14,6 @@ import { FieldView, FieldViewProps } from './FieldView'; import { Handle, Tick, TooltipRail, Track } from './SliderBox-components'; import './SliderBox.scss'; - -library.add(faEdit as any); - const SliderSchema = createSchema({ _sliderMin: "number", _sliderMax: "number", diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index ee92e517c..d50a10bdd 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -23,7 +23,7 @@ import { SnappingManager } from "../../util/SnappingManager"; 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 + _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 }); type VideoDocument = makeInterface<[typeof documentSchema, typeof timeSchema]>; const VideoDocument = makeInterface(documentSchema, timeSchema); @@ -82,7 +82,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD @action public FullScreen() { this._fullScreen = true; this.player && this.player.requestFullscreen(); - this._youtubePlayer && this.props.addDocTab(this.rootDoc, "inTab"); + this._youtubePlayer && this.props.addDocTab(this.rootDoc, "add"); } choosePath(url: string) { @@ -109,14 +109,14 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD if (!this._videoRef) { const b = Docs.Create.LabelDocument({ x: (this.layoutDoc.x || 0) + width, y: (this.layoutDoc.y || 1), - _width: 150, _height: 50, title: (this.layoutDoc.currentTimecode || 0).toString(), + _width: 150, _height: 50, title: (this.layoutDoc._currentTimecode || 0).toString(), }); b.isLinkButton = true; this.props.addDocument?.(b); DocUtils.MakeLink({ doc: b }, { doc: this.rootDoc }, "video snapshot"); Networking.PostToServer("/youtubeScreenshot", { id: this.youtubeVideoId, - timecode: this.layoutDoc.currentTimecode + timecode: this.layoutDoc._currentTimecode }).then(response => { const resolved = response?.accessPaths?.agnostic?.client; if (resolved) { @@ -128,7 +128,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD //convert to desired file format const dataUrl = canvas.toDataURL('image/png'); // can also use 'image/png' // if you want to preview the captured image, - const filename = path.basename(encodeURIComponent("snapshot" + StrCast(this.rootDoc.title).replace(/\..*$/, "") + "_" + (this.layoutDoc.currentTimecode || 0).toString().replace(/\./, "_"))); + const filename = path.basename(encodeURIComponent("snapshot" + StrCast(this.rootDoc.title).replace(/\..*$/, "") + "_" + (this.layoutDoc._currentTimecode || 0).toString().replace(/\./, "_"))); VideoBox.convertDataUri(dataUrl, filename).then((returnedFilename: string) => { if (returnedFilename) { this.createRealSummaryLink(returnedFilename); @@ -144,7 +144,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD const imageSummary = Docs.Create.ImageDocument(url, { _nativeWidth: this.layoutDoc._nativeWidth, _nativeHeight: this.layoutDoc._nativeHeight, x: (this.layoutDoc.x || 0) + width, y: (this.layoutDoc.y || 0), - _width: 150, _height: height / width * 150, title: "--snapshot" + (this.layoutDoc.currentTimecode || 0) + " image-" + _width: 150, _height: height / width * 150, title: "--snapshot" + (this.layoutDoc._currentTimecode || 0) + " image-" }); Doc.GetProto(imageSummary)["data-nativeWidth"] = this.layoutDoc._nativeWidth; Doc.GetProto(imageSummary)["data-nativeHeight"] = this.layoutDoc._nativeHeight; @@ -155,8 +155,8 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD @action updateTimecode = () => { - this.player && (this.layoutDoc.currentTimecode = this.player.currentTime); - this._youtubePlayer && (this.layoutDoc.currentTimecode = this._youtubePlayer.getCurrentTime()); + this.player && (this.layoutDoc._currentTimecode = this.player.currentTime); + this._youtubePlayer && (this.layoutDoc._currentTimecode = this._youtubePlayer.getCurrentTime()); } componentDidMount() { @@ -185,9 +185,10 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD this._videoRef = vref; if (vref) { this._videoRef!.ontimeupdate = this.updateTimecode; + // @ts-ignore vref.onfullscreenchange = action((e) => this._fullScreen = vref.webkitDisplayingFullscreen); - this._reactionDisposer && this._reactionDisposer(); - this._reactionDisposer = reaction(() => (this.layoutDoc.currentTimecode || 0), + this._reactionDisposer?.(); + this._reactionDisposer = reaction(() => (this.layoutDoc._currentTimecode || 0), time => !this._playing && (vref.currentTime = time), { fireImmediately: true }); } } @@ -272,21 +273,21 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD const onYoutubePlayerReady = (event: any) => { this._reactionDisposer?.(); this._youtubeReactionDisposer?.(); - this._reactionDisposer = reaction(() => this.layoutDoc.currentTimecode, () => !this._playing && this.Seek((this.layoutDoc.currentTimecode || 0))); + this._reactionDisposer = reaction(() => this.layoutDoc._currentTimecode, () => !this._playing && this.Seek((this.layoutDoc._currentTimecode || 0))); this._youtubeReactionDisposer = reaction( - () => Doc.GetSelectedTool() === InkTool.None && this.props.isSelected(true) && !SnappingManager.GetIsDragging() && !DocumentDecorations.Instance.Interacting, + () => !this.props.Document.isAnnotating && Doc.GetSelectedTool() === InkTool.None && this.props.isSelected(true) && !SnappingManager.GetIsDragging() && !DocumentDecorations.Instance.Interacting, (interactive) => iframe.style.pointerEvents = interactive ? "all" : "none", { fireImmediately: true }); }; this._youtubePlayer = new YT.Player(`${this.youtubeVideoId + this._youtubeIframeId}-player`, { events: { - 'onReady': onYoutubePlayerReady, - 'onStateChange': onYoutubePlayerStateChange, + 'onReady': this.props.dontRegisterView ? undefined : onYoutubePlayerReady, + 'onStateChange': this.props.dontRegisterView ? undefined : onYoutubePlayerStateChange, } }); } private get uIButtons() { - const curTime = (this.layoutDoc.currentTimecode || 0); + const curTime = (this.layoutDoc._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> @@ -328,7 +329,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD onResetMove = (e: PointerEvent) => { this._isResetClick += Math.abs(e.movementX) + Math.abs(e.movementY); - this.Seek(Math.max(0, (this.layoutDoc.currentTimecode || 0) + Math.sign(e.movementX) * 0.0333)); + this.Seek(Math.max(0, (this.layoutDoc._currentTimecode || 0) + Math.sign(e.movementX) * 0.0333)); e.stopImmediatePropagation(); } @@ -336,14 +337,14 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD onResetUp = (e: PointerEvent) => { document.removeEventListener("pointermove", this.onResetMove, true); document.removeEventListener("pointerup", this.onResetUp, true); - this._isResetClick < 10 && (this.layoutDoc.currentTimecode = 0); + this._isResetClick < 10 && (this.layoutDoc._currentTimecode = 0); } @computed get youtubeContent() { this._youtubeIframeId = VideoBox._youtubeIframeCounter++; this._youtubeContentCreated = this._forceCreateYouTubeIFrame ? true : true; const style = "videoBox-content-YouTube" + (this._fullScreen ? "-fullScreen" : ""); - const start = untracked(() => Math.round((this.layoutDoc.currentTimecode || 0))); + const start = untracked(() => Math.round((this.layoutDoc._currentTimecode || 0))); return <iframe key={this._youtubeIframeId} id={`${this.youtubeVideoId + this._youtubeIframeId}-player`} onLoad={this.youtubeIframeLoaded} className={`${style}`} width={(this.layoutDoc._nativeWidth || 640)} height={(this.layoutDoc._nativeHeight || 390)} src={`https://www.youtube.com/embed/${this.youtubeVideoId}?enablejsapi=1&rel=0&showinfo=1&autoplay=0&mute=1&start=${start}&modestbranding=1&controls=${VideoBox._showControls ? 1 : 0}`} />; @@ -353,7 +354,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD addDocumentWithTimestamp(doc: Doc | Doc[]): boolean { const docs = doc instanceof Doc ? [doc] : doc; docs.forEach(doc => { - const curTime = (this.layoutDoc.currentTimecode || -1); + const curTime = (this.layoutDoc._currentTimecode || -1); curTime !== -1 && (doc.displayTimecode = curTime); }); return this.addDocument(doc); @@ -384,6 +385,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD ScreenToLocalTransform={this.props.ScreenToLocalTransform} renderDepth={this.props.renderDepth + 1} docFilters={this.props.docFilters} + searchFilterDocs={this.props.searchFilterDocs} ContainingCollectionDoc={this.props.ContainingCollectionDoc}> {this.contentFunc} </CollectionFreeFormView> diff --git a/src/client/views/nodes/WebBox.scss b/src/client/views/nodes/WebBox.scss index 875142169..134860d0a 100644 --- a/src/client/views/nodes/WebBox.scss +++ b/src/client/views/nodes/WebBox.scss @@ -70,7 +70,6 @@ } } - .webBox-overlay { width: 100%; height: 100%; @@ -82,61 +81,24 @@ background:lightGray; width: 100%; } - .webBox-freeze { - display: flex; - align-items: center; - justify-content: center; - margin-right: 5px; - width: 30px; - } - - .webBox-urlEditor { - position: relative; - opacity: 0.9; + .webBox-annotationToggle { z-index: 9001; - transition: top .5s; - - .urlEditor { - display: grid; - grid-template-columns: 1fr auto; - padding-bottom: 10px; - overflow: hidden; - - .editorBase { - display: flex; - - .editor-collapse { - transition: all .5s, opacity 0.3s; - position: absolute; - width: 40px; - transform-origin: top left; - } - - .switchToText { - color: $main-accent; - } - - .switchToText:hover { - color: $dark-color; - } - } - - button:hover { - transform: scale(1); - } + position: absolute; + top: 2; + left: 2; + box-shadow: black 0.3em 0.3em 1em; + border-radius: 5px; + display: flex; + opacity: 0.3; + width: 25px; + height: 25px; + align-items: center; + > svg { + margin: auto; } } - - .webpage-urlInput { - padding: 12px 10px 11px 10px; - border: 0px; - color: grey; - letter-spacing: 2px; - outline-color: black; - background: rgb(238, 238, 238); - width: 100%; - margin-right: 10px; - height: 100%; + .webBox-annotationToggle:hover { + opacity: 1; } .touch-iframe-overlay { diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index 1393e7868..c5d7c3c9f 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -24,13 +24,14 @@ import { ContextMenu } from "../ContextMenu"; import { ContextMenuProps } from "../ContextMenuItem"; import { ViewBoxAnnotatableComponent } from "../DocComponent"; import { DocumentDecorations } from "../DocumentDecorations"; -import Annotation from "../pdf/Annotation"; -import PDFMenu from "../pdf/PDFMenu"; +import { Annotation } from "../pdf/Annotation"; +import { PDFMenu } from "../pdf/PDFMenu"; import { PdfViewerMarquee } from "../pdf/PDFViewer"; import { FieldView, FieldViewProps } from './FieldView'; import "./WebBox.scss"; import "../pdf/PDFViewer.scss"; import React = require("react"); +import { Tooltip } from '@material-ui/core'; const htmlToText = require("html-to-text"); type WebDocument = makeInterface<[typeof documentSchema]>; @@ -235,81 +236,16 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum e.stopPropagation(); } - toggleAnnotationMode = () => { - this.layoutDoc.isAnnotating = !this.layoutDoc.isAnnotating; - } - - urlEditor() { - return ( - <div className="webBox-urlEditor" - onDrop={this.onUrlDrop} - onDragOver={this.onUrlDragover} style={{ top: this._collapsed ? -70 : 0 }}> - <div className="urlEditor"> - <div className="editorBase"> - <button className="editor-collapse" - style={{ - top: this._collapsed ? 70 : 0, - transform: `rotate(${this._collapsed ? 180 : 0}deg) scale(${this._collapsed ? 0.5 : 1}) translate(${this._collapsed ? "-100%, -100%" : "0, 0"})`, - opacity: (this._collapsed && !this.props.isSelected()) ? 0 : 0.9, - left: (this._collapsed ? 0 : "unset"), - }} - title="Collapse Url Editor" onClick={this.toggleCollapse}> - <FontAwesomeIcon icon="caret-up" size="2x" /> - </button> - <div className="webBox-buttons" - onDrop={this.onUrlDrop} - onDragOver={this.onUrlDragover} style={{ display: this._collapsed ? "none" : "flex" }}> - <div className="webBox-freeze" title={"Annotate"} style={{ background: this.layoutDoc.isAnnotating ? "lightBlue" : "gray" }} onClick={this.toggleAnnotationMode} > - <FontAwesomeIcon icon={faPen} size={"2x"} /> - </div> - <div className="webBox-freeze" title={"Select"} style={{ background: !this.layoutDoc.isAnnotating ? "lightBlue" : "gray" }} onClick={this.toggleAnnotationMode} > - <FontAwesomeIcon icon={faMousePointer} size={"2x"} /> - </div> - <input className="webpage-urlInput" - placeholder="ENTER URL" - value={this._url} - onDrop={this.onUrlDrop} - onDragOver={this.onUrlDragover} - onChange={this.onURLChange} - onKeyDown={this.onValueKeyDown} - onClick={(e) => { - this._keyInput.current!.select(); - e.stopPropagation(); - }} - ref={this._keyInput} - /> - <div style={{ - display: "flex", - flexDirection: "row", - justifyContent: "space-between", - maxWidth: "120px", - }}> - <button className="submitUrl" onClick={this.submitURL} - onDragOver={this.onUrlDragover} onDrop={this.onUrlDrop}> - GO - </button> - <button className="submitUrl" onClick={this.back}> - <FontAwesomeIcon icon="caret-left" size="lg"></FontAwesomeIcon> - </button> - <button className="submitUrl" onClick={this.forward}> - <FontAwesomeIcon icon="caret-right" size="lg"></FontAwesomeIcon> - </button> - </div> - </div> - </div> - </div> + editToggleBtn() { + return <Tooltip title={<div className="dash-tooltip" >{`${this.props.Document.isAnnotating ? "Exit" : "Enter"} annotation mode`}</div>}> + <div className="webBox-annotationToggle" + style={{ color: this.props.Document.isAnnotating ? "black" : "white", backgroundColor: this.props.Document.isAnnotating ? "white" : "black" }} + onClick={action(() => this.layoutDoc.isAnnotating = !this.layoutDoc.isAnnotating)}> + <FontAwesomeIcon icon="edit" size="sm" /> </div> - ); + </Tooltip>; } - - @action - toggleCollapse = () => { - this._collapsed = !this._collapsed; - } - - - _ignore = 0; onPreWheel = (e: React.WheelEvent) => { this._ignore = e.timeStamp; @@ -438,7 +374,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum specificContextMenu = (e: React.MouseEvent): void => { const cm = ContextMenu.Instance; const funcs: ContextMenuProps[] = []; - funcs.push({ description: (this.layoutDoc.UseCors ? "Don't Use" : "Use") + " Cors", event: () => this.layoutDoc.UseCors = !this.layoutDoc.UseCors, icon: "snowflake" }); + funcs.push({ description: (this.layoutDoc.useCors ? "Don't Use" : "Use") + " Cors", event: () => this.layoutDoc.useCors = !this.layoutDoc.useCors, icon: "snowflake" }); cm.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" }); } @@ -453,7 +389,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum if (field instanceof HtmlField) { view = <span className="webBox-htmlSpan" dangerouslySetInnerHTML={{ __html: field.html }} />; } else if (field instanceof WebField) { - const url = this.layoutDoc.UseCors ? Utils.CorsProxy(field.url.href) : field.url.href; + const url = this.layoutDoc.useCors ? Utils.CorsProxy(field.url.href) : field.url.href; view = <iframe className="webBox-iframe" enable-annotation={"true"} ref={this._iframeRef} src={url} onLoad={this.iframeLoaded} // the 'allow-top-navigation' and 'allow-top-navigation-by-user-activation' attributes are left out to prevent iframes from redirecting the top-level Dash page sandbox={"allow-forms allow-modals allow-orientation-lock allow-pointer-lock allow-popups allow-popups-to-escape-sandbox allow-presentation allow-same-origin allow-scripts"} />; @@ -474,16 +410,15 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum style={{ width: Number.isFinite(this.props.ContentScaling()) ? `${Math.max(100, 100 / this.props.ContentScaling())}% ` : "100%" }} onWheel={this.onPostWheel} onPointerDown={this.onPostPointer} onPointerMove={this.onPostPointer} onPointerUp={this.onPostPointer}> {view} - </div>; + </div> {!frozen ? (null) : - <div className="webBox-overlay" style={{ pointerEvents: this.layoutDoc.isBackground ? undefined : "all" }} + <div className="webBox-overlay" style={{ pointerEvents: this.layoutDoc._isBackground ? undefined : "all" }} onWheel={this.onPreWheel} onPointerDown={this.onPrePointer} onPointerMove={this.onPrePointer} onPointerUp={this.onPrePointer}> <div className="touch-iframe-overlay" onPointerDown={this.onLongPressDown} > <div className="indicator" ref={this._iframeIndicatorRef}></div> <div className="dragger" ref={this._iframeDragRef}></div> </div> </div>} - {this.urlEditor()} </>); } @@ -560,12 +495,12 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum Doc.GetProto(targetDoc).data = new List<Doc>([clipDoc]); clipDoc.rootDocument = targetDoc; targetDoc.layoutKey = "layout"; - const annotationDoc = this.highlight("rgba(146, 245, 95, 0.467)"); // yellowish highlight color when dragging out a text selection + const annotationDoc = this.highlight("rgba(173, 216, 230, 0.75)"); // hyperlink color if (annotationDoc) { DragManager.StartPdfAnnoDrag([ele], new DragManager.PdfAnnoDragData(this.props.Document, annotationDoc, targetDoc), e.pageX, e.pageY, { dragComplete: e => { - if (!e.aborted && e.annoDragData && !e.annoDragData.linkedToDoc) { - DocUtils.MakeLink({ doc: annotationDoc }, { doc: e.annoDragData.dropDocument }, "Annotation"); + if (!e.aborted && e.annoDragData && !e.annoDragData.linkDocument) { + e.annoDragData.linkDocument = DocUtils.MakeLink({ doc: annotationDoc }, { doc: e.annoDragData.dropDocument }, "Annotation"); annotationDoc.isLinkButton = true; } } @@ -682,7 +617,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum transform: `scale(${this.props.ContentScaling()})`, width: Number.isFinite(this.props.ContentScaling()) ? `${100 / this.props.ContentScaling()}% ` : "100%", height: Number.isFinite(this.props.ContentScaling()) ? `${100 / this.props.ContentScaling()}% ` : "100%", - pointerEvents: this.layoutDoc.isBackground ? "none" : undefined + pointerEvents: this.layoutDoc._isBackground ? "none" : undefined }} onContextMenu={this.specificContextMenu}> <base target="_blank" /> @@ -690,7 +625,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum <div className={"webBox-outerContent"} ref={this._outerRef} style={{ width: Number.isFinite(this.props.ContentScaling()) ? `${Math.max(100, 100 / this.props.ContentScaling())}% ` : "100%", - pointerEvents: this.layoutDoc.isAnnotating && !this.layoutDoc.isBackground ? "all" : "none" + pointerEvents: this.layoutDoc.isAnnotating && !this.layoutDoc._isBackground ? "all" : "none" }} onWheel={e => e.stopPropagation()} onPointerDown={this.onMarqueeDown} @@ -709,7 +644,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum }}> <div className={"webBox-innerContent"} style={{ height: NumCast(this.layoutDoc.scrollHeight), - pointerEvents: this.layoutDoc.isBackground ? "none" : undefined + pointerEvents: this.layoutDoc._isBackground ? "none" : undefined }}> <CollectionFreeFormView {...this.props} PanelHeight={this.props.PanelHeight} @@ -733,6 +668,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum ScreenToLocalTransform={this.scrollXf} renderDepth={this.props.renderDepth + 1} docFilters={this.props.docFilters} + searchFilterDocs={this.props.searchFilterDocs} ContainingCollectionDoc={this.props.ContainingCollectionDoc}> </CollectionFreeFormView> </div> @@ -740,6 +676,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum {this.annotationLayer} <PdfViewerMarquee isMarqueeing={this.marqueeing} width={this.marqueeWidth} height={this.marqueeHeight} x={this.marqueeX} y={this.marqueeY} /> </div > + + {this.props.isSelected() ? this.editToggleBtn() : null} </div>); } }
\ No newline at end of file diff --git a/src/client/views/nodes/formattedText/DashDocView.tsx b/src/client/views/nodes/formattedText/DashDocView.tsx index 212da3f3d..3b77735a7 100644 --- a/src/client/views/nodes/formattedText/DashDocView.tsx +++ b/src/client/views/nodes/formattedText/DashDocView.tsx @@ -5,13 +5,14 @@ import { Id } from "../../../../fields/FieldSymbols"; import { ObjectField } from "../../../../fields/ObjectField"; import { ComputedField } from "../../../../fields/ScriptField"; import { BoolCast, Cast, NumCast, StrCast } from "../../../../fields/Types"; -import { emptyFunction, returnEmptyString, returnFalse, Utils, returnZero, returnEmptyFilter } from "../../../../Utils"; +import { emptyFunction, returnEmptyString, returnFalse, Utils, returnZero, returnEmptyFilter, returnEmptyDoclist } from "../../../../Utils"; import { DocServer } from "../../../DocServer"; import { Docs, DocUtils } from "../../../documents/Documents"; import { DocumentView } from "../DocumentView"; import { FormattedTextBox } from "./FormattedTextBox"; import { Transform } from "../../../util/Transform"; import React = require("react"); +import { CurrentUserUtils } from "../../../util/CurrentUserUtils"; interface IDashDocView { node: any; @@ -114,7 +115,7 @@ export class DashDocView extends React.Component<IDashDocView> { } /*endregion*/ - componentWillMount = () => { + componentWillUnmount = () => { this._reactionDisposer?.(); } @@ -175,7 +176,7 @@ export class DashDocView extends React.Component<IDashDocView> { const outerStyle = { position: "relative" as "relative", textIndent: "0", - border: "1px solid " + StrCast(this._textBox.Document.color, (Cast(Doc.UserDoc().activeWorkspace, Doc, null).darkScheme ? "dimGray" : "lightGray")), + border: "1px solid " + StrCast(this._textBox.Document.color, (CurrentUserUtils.ActiveDashboard.darkScheme ? "dimGray" : "lightGray")), width: this.props.node.props.width, height: this.props.node.props.height, display: this.props.node.props.hidden ? "none" : "inline-block", @@ -202,7 +203,7 @@ export class DashDocView extends React.Component<IDashDocView> { ({ dim, color }) => { spanStyle.width = outerStyle.width = Math.max(20, dim[0]) + "px"; spanStyle.height = outerStyle.height = Math.max(20, dim[1]) + "px"; - outerStyle.border = "1px solid " + StrCast(finalLayout.color, (Cast(Doc.UserDoc().activeWorkspace, Doc, null).darkScheme ? "dimGray" : "lightGray")); + outerStyle.border = "1px solid " + StrCast(finalLayout.color, (CurrentUserUtils.ActiveDashboard.darkScheme ? "dimGray" : "lightGray")); }, { fireImmediately: true }); if (node.attrs.width !== dashDoc._width + "px" || node.attrs.height !== dashDoc._height + "px") { @@ -254,7 +255,8 @@ export class DashDocView extends React.Component<IDashDocView> { whenActiveChanged={returnFalse} bringToFront={emptyFunction} dontRegisterView={false} - docFilters={this.props.tbox?.props.docFilters||returnEmptyFilter} + docFilters={this.props.tbox?.props.docFilters || returnEmptyFilter} + searchFilterDocs={this.props.tbox?.props.searchFilterDocs || returnEmptyDoclist} ContainingCollectionView={this._textBox.props.ContainingCollectionView} ContainingCollectionDoc={this._textBox.props.ContainingCollectionDoc} ContentScaling={this.contentScaling} diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx index 8ae71c035..f2658e77e 100644 --- a/src/client/views/nodes/formattedText/DashFieldView.tsx +++ b/src/client/views/nodes/formattedText/DashFieldView.tsx @@ -169,7 +169,7 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna onPointerDownEnumerables = async (e: any) => { e.stopPropagation(); const collview = await DocUtils.addFieldEnumerations(this._textBoxDoc, this._fieldKey, [{ title: this._fieldKey }]); - collview instanceof Doc && this.props.tbox.props.addDocTab(collview, "onRight"); + collview instanceof Doc && this.props.tbox.props.addDocTab(collview, "add:right"); } @@ -183,7 +183,7 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna } if (container) { const alias = Doc.MakeAlias(container.props.Document); - alias.viewType = CollectionViewType.Time; + alias._viewType = CollectionViewType.Time; let list = Cast(alias._columnHeaders, listSpec(SchemaHeaderField)); if (!list) { alias._columnHeaders = list = new List<SchemaHeaderField>(); @@ -191,7 +191,7 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna list.map(c => c.heading).indexOf(this._fieldKey) === -1 && list.push(new SchemaHeaderField(this._fieldKey, "#f1efeb")); list.map(c => c.heading).indexOf("text") === -1 && list.push(new SchemaHeaderField("text", "#f1efeb")); alias._pivotField = this._fieldKey.startsWith("#") ? "#" : this._fieldKey; - this.props.tbox.props.addDocTab(alias, "onRight"); + this.props.tbox.props.addDocTab(alias, "add:right"); } } diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss index afdd8fea2..160f4ba72 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.scss +++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss @@ -32,8 +32,8 @@ .formattedTextBox-dictation { height: 12px; width: 10px; - top: 0px; - left: 0px; + bottom: 5px; + right: 8px; position: absolute; } } diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 2ded33346..311143ff7 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -1,5 +1,3 @@ -import { library } from '@fortawesome/fontawesome-svg-core'; -import { faEdit, faSmile, faTextHeight, faUpload } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { isEqual } from "lodash"; import { action, computed, IReactionDisposer, Lambda, observable, reaction, runInAction, trace } from "mobx"; @@ -22,7 +20,7 @@ import { InkTool } from '../../../../fields/InkField'; import { PrefetchProxy } from '../../../../fields/Proxy'; import { RichTextField } from "../../../../fields/RichTextField"; import { RichTextUtils } from '../../../../fields/RichTextUtils'; -import { createSchema, makeInterface } from "../../../../fields/Schema"; +import { makeInterface } from "../../../../fields/Schema"; import { Cast, DateCast, NumCast, StrCast, ScriptCast, BoolCast } from "../../../../fields/Types"; import { TraceMobx, OVERRIDE_ACL, GetEffectiveAcl } from '../../../../fields/util'; import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, returnOne, returnZero, Utils, setupMoveUpEvents } from '../../../../Utils'; @@ -33,8 +31,8 @@ import { DocumentType } from '../../../documents/DocumentTypes'; import { DictationManager } from '../../../util/DictationManager'; import { DragManager } from "../../../util/DragManager"; import { makeTemplate } from '../../../util/DropConverter'; -import buildKeymap, { updateBullets } from "./ProsemirrorExampleTransfer"; -import RichTextMenu from './RichTextMenu'; +import { buildKeymap, updateBullets } from "./ProsemirrorExampleTransfer"; +import { RichTextMenu, RichTextMenuPlugin } from './RichTextMenu'; import { RichTextRules } from "./RichTextRules"; //import { DashDocView } from "./DashDocView"; @@ -61,9 +59,6 @@ import { FormattedTextBoxComment, formattedTextBoxCommentPlugin, findLinkMark } import React = require("react"); import { DocumentManager } from '../../../util/DocumentManager'; -library.add(faEdit); -library.add(faSmile, faTextHeight, faUpload); - export interface FormattedTextBoxProps { makeLink?: () => Opt<Doc>; // bcz: hack: notifies the text document when the container has made a link. allows the text doc to react and setup a hyeprlink for any selected text hideOnLeave?: boolean; // used by DocumentView for setting caption's hide on leave (bcz: would prefer to have caption-hideOnLeave field set or something similar) @@ -99,8 +94,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp private _linkTime: number | null = null; private _pause: boolean = false; - @computed get _recording() { return this.dataDoc.audioState === "recording"; } - set _recording(value) { this.dataDoc.audioState = value ? "recording" : undefined; } + @computed get _recording() { return this.dataDoc?.audioState === "recording"; } + set _recording(value) { + this.dataDoc.audioState = value ? "recording" : undefined; + } @observable private _entered = false; @@ -225,7 +222,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const id = Utils.GenerateDeterministicGuid(this.dataDoc[Id] + key); const allLinks = [{ href: Utils.prepend("/doc/" + id), title: value, targetId: id }]; - const link = this._editorView.state.schema.marks.linkAnchor.create({ allLinks, location: "onRight", title: value }); + const link = this._editorView.state.schema.marks.linkAnchor.create({ allLinks, location: "add:right", title: value }); const mval = this._editorView.state.schema.marks.metadataVal.create(); 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); @@ -350,7 +347,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp updateTitle = () => { if ((this.props.Document.isTemplateForField === "text" || !this.props.Document.isTemplateForField) && // only update the title if the data document's data field is changing - StrCast(this.dataDoc.title).startsWith("-") && this._editorView && !this.rootDoc.customTitle) { + StrCast(this.dataDoc.title).startsWith("-") && this._editorView && !this.dataDoc["title-custom"] && + Doc.LayoutFieldKey(this.rootDoc) === this.fieldKey) { let node = this._editorView.state.doc; while (node.firstChild && node.firstChild.type.name !== "text") node = node.firstChild; const str = node.textContent; @@ -374,10 +372,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp this._editorView.dispatch(tr.addMark(flattened[lastSel].from, flattened[lastSel].to, link)); } } - public highlightSearchTerms = (terms: string[], alt: boolean) => { + public highlightSearchTerms = (terms: string[], backward: boolean) => { if (this._editorView && (this._editorView as any).docView && terms.some(t => t)) { - - 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 }); const res = terms.filter(t => t).map(term => this.findInNode(this._editorView!, this._editorView!.state.doc, term)); @@ -385,31 +381,23 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp let tr = this._editorView.state.tr; const flattened: TextSelection[] = []; res.map(r => r.map(h => flattened.push(h))); - if (BoolCast(Doc.GetProto(this.dataDoc).resetSearch) === true) { - this._searchIndex = 0; - Doc.GetProto(this.dataDoc).resetSearch = undefined; - } - else { - this._searchIndex = ++this._searchIndex > flattened.length - 1 ? 0 : this._searchIndex; - if (alt === true) { - if (this._searchIndex > 1) { - this._searchIndex += -2; - } - else if (this._searchIndex === 1) { - this._searchIndex = length - 1; - } - else if (this._searchIndex === 0 && length !== 1) { - this._searchIndex = length - 2; - } - + this._searchIndex = ++this._searchIndex > flattened.length - 1 ? 0 : this._searchIndex; + if (backward === true) { + if (this._searchIndex > 1) { + this._searchIndex += -2; } + else if (this._searchIndex === 1) { + this._searchIndex = length - 1; + } + else if (this._searchIndex === 0 && length !== 1) { + this._searchIndex = length - 2; + } + } 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)); flattened[lastSel] && this._editorView.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(flattened[lastSel].from), tr.doc.resolve(flattened[lastSel].to))).scrollIntoView()); - - console.log(this._searchIndex); } } @@ -472,12 +460,15 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } else if (de.complete.linkDragData) { de.complete.linkDragData.linkDropCallback = this.linkDrop; } + else if (de.complete.annoDragData) { + de.complete.annoDragData.linkDropCallback = this.linkDrop; + } } - linkDrop = (data: DragManager.LinkDragData) => { + linkDrop = (data: { linkDocument?: Doc }) => { const linkDoc = data.linkDocument!; const anchor1Title = linkDoc.anchor1 instanceof Doc ? StrCast(linkDoc.anchor1.title) : "-untitled-"; const anchor1Id = linkDoc.anchor1 instanceof Doc ? linkDoc.anchor1[Id] : ""; - this.makeLinkToSelection(linkDoc[Id], anchor1Title, "onRight", anchor1Id); + this.makeLinkToSelection(linkDoc[Id], anchor1Title, "add:right", anchor1Id); } getNodeEndpoints(context: Node, node: Node): { from: number, to: number } | null { @@ -511,10 +502,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp if (node.isTextblock) { let index = 0, foundAt; const ep = this.getNodeEndpoints(pm.state.doc, node); - while (ep && (foundAt = node.textContent.slice(index).search(RegExp(find, "i"))) > -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; + const regexp = new RegExp(find.replace("*", ""), "i"); + if (regexp) { + while (ep && (foundAt = node.textContent.slice(index).search(regexp)) > -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; + } } } else { node.content.forEach((child, i) => ret = ret.concat(this.findInNode(pm, child, find))); @@ -664,7 +658,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const newBullets: Doc[] = this.recursiveProgressivize(1, list)[0]; mainBulletList.push.apply(mainBulletList, newBullets); } - console.log(mainBulletList.length); const title = Docs.Create.TextDocument(StrCast(this.rootDoc.title), { title: "Title", _width: 800, _height: 70, x: 20, y: -10, _fontSize: '20pt', backgroundColor: "rgba(0,0,0,0)", appearFrame: 0, _fontWeight: 700 }); mainBulletList.push(title); const doc = Docs.Create.FreeformDocument(mainBulletList, { @@ -719,7 +712,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp recordDictation = () => { DictationManager.Controls.listen({ - interimHandler: this.setCurrentBulletContent, + interimHandler: this.setDictationContent, continuous: { indefinite: false }, }).then(results => { if (results && [DictationManager.Controls.Infringed].includes(results)) { @@ -730,22 +723,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } stopDictation = (abort: boolean) => { DictationManager.Controls.stop(!abort); }; - recordBullet = async () => { - const completedCue = "end session"; - const results = await DictationManager.Controls.listen({ - interimHandler: this.setCurrentBulletContent, - continuous: { indefinite: false }, - terminators: [completedCue, "bullet", "next"] - }); - if (results && [DictationManager.Controls.Infringed, completedCue].includes(results)) { - DictationManager.Controls.stop(); - return; - } - this.nextBullet(this._editorView!.state.selection.to); - setTimeout(this.recordBullet, 2000); - } - - setCurrentBulletContent = (value: string) => { + setDictationContent = (value: string) => { if (this._editorView) { const state = this._editorView.state; const now = Date.now(); @@ -760,33 +738,17 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } } } - const recordingStart = DateCast(this.props.Document.recordingStart).date.getTime(); - this._break = false; - value = "" + (mark.attrs.modified * 1000 - recordingStart) / 1000 + value; const from = state.selection.from; - const inserted = state.tr.insertText(value).addMark(from, from + value.length + 1, mark); - this._editorView.dispatch(inserted.setSelection(TextSelection.create(inserted.doc, from, from + value.length + 1))); - } - } - - nextBullet = (pos: number) => { - if (this._editorView) { - const frag = Fragment.fromArray(this.newListItems(2)); - if (this._editorView.state.doc.resolve(pos).depth >= 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; - state = this._editorView.state; - this._editorView.dispatch(state.tr.setSelection(TextSelection.create(this._editorView.state.doc, pos, pos))); + this._break = false; + if (this.props.Document.recordingStart) { + const recordingStart = DateCast(this.props.Document.recordingStart)?.date.getTime(); + value = "" + (mark.attrs.modified * 1000 - recordingStart) / 1000 + value; } + const tr = state.tr.insertText(value).addMark(from, from + value.length + 1, mark); + this._editorView.dispatch(tr.setSelection(TextSelection.create(tr.doc, from, from + value.length + 1))); } } - private newListItems = (count: number) => { - return numberRange(count).map(x => schema.nodes.list_item.create(undefined, schema.nodes.paragraph.create())); - } - _keymap: any = undefined; _rules: RichTextRules | undefined; @computed get config() { @@ -852,13 +814,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp if (linkDoc) { const anchor2Title = linkDoc.anchor2 instanceof Doc ? StrCast(linkDoc.anchor2.title) : "-untitled-"; const anchor2Id = linkDoc.anchor2 instanceof Doc ? linkDoc.anchor2[Id] : ""; - this.makeLinkToSelection(linkDoc[Id], anchor2Title, "onRight", anchor2Id); + this.makeLinkToSelection(linkDoc[Id], anchor2Title, "add:right", anchor2Id); } }, { fireImmediately: true } ); this._disposers.editorState = reaction( () => { + if (!this.dataDoc || !this.layoutDoc) return undefined; if (this.dataDoc?.[this.props.fieldKey + "-noTemplate"] || !this.layoutDoc[this.props.fieldKey + "-textTemplate"]) { return Cast(this.dataDoc[this.props.fieldKey], RichTextField, null)?.Data; } @@ -894,13 +857,16 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } ); this._disposers.autoHeight = reaction( - () => [this.layoutDoc[WidthSym](), this.layoutDoc._autoHeight], - () => setTimeout(() => this.tryUpdateHeight(), 0) + () => ({ + width: NumCast(this.layoutDoc._width), + autoHeight: this.layoutDoc?._autoHeight + }), + ({ width, autoHeight }) => width !== undefined && setTimeout(() => this.tryUpdateHeight(), 0) ); this._disposers.height = reaction( - () => this.layoutDoc[HeightSym](), + () => NumCast(this.layoutDoc._height), action(height => { - if (height <= 20 && height < NumCast(this.layoutDoc._delayAutoHeight, 20)) { + if (height !== undefined && height <= 20 && height < NumCast(this.layoutDoc._delayAutoHeight, 20)) { this.layoutDoc._delayAutoHeight = height; } }) @@ -908,36 +874,38 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp this.setupEditor(this.config, this.props.fieldKey); - this._disposers.searchAlt = reaction(() => this.rootDoc.searchMatchAlt, - search => search ? this.highlightSearchTerms([Doc.SearchQuery()], false) : this.unhighlightSearchTerms(), - { fireImmediately: true }); - this._disposers.search = reaction(() => this.rootDoc.searchMatch, - search => search ? this.highlightSearchTerms([Doc.SearchQuery()], true) : this.unhighlightSearchTerms(), - { fireImmediately: this.rootDoc.searchMatch ? true : false }); - - this._disposers.record = reaction(() => this._recording, - () => { - if (this._recording) { - setTimeout(action(() => { - this.stopDictation(true); - setTimeout(() => this.recordDictation(), 500); - }), 500); - } else setTimeout(() => this.stopDictation(true), 0); - } - ); + this._disposers.search = reaction(() => Doc.IsSearchMatch(this.rootDoc), + search => search ? this.highlightSearchTerms([Doc.SearchQuery()], search.searchMatch < 0) : this.unhighlightSearchTerms(), + { fireImmediately: Doc.IsSearchMatchUnmemoized(this.rootDoc) ? true : false }); + + this._disposers.selected = reaction(() => this.props.isSelected(), action(() => this._recording = false)); + + if (!this.props.dontRegisterView) { + this._disposers.record = reaction(() => this._recording, + () => { + if (this._recording) { + setTimeout(action(() => { + this.stopDictation(true); + setTimeout(() => this.recordDictation(), 500); + }), 500); + } else setTimeout(() => this.stopDictation(true), 0); + } + ); + } this._disposers.scrollToRegion = reaction( () => StrCast(this.layoutDoc.scrollToLinkID), async (scrollToLinkID) => { const findLinkFrag = (frag: Fragment, editor: EditorView) => { const nodes: Node[] = []; + let offset = 0; frag.forEach((node, index) => { const examinedNode = findLinkNode(node, editor); if (examinedNode?.textContent) { nodes.push(examinedNode); - start += index; + offset = index; } }); - return { frag: Fragment.fromArray(nodes), start: start }; + return { frag: Fragment.fromArray(nodes), start: start + offset }; }; const findLinkNode = (node: Node, editor: EditorView) => { if (!node.isText) { @@ -949,7 +917,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp return linkIndex !== -1 && marks[linkIndex].attrs.allLinks.find((item: { href: string }) => scrollToLinkID === item.href.replace(/.*\/doc\//, "")) ? node : undefined; }; - let start = 0; + const start = 0; if (this._editorView && scrollToLinkID) { const editor = this._editorView; const ret = findLinkFrag(editor.state.doc.content, editor); @@ -1036,7 +1004,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } }, 0); dataDoc.title = exportState.title; - this.rootDoc.customTitle = true; + this.dataDoc["title-custom"] = true; dataDoc.unchanged = true; } else { delete dataDoc[GoogleRef]; @@ -1131,7 +1099,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const marks = [...node.marks]; const linkIndex = marks.findIndex(mark => mark.type.name === "link"); const allLinks = [{ href: Utils.prepend(`/doc/${linkId}`), title, linkId }]; - const link = view.state.schema.mark(view.state.schema.marks.linkAnchor, { allLinks, location: "onRight", title, docref: true }); + const link = view.state.schema.mark(view.state.schema.marks.linkAnchor, { allLinks, location: "add:right", title, docref: true }); marks.splice(linkIndex === -1 ? 0 : linkIndex, 1, link); return node.mark(marks); } @@ -1278,10 +1246,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp FormattedTextBoxComment.Hide(); if (FormattedTextBoxComment.linkDoc) { if (FormattedTextBoxComment.linkDoc.type !== DocumentType.LINK) { - this.props.addDocTab(FormattedTextBoxComment.linkDoc, e.ctrlKey ? "inTab" : "onRight"); + this.props.addDocTab(FormattedTextBoxComment.linkDoc, e.ctrlKey ? "add" : "add:right"); } else { DocumentManager.Instance.FollowLink(FormattedTextBoxComment.linkDoc, this.props.Document, - (doc: Doc, followLinkLocation: string) => this.props.addDocTab(doc, e.ctrlKey ? "inTab" : followLinkLocation)); + (doc: Doc, followLinkLocation: string) => this.props.addDocTab(doc, e.ctrlKey ? "add" : followLinkLocation)); } } @@ -1307,7 +1275,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp // jump rich text menu to this textbox const bounds = this._ref.current?.getBoundingClientRect(); - if (bounds && this.layoutDoc._chromeStatus !== "disabled") { + if (bounds && this.layoutDoc._chromeStatus !== "disabled" && RichTextMenu.Instance) { const x = Math.min(Math.max(bounds.left, 0), window.innerWidth - RichTextMenu.Instance.width); let y = Math.min(Math.max(0, bounds.top - RichTextMenu.Instance.height - 50), window.innerHeight - RichTextMenu.Instance.height); if (coords && coords.left > x && coords.left < x + RichTextMenu.Instance.width && coords.top > y && coords.top < y + RichTextMenu.Instance.height + 50) { @@ -1411,11 +1379,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } } + menuPlugin: any; + richTextMenuPlugin() { + const self = this; return new Plugin({ view(newView) { - RichTextMenu.Instance?.changeView(newView); - return RichTextMenu.Instance; + self.props.isSelected(true) && RichTextMenu.Instance && (RichTextMenu.Instance.view = newView); + return self.menuPlugin = new RichTextMenuPlugin({ editorProps: this.props }); } }); } @@ -1432,13 +1403,15 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } return wasUndoing; } + public static LiveTextUndo: UndoManager.Batch | undefined; public static HadSelection: boolean = false; onBlur = (e: any) => { FormattedTextBox.HadSelection = window.getSelection()?.toString() !== ""; - //DictationManager.Controls.stop(false); this.endUndoTypingBatch(); this.doLinkOnDeselect(); + FormattedTextBox.LiveTextUndo?.end(); + FormattedTextBox.LiveTextUndo = undefined; // move the richtextmenu offscreen //if (!RichTextMenu.Instance.Pinned) RichTextMenu.Instance.delayHide(); } @@ -1524,8 +1497,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp TraceMobx(); const scale = this.props.hideOnLeave ? 1 : this.props.ContentScaling() * NumCast(this.layoutDoc._viewScale, 1); const rounded = StrCast(this.layoutDoc.borderRounding) === "100%" ? "-rounded" : ""; - const interactive = Doc.GetSelectedTool() === InkTool.None && !this.layoutDoc.isBackground; - setTimeout(() => this._editorView && RichTextMenu.Instance.updateFromDash(this._editorView, undefined, this.props), this.props.isSelected() ? 10 : 0); // need to make sure that we update a text box that is selected after updating the one that was deselected + const interactive = Doc.GetSelectedTool() === InkTool.None && !this.layoutDoc._isBackground; + this.props.isSelected() && setTimeout(() => this._editorView && RichTextMenu.Instance?.updateMenu(this._editorView, undefined, this.props), 0); // need to make sure that we update a text box that is selected after updating the one that was deselected if (!this.props.isSelected() && FormattedTextBoxComment.textBox === this) { setTimeout(() => FormattedTextBoxComment.Hide(), 0); } @@ -1546,13 +1519,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp width: "100%", height: this.props.height ? this.props.height : this.layoutDoc._autoHeight && this.props.renderDepth ? "max-content" : undefined, background: Doc.UserDoc().renderStyle === "comic" ? "transparent" : this.props.background ? this.props.background : StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], this.props.hideOnLeave ? "rgba(0,0,0 ,0.4)" : ""), - opacity: this.props.hideOnLeave ? (this._entered ? 1 : 0.1) : 1, color: this.props.color ? this.props.color : StrCast(this.layoutDoc[this.props.fieldKey + "-color"], this.props.hideOnLeave ? "white" : "inherit"), pointerEvents: interactive ? undefined : "none", fontSize: Cast(this.layoutDoc._fontSize, "string", null), fontWeight: Cast(this.layoutDoc._fontWeight, "number", null), fontFamily: StrCast(this.layoutDoc._fontFamily, "inherit"), - transition: "opacity 1s" }} onContextMenu={this.specificContextMenu} onKeyDown={this.onKeyPress} @@ -1616,14 +1587,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp <div className="formattedTextBox-sidebar-handle" onPointerDown={this.sidebarDown} /> </div>} {!this.layoutDoc._showAudio ? (null) : - <div className="formattedTextBox-dictation" - onPointerDown={e => { - runInAction(() => this._recording = !this._recording); - setTimeout(() => this._editorView!.focus(), 500); - e.stopPropagation(); - }} > + <div className="formattedTextBox-dictation" onClick={action(e => this._recording = !this._recording)} > <FontAwesomeIcon className="formattedTextBox-audioFont" - style={{ color: this._recording ? "red" : "blue", opacity: this._recording ? 1 : 0.5, display: this.props.isSelected() ? "" : "none" }} icon={"microphone"} size="sm" /> + style={{ + color: this._recording ? "red" : "blue", + transitionDelay: "0.6s", + opacity: this._recording ? 1 : 0.25, + }} + icon={"microphone"} size="sm" /> </div>} </div> </div> diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx index 6f3984f39..b4f648273 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx @@ -4,7 +4,7 @@ import { EditorView } from "prosemirror-view"; import * as ReactDOM from 'react-dom'; import { Doc, DocCastAsync, Opt } from "../../../../fields/Doc"; import { Cast, FieldValue, NumCast, StrCast } from "../../../../fields/Types"; -import { emptyFunction, returnEmptyString, returnFalse, Utils, emptyPath, returnZero, returnOne, returnEmptyFilter } from "../../../../Utils"; +import { emptyFunction, returnEmptyString, returnFalse, Utils, emptyPath, returnZero, returnOne, returnEmptyFilter, returnEmptyDoclist } from "../../../../Utils"; import { DocServer } from "../../../DocServer"; import { DocumentManager } from "../../../util/DocumentManager"; import { schema } from "./schema_rts"; @@ -103,7 +103,7 @@ export class FormattedTextBoxComment { this.deleteLink(); } else if (FormattedTextBoxComment._followRef && FormattedTextBoxComment._followRef.contains(e.target as any)) { if (FormattedTextBoxComment.linkDoc.type !== DocumentType.LINK) { - textBox.props.addDocTab(FormattedTextBoxComment.linkDoc, e.ctrlKey ? "inTab" : "onRight"); + textBox.props.addDocTab(FormattedTextBoxComment.linkDoc, e.ctrlKey ? "add" : "add:right"); } else { const anchor = FieldValue(Doc.AreProtosEqual(FieldValue(Cast(FormattedTextBoxComment.linkDoc.anchor1, Doc)), textBox.dataDoc) ? Cast(FormattedTextBoxComment.linkDoc.anchor2, Doc) : (Cast(FormattedTextBoxComment.linkDoc.anchor1, Doc)) @@ -111,29 +111,29 @@ export class FormattedTextBoxComment { const target = anchor?.annotationOn ? await DocCastAsync(anchor.annotationOn) : anchor; if (FormattedTextBoxComment.linkDoc.follow) { - if (FormattedTextBoxComment.linkDoc.follow === "Default") { - DocumentManager.Instance.FollowLink(FormattedTextBoxComment.linkDoc, textBox.props.Document, doc => textBox.props.addDocTab(doc, "onRight"), false); + if (FormattedTextBoxComment.linkDoc.follow === "default") { + DocumentManager.Instance.FollowLink(FormattedTextBoxComment.linkDoc, textBox.props.Document, doc => textBox.props.addDocTab(doc, "add:right"), false); } else if (FormattedTextBoxComment.linkDoc.follow === "Always open in right tab") { - if (target) { textBox.props.addDocTab(target, "onRight"); } + if (target) { textBox.props.addDocTab(target, "add:right"); } } else if (FormattedTextBoxComment.linkDoc.follow === "Always open in new tab") { - if (target) { textBox.props.addDocTab(target, "inTab"); } + if (target) { textBox.props.addDocTab(target, "add"); } } } else { - DocumentManager.Instance.FollowLink(FormattedTextBoxComment.linkDoc, textBox.props.Document, doc => textBox.props.addDocTab(doc, "onRight"), false); + DocumentManager.Instance.FollowLink(FormattedTextBoxComment.linkDoc, textBox.props.Document, doc => textBox.props.addDocTab(doc, "add:right"), false); } } } else { if (FormattedTextBoxComment.linkDoc.type !== DocumentType.LINK) { - textBox.props.addDocTab(FormattedTextBoxComment.linkDoc, e.ctrlKey ? "inTab" : "onRight"); + textBox.props.addDocTab(FormattedTextBoxComment.linkDoc, e.ctrlKey ? "add" : "add:right"); } else { DocumentManager.Instance.FollowLink(FormattedTextBoxComment.linkDoc, textBox.props.Document, - (doc: Doc, followLinkLocation: string) => textBox.props.addDocTab(doc, e.ctrlKey ? "inTab" : followLinkLocation)); + (doc: Doc, followLinkLocation: string) => textBox.props.addDocTab(doc, e.ctrlKey ? "add" : followLinkLocation)); } } } } else if (textBox && (FormattedTextBoxComment.tooltipText as any).href) { - textBox.props.addDocTab(Docs.Create.WebDocument((FormattedTextBoxComment.tooltipText as any).href, { title: (FormattedTextBoxComment.tooltipText as any).href, _width: 200, _height: 400, UseCors: true }), "onRight"); + textBox.props.addDocTab(Docs.Create.WebDocument((FormattedTextBoxComment.tooltipText as any).href, { title: (FormattedTextBoxComment.tooltipText as any).href, _width: 200, _height: 400, useCors: true }), "add:right"); } keep && textBox && FormattedTextBoxComment.start !== undefined && textBox.adoptAnnotation( FormattedTextBoxComment.start, FormattedTextBoxComment.end, FormattedTextBoxComment.mark); @@ -232,6 +232,16 @@ export class FormattedTextBoxComment { const mark = child ? findLinkMark(child.marks) : undefined; const href = (!mark?.attrs.docref || naft === nbef) && mark?.attrs.allLinks.find((item: { href: string }) => item.href)?.href || forceUrl; if (forceUrl || (href && child && nbef && naft && mark?.attrs.showPreview)) { + try { + ReactDOM.unmountComponentAtNode(FormattedTextBoxComment.tooltipText); + } catch (e) { } + FormattedTextBoxComment.tooltip.removeChild(FormattedTextBoxComment.tooltipText); + FormattedTextBoxComment.tooltipText = document.createElement("div"); + FormattedTextBoxComment.tooltipText.style.width = "100%"; + FormattedTextBoxComment.tooltipText.style.height = "100%"; + FormattedTextBoxComment.tooltipText.style.textOverflow = "ellipsis"; + FormattedTextBoxComment.tooltip.appendChild(FormattedTextBoxComment.tooltipText); + FormattedTextBoxComment.tooltipText.textContent = "external => " + href; (FormattedTextBoxComment.tooltipText as any).href = href; if (href.startsWith("https://en.wikipedia.org/wiki/")) { @@ -241,12 +251,9 @@ export class FormattedTextBoxComment { FormattedTextBoxComment.tooltipText.style.overflow = "hidden"; } if (href.indexOf(Utils.prepend("/doc/")) === 0) { + const docTarget = href.replace(Utils.prepend("/doc/"), "").split("?")[0]; FormattedTextBoxComment.tooltipText.textContent = "target not found..."; (FormattedTextBoxComment.tooltipText as any).href = ""; - const docTarget = href.replace(Utils.prepend("/doc/"), "").split("?")[0]; - try { - ReactDOM.unmountComponentAtNode(FormattedTextBoxComment.tooltipText); - } catch (e) { } docTarget && DocServer.GetRefField(docTarget).then(async linkDoc => { if (linkDoc instanceof Doc) { (FormattedTextBoxComment.tooltipText as any).href = href; @@ -302,6 +309,7 @@ export class FormattedTextBoxComment { pinToPres={returnFalse} dontRegisterView={true} docFilters={returnEmptyFilter} + searchFilterDocs={returnEmptyDoclist} ContainingCollectionDoc={undefined} ContainingCollectionView={undefined} renderDepth={0} @@ -323,8 +331,8 @@ export class FormattedTextBoxComment { ReactDOM.render(docPreview, FormattedTextBoxComment.tooltipText); - FormattedTextBoxComment.tooltip.style.width = NumCast(target._width) ? `${NumCast(target._width)}` : "100%"; - FormattedTextBoxComment.tooltip.style.height = NumCast(target._height) ? `${NumCast(target._height)}` : "100%"; + FormattedTextBoxComment.tooltip.style.width = "100%"; + FormattedTextBoxComment.tooltip.style.height = "100%"; } } }); diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts index 8faf752b4..c6bacc1a8 100644 --- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts +++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts @@ -33,7 +33,7 @@ export let updateBullets = (tx2: Transaction, schema: Schema, assignedMapStyle?: return tx2; }; -export default function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKeys?: KeyMap): KeyMap { +export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKeys?: KeyMap): KeyMap { const keys: { [key: string]: any } = {}; function bind(key: string, cmd: any) { @@ -104,7 +104,7 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, props: any //Command to create a new Tab with a PDF of all the command shortcuts bind("Mod-/", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => { const newDoc = Docs.Create.PdfDocument(Utils.prepend("/assets/cheat-sheet.pdf"), { _fitWidth: true, _width: 300, _height: 300 }); - props.addDocTab(newDoc, "onRight"); + props.addDocTab(newDoc, "add:right"); }); //Commands to modify BlockType @@ -143,7 +143,7 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, props: any const layoutKey = StrCast(originalDoc.layoutKey); const newDoc = Doc.MakeCopy(originalDoc, true); newDoc[DataSym][Doc.LayoutFieldKey(newDoc)] = undefined; - newDoc.y = NumCast(originalDoc.y) + NumCast(originalDoc._height) + 10; + newDoc.x = NumCast(originalDoc.x) + NumCast(originalDoc._width) + 10; if (layoutKey !== "layout" && originalDoc[layoutKey] instanceof Doc) { newDoc[layoutKey] = originalDoc[layoutKey]; } @@ -168,7 +168,7 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, props: any const layoutKey = StrCast(originalDoc.layoutKey); const newDoc = Doc.MakeCopy(originalDoc, true); newDoc[DataSym][Doc.LayoutFieldKey(newDoc)] = undefined; - newDoc.x = NumCast(originalDoc.x) + NumCast(originalDoc._width) + 10; + newDoc.y = NumCast(originalDoc.y) + NumCast(originalDoc._height) + 10; if (layoutKey !== "layout" && originalDoc[layoutKey] instanceof Doc) { newDoc[layoutKey] = originalDoc[layoutKey]; } diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx index b683fb25d..307238ea1 100644 --- a/src/client/views/nodes/formattedText/RichTextMenu.tsx +++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx @@ -1,8 +1,8 @@ import React = require("react"); -import { IconProp, library } from '@fortawesome/fontawesome-svg-core'; -import { faBold, faCaretDown, faChevronLeft, faEyeDropper, faHighlighter, faOutdent, faIndent, faHandPointLeft, faHandPointRight, faItalic, faLink, faPaintRoller, faPalette, faStrikethrough, faSubscript, faSuperscript, faUnderline } from "@fortawesome/free-solid-svg-icons"; +import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { action, observable, IReactionDisposer, reaction } from "mobx"; +import { Tooltip } from "@material-ui/core"; +import { action, IReactionDisposer, observable, reaction, runInAction } from "mobx"; import { observer } from "mobx-react"; import { lift, wrapIn } from "prosemirror-commands"; import { Mark, MarkType, Node as ProsNode, NodeType, ResolvedPos } from "prosemirror-model"; @@ -11,31 +11,28 @@ import { EditorState, NodeSelection, TextSelection } from "prosemirror-state"; import { EditorView } from "prosemirror-view"; import { Doc } from "../../../../fields/Doc"; import { DarkPastelSchemaPalette, PastelSchemaPalette } from '../../../../fields/SchemaHeaderField'; -import { Cast, StrCast, BoolCast, NumCast } from "../../../../fields/Types"; +import { Cast, StrCast } from "../../../../fields/Types"; +import { TraceMobx } from "../../../../fields/util"; import { unimplementedFunction, Utils } from "../../../../Utils"; import { DocServer } from "../../../DocServer"; import { LinkManager } from "../../../util/LinkManager"; import { SelectionManager } from "../../../util/SelectionManager"; -import AntimodeMenu from "../../AntimodeMenu"; +import { undoBatch, UndoManager } from "../../../util/UndoManager"; +import { AntimodeMenu, AntimodeMenuProps } from "../../AntimodeMenu"; import { FieldViewProps } from "../FieldView"; import { FormattedTextBox, FormattedTextBoxProps } from "./FormattedTextBox"; import { updateBullets } from "./ProsemirrorExampleTransfer"; import "./RichTextMenu.scss"; import { schema } from "./schema_rts"; -import { TraceMobx } from "../../../../fields/util"; -import { UndoManager, undoBatch } from "../../../util/UndoManager"; -import { Tooltip } from "@material-ui/core"; const { toggleMark } = require("prosemirror-commands"); -library.add(faBold, faItalic, faChevronLeft, faUnderline, faStrikethrough, faSuperscript, faSubscript, faOutdent, faIndent, faHandPointLeft, faHandPointRight, faEyeDropper, faCaretDown, faPalette, faHighlighter, faLink, faPaintRoller); - @observer -export default class RichTextMenu extends AntimodeMenu { +export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { static Instance: RichTextMenu; public overMenu: boolean = false; // kind of hacky way to prevent selects not being selectable - private view?: EditorView; + public view?: EditorView; public editorProps: FieldViewProps & FormattedTextBoxProps | undefined; public _brushMap: Map<string, Set<Mark>> = new Map(); @@ -78,7 +75,7 @@ export default class RichTextMenu extends AntimodeMenu { RichTextMenu.Instance = this; this._canFade = false; //this.Pinned = BoolCast(Doc.UserDoc()["menuRichText-pinned"]); - this.Pinned = true; + runInAction(() => this.Pinned = true); this.fontSizeOptions = [ { mark: schema.marks.pFontSize.create({ fontSize: 7 }), title: "Set font size", label: "7pt", command: this.changeFontSize }, @@ -156,22 +153,8 @@ export default class RichTextMenu extends AntimodeMenu { public delayHide = () => this._delayHide = true; @action - changeView(view: EditorView) { - if ((view as any)?.TextView?.props.isSelected(true)) { - this.view = view; - } - } - - update(view: EditorView, lastState: EditorState | undefined) { - RichTextMenu.Instance.updateFromDash(view, lastState, this.editorProps); - } - - @action - public async updateFromDash(view: EditorView, lastState: EditorState | undefined, props: any) { - RichTextMenu.Instance.finalUpdateFromDash(view, lastState, props); - } - public async finalUpdateFromDash(view: EditorView, lastState: EditorState | undefined, props: any) { - if (!view || !(view as any).TextView?.props.isSelected(true)) { + public updateMenu(view: EditorView, lastState: EditorState | undefined, props: any) { + if (!view || !(view as any).TextView?.props.isSelected(true) || !view.hasFocus()) { return; } this.view = view; @@ -199,8 +182,7 @@ export default class RichTextMenu extends AntimodeMenu { this.activeHighlightColor = !activeHighlights.length ? "" : activeHighlights.length === 1 ? String(activeHighlights[0]) : "..."; // update link in current selection - const targetTitle = await this.getTextLinkTargetTitle(); - this.setCurrentLink(targetTitle); + this.getTextLinkTargetTitle().then(targetTitle => this.setCurrentLink(targetTitle)); } setMark = (mark: Mark, state: EditorState<any>, dispatch: any, dontToggle: boolean = false) => { @@ -268,7 +250,9 @@ export default class RichTextMenu extends AntimodeMenu { const pos = this.view.state.selection.$from; const ref_node = this.reference_node(pos); if (ref_node && ref_node !== this.view.state.doc && ref_node.isText) { - ref_node.marks.forEach(m => { + const marks = Array.from(ref_node.marks); + marks.push(...(this.view.state.storedMarks as any)); + marks.forEach(m => { m.type === state.schema.marks.pFontFamily && activeFamilies.push(m.attrs.family); m.type === state.schema.marks.pFontColor && activeColors.push(m.attrs.color); m.type === state.schema.marks.pFontSize && activeSizes.push(String(m.attrs.fontSize) + "pt"); @@ -443,14 +427,20 @@ export default class RichTextMenu extends AntimodeMenu { if ((this.view?.state.selection.$from.pos || 0) < 2) { this.TextView.layoutDoc._fontSize = mark.attrs.fontSize; } - this.setMark(view.state.schema.marks.pFontSize.create({ fontSize: mark.attrs.fontSize }), view.state, view.dispatch, true); + const fmark = view.state.schema.marks.pFontSize.create({ fontSize: mark.attrs.fontSize }); + this.setMark(fmark, view.state, (tx: any) => view.dispatch(tx.addStoredMark(fmark)), true); + view.focus(); + this.updateMenu(view, undefined, this.props); } changeFontFamily = (mark: Mark, view: EditorView) => { if ((this.view?.state.selection.$from.pos || 0) < 2) { this.TextView.layoutDoc._fontFamily = mark.attrs.family; } - this.setMark(view.state.schema.marks.pFontFamily.create({ family: mark.attrs.family }), view.state, view.dispatch, true); + const fmark = view.state.schema.marks.pFontFamily.create({ family: mark.attrs.family }); + this.setMark(fmark, view.state, (tx: any) => view.dispatch(tx.addStoredMark(fmark)), true); + view.focus(); + this.updateMenu(view, undefined, this.props); } // TODO: remove doesn't work @@ -486,6 +476,8 @@ export default class RichTextMenu extends AntimodeMenu { this.view.dispatch(tx3); } } + this.view.focus(); + this.updateMenu(this.view, undefined, this.props); } insertSummarizer(state: EditorState<any>, dispatch: any) { @@ -690,16 +682,22 @@ export default class RichTextMenu extends AntimodeMenu { e.preventDefault(); e.stopPropagation(); self.TextView.endUndoTypingBatch(); - UndoManager.RunInBatch(() => self.view && self.insertColor(self.activeFontColor, self.view.state, self.view.dispatch), "rt menu color"); - self.TextView.EditorView!.focus(); + if (self.view) { + UndoManager.RunInBatch(() => self.view && self.insertColor(self.activeFontColor, self.view.state, self.view.dispatch), "rt menu color"); + self.view.focus(); + self.updateMenu(self.view, undefined, self.props); + } } function changeColor(e: React.PointerEvent, color: string) { e.preventDefault(); e.stopPropagation(); self.setActiveColor(color); self.TextView.endUndoTypingBatch(); - UndoManager.RunInBatch(() => self.view && self.insertColor(self.activeFontColor, self.view.state, self.view.dispatch), "rt menu color"); - self.TextView.EditorView!.focus(); + if (self.view) { + UndoManager.RunInBatch(() => self.view && self.insertColor(self.activeFontColor, self.view.state, self.view.dispatch), "rt menu color"); + self.view.focus(); + self.updateMenu(self.view, undefined, self.props); + } } // onPointerDown={onColorClick} @@ -812,7 +810,7 @@ export default class RichTextMenu extends AntimodeMenu { <div className="dropdown link-menu"> <p>Linked to:</p> <input value={link} placeholder="Enter URL" onChange={onLinkChange} /> - <button className="make-button" onPointerDown={e => this.makeLinkToURL(link, "onRight")}>Apply hyperlink</button> + <button className="make-button" onPointerDown={e => this.makeLinkToURL(link, "add:right")}>Apply hyperlink</button> <div className="divider"></div> <button className="remove-button" onPointerDown={e => this.deleteLink()}>Remove link</button> </div>; @@ -859,7 +857,7 @@ export default class RichTextMenu extends AntimodeMenu { // TODO: should check for valid URL @undoBatch makeLinkToURL = (target: string, lcoation: string) => { - ((this.view as any)?.TextView as FormattedTextBox).makeLinkToSelection("", target, "onRight", "", target); + ((this.view as any)?.TextView as FormattedTextBox).makeLinkToSelection("", target, "onRadd:rightight", "", target); } @undoBatch @@ -988,7 +986,7 @@ export default class RichTextMenu extends AntimodeMenu { {[this.createMarksDropdown(this.activeFontSize, this.fontSizeOptions, "font size", action((val: string) => this.activeFontSize = val)), this.createMarksDropdown(this.activeFontFamily, this.fontFamilyOptions, "font family", action((val: string) => this.activeFontFamily = val)), <div className="richTextMenu-divider" key="divider 4" />, - this.createNodesDropdown(this.activeListType, this.listTypeOptions, "list type", action((val: string) => this.activeListType = val)), + this.createNodesDropdown(this.activeListType, this.listTypeOptions, "list type", () => ({})), this.createButton("sort-amount-down", "Summarize", undefined, this.insertSummarizer), this.createButton("quote-left", "Blockquote", undefined, this.insertBlockquote), this.createButton("minus", "Horizontal Rule", undefined, this.insertHorizontalRule), @@ -1020,6 +1018,7 @@ interface ButtonDropdownProps { dropdownContent: JSX.Element; openDropdownOnButton?: boolean; link?: boolean; + pdf?: boolean; } @observer @@ -1059,15 +1058,33 @@ export class ButtonDropdown extends React.Component<ButtonDropdownProps> { }, 0); } + render() { return ( <div className="button-dropdown-wrapper" ref={node => this.ref = node}> - <div className="antimodeMenu-button dropdown-button-combined" onPointerDown={this.onDropdownClick}> - {this.props.button} - <div style={{ marginTop: "-8.5" }}><FontAwesomeIcon icon="caret-down" size="sm" /></div> - </div> + {!this.props.pdf ? + <div className="antimodeMenu-button dropdown-button-combined" onPointerDown={this.onDropdownClick}> + {this.props.button} + <div style={{ marginTop: "-8.5" }}><FontAwesomeIcon icon="caret-down" size="sm" /></div> + </div> + : + <> + {this.props.button} + <button className="dropdown-button antimodeMenu-button" key="antimodebutton" onPointerDown={this.onDropdownClick}> + <FontAwesomeIcon icon="caret-down" size="sm" /> + </button> + </>} {this.showDropdown ? this.props.dropdownContent : (null)} </div> ); } +} + + +interface RichTextMenuPluginProps { + editorProps: any; +} +export class RichTextMenuPlugin extends React.Component<RichTextMenuPluginProps> { + render() { return null; } + update(view: EditorView, lastState: EditorState | undefined) { RichTextMenu.Instance?.updateMenu(view, lastState, this.props.editorProps); } }
\ No newline at end of file diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts index dc1d8a2c8..5c0505909 100644 --- a/src/client/views/nodes/formattedText/RichTextRules.ts +++ b/src/client/views/nodes/formattedText/RichTextRules.ts @@ -3,13 +3,13 @@ import { NodeSelection, TextSelection } from "prosemirror-state"; import { DataSym, Doc } from "../../../../fields/Doc"; import { Id } from "../../../../fields/FieldSymbols"; import { ComputedField } from "../../../../fields/ScriptField"; -import { Cast, NumCast } from "../../../../fields/Types"; +import { Cast, NumCast, StrCast } from "../../../../fields/Types"; import { returnFalse, Utils } from "../../../../Utils"; import { DocServer } from "../../../DocServer"; import { Docs, DocUtils } from "../../../documents/Documents"; import { FormattedTextBox } from "./FormattedTextBox"; import { wrappingInputRule } from "./prosemirrorPatches"; -import RichTextMenu from "./RichTextMenu"; +import { RichTextMenu } from "./RichTextMenu"; import { schema } from "./schema_rts"; import { List } from "../../../../fields/List"; @@ -92,7 +92,7 @@ export class RichTextRules { const inlineLayoutKey = "layout_" + inlineFieldKey; // the field holding the layout string that will render the inline annotation const textDocInline = Docs.Create.TextDocument("", { layoutKey: inlineLayoutKey, _width: 75, _height: 35, annotationOn: textDoc, _autoHeight: true, _fontSize: "9pt", title: "inline comment" }); textDocInline.title = inlineFieldKey; // give the annotation its own title - textDocInline.customTitle = true; // And make sure that it's 'custom' so that editing text doesn't change the title of the containing doc + textDocInline["title-custom"] = true; // And make sure that it's 'custom' so that editing text doesn't change the title of the containing doc textDocInline.isTemplateForField = inlineFieldKey; // this is needed in case the containing text doc is converted to a template at some point textDocInline.proto = textDoc; // make the annotation inherit from the outer text doc so that it can resolve any nested field references, e.g., [[field]] textDocInline._textContext = ComputedField.MakeFunction(`copyField(self.${inlineFieldKey})`); @@ -279,7 +279,7 @@ export class RichTextRules { DocUtils.Publish(target, docid, returnFalse, returnFalse); DocUtils.MakeLink({ doc: this.Document }, { doc: target }, "portal to"); }); - const link = state.schema.marks.linkAnchor.create({ href: Utils.prepend("/doc/" + docid), location: "onRight", title: docid, targetId: docid }); + const link = state.schema.marks.linkAnchor.create({ href: Utils.prepend("/doc/" + docid), location: "add:right", title: docid, targetId: docid }); return state.tr.deleteRange(end - 1, end).deleteRange(start, start + 2).addMark(start, end - 3, link); } return state.tr; @@ -321,7 +321,11 @@ export class RichTextRules { (state, match, start, end) => { const tag = match[1]; if (!tag) return state.tr; - this.Document[DataSym]["#" + tag] = "."; + this.Document[DataSym]["#" + tag] = "#" + tag; + const tags = StrCast(this.Document.tags, ":"); + if (!tags.includes(`#${tag}:`)) { + this.Document[DataSym].tags = `"${tags + "#" + tag + ':'}"`; + } const fieldView = state.schema.nodes.dashField.create({ fieldKey: "#" + tag }); return state.tr.deleteRange(start, end).insert(start, fieldView); }), diff --git a/src/client/views/nodes/formattedText/RichTextSchema.tsx b/src/client/views/nodes/formattedText/RichTextSchema.tsx index 33a080fe4..f0bacb735 100644 --- a/src/client/views/nodes/formattedText/RichTextSchema.tsx +++ b/src/client/views/nodes/formattedText/RichTextSchema.tsx @@ -13,6 +13,7 @@ import { Transform } from "../../../util/Transform"; import { DocumentView } from "../DocumentView"; import { FormattedTextBox } from "./FormattedTextBox"; import React = require("react"); +import { CurrentUserUtils } from "../../../util/CurrentUserUtils"; export class DashDocView { @@ -43,7 +44,7 @@ export class DashDocView { this._outer = document.createElement("span"); this._outer.style.position = "relative"; this._outer.style.textIndent = "0"; - this._outer.style.border = "1px solid " + StrCast(tbox.layoutDoc.color, (Cast(Doc.UserDoc().activeWorkspace, Doc, null).darkScheme ? "dimGray" : "lightGray")); + this._outer.style.border = "1px solid " + StrCast(tbox.layoutDoc.color, (CurrentUserUtils.ActiveDashboard.darkScheme ? "dimGray" : "lightGray")); this._outer.style.width = node.attrs.width; this._outer.style.height = node.attrs.height; this._outer.style.display = node.attrs.hidden ? "none" : "inline-block"; @@ -126,7 +127,7 @@ export class DashDocView { this._reactionDisposer = reaction(() => ({ dim: [finalLayout[WidthSym](), finalLayout[HeightSym]()], color: finalLayout.color }), ({ dim, color }) => { this._dashSpan.style.width = this._outer.style.width = Math.max(20, dim[0]) + "px"; this._dashSpan.style.height = this._outer.style.height = Math.max(20, dim[1]) + "px"; - this._outer.style.border = "1px solid " + StrCast(finalLayout.color, (Cast(Doc.UserDoc().activeWorkspace, Doc, null).darkScheme ? "dimGray" : "lightGray")); + this._outer.style.border = "1px solid " + StrCast(finalLayout.color, (CurrentUserUtils.ActiveDashboard.darkScheme ? "dimGray" : "lightGray")); }, { fireImmediately: true }); const doReactRender = (finalLayout: Doc, resolvedDataDoc: Doc) => { @@ -155,6 +156,7 @@ export class DashDocView { bringToFront={emptyFunction} dontRegisterView={false} docFilters={this._textBox.props.docFilters} + searchFilterDocs={this._textBox.props.searchFilterDocs} ContainingCollectionView={this._textBox.props.ContainingCollectionView} ContainingCollectionDoc={this._textBox.props.ContainingCollectionDoc} ContentScaling={this.contentScaling} diff --git a/src/client/views/nodes/formattedText/nodes_rts.ts b/src/client/views/nodes/formattedText/nodes_rts.ts index 1616500f6..64f7d27e5 100644 --- a/src/client/views/nodes/formattedText/nodes_rts.ts +++ b/src/client/views/nodes/formattedText/nodes_rts.ts @@ -148,7 +148,7 @@ export const nodes: { [index: string]: NodeSpec } = { alt: { default: null }, title: { default: null }, float: { default: "left" }, - location: { default: "onRight" }, + location: { default: "add:right" }, docid: { default: "" } }, group: "inline", @@ -177,7 +177,7 @@ export const nodes: { [index: string]: NodeSpec } = { height: { default: 100 }, title: { default: null }, float: { default: "right" }, - location: { default: "onRight" }, + location: { default: "add:right" }, hidden: { default: false }, fieldKey: { default: "" }, docid: { default: "" }, |
