diff options
Diffstat (limited to 'src/client/views')
47 files changed, 640 insertions, 653 deletions
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx index ef1228976..128ba858f 100644 --- a/src/client/views/DocComponent.tsx +++ b/src/client/views/DocComponent.tsx @@ -11,7 +11,7 @@ import { GetEffectiveAcl, SharingPermissions, distributeAcls, denormalizeEmail } /// DocComponent returns a generic React base class used by views that don't have 'fieldKey' props (e.g.,CollectionFreeFormDocumentView, DocumentView) -interface DocComponentProps { +export interface DocComponentProps { Document: Doc; LayoutTemplate?: () => Opt<Doc>; LayoutTemplateString?: string; diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index 209e750d6..a9cd7b4a9 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -312,7 +312,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV return false; } - _ref = React.createRef<TemplateMenu>(); + _ref = React.createRef<HTMLDivElement>(); @observable _tooltipOpen: boolean = false; @computed get templateButton() { @@ -321,11 +321,11 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV return !view0 ? (null) : <Tooltip title={<div className="dash-tooltip">Tap to Customize Layout. Drag an embeddable alias</div>} open={this._tooltipOpen} onClose={action(() => this._tooltipOpen = false)} placement="bottom"> <div className="documentButtonBar-linkFlyout" ref={this._dragRef} - onPointerEnter={action(() => !(this._ref.current as any as HTMLElement)?.getBoundingClientRect().width && (this._tooltipOpen = true))} > + onPointerEnter={action(() => !this._ref.current?.getBoundingClientRect().width && (this._tooltipOpen = true))} > <Flyout anchorPoint={anchorPoints.LEFT_TOP} onOpen={action(() => this._aliasDown = true)} onClose={action(() => this._aliasDown = false)} content={!this._aliasDown ? (null) : - <TemplateMenu ref={this._ref} docViews={views.filter(v => v).map(v => v as DocumentView)} />}> + <div ref={this._ref}> <TemplateMenu docViews={views.filter(v => v).map(v => v as DocumentView)} /></div>}> <div className={"documentButtonBar-linkButton-empty"} ref={this._dragRef} onPointerDown={this.onAliasButtonDown} > {<FontAwesomeIcon className="documentdecorations-icon" icon="edit" size="sm" />} </div> diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 2391a21e6..1e3c0ba92 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -292,10 +292,7 @@ export class DocumentDecorations extends React.Component<{ boundsLeft: number, b this._rotateUndo = undefined; } - - - _initialAutoHeight = false; - _dragHeights = new Map<Doc, number>(); + _dragHeights = new Map<Doc, { start: number, lowest: number }>(); @action onPointerDown = (e: React.PointerEvent): void => { @@ -323,12 +320,9 @@ export class DocumentDecorations extends React.Component<{ boundsLeft: number, b } this._snapX = e.pageX; this._snapY = e.pageY; - this._initialAutoHeight = true; DragManager.docsBeingDragged = SelectionManager.Views().map(dv => dv.rootDoc); - SelectionManager.Views().map(dv => { - this._dragHeights.set(dv.layoutDoc, NumCast(dv.layoutDoc._height)); - dv.layoutDoc._delayAutoHeight = dv.layoutDoc._height; - }); + SelectionManager.Views().map(dv => + this._dragHeights.set(dv.layoutDoc, { start: NumCast(dv.layoutDoc._height), lowest: NumCast(dv.layoutDoc._height) })); } onPointerMove = (e: PointerEvent, down: number[], move: number[]): boolean => { @@ -369,8 +363,7 @@ export class DocumentDecorations extends React.Component<{ boundsLeft: number, b let dX = 0, dY = 0, dW = 0, dH = 0; const unfreeze = () => SelectionManager.Views().forEach(action((element: DocumentView) => - ((element.rootDoc.type === DocumentType.RTF || - element.rootDoc.type === DocumentType.COMPARISON || + ((element.rootDoc.type === DocumentType.COMPARISON || (element.rootDoc.type === DocumentType.WEB && Doc.LayoutField(element.rootDoc) instanceof HtmlField)) && Doc.NativeHeight(element.layoutDoc)) && element.toggleNativeDimensions())); switch (this._resizeHdlId) { @@ -421,8 +414,8 @@ export class DocumentDecorations extends React.Component<{ boundsLeft: number, b if (e.ctrlKey && !Doc.NativeHeight(docView.props.Document)) docView.toggleNativeDimensions(); if (dX !== 0 || dY !== 0 || dW !== 0 || dH !== 0) { const doc = Document(docView.rootDoc); - let nwidth = docView.nativeWidth; - let nheight = docView.nativeHeight; + const nwidth = docView.nativeWidth; + const nheight = docView.nativeHeight; const width = (doc._width || 0); let height = (doc._height || (nheight / nwidth * width)); height = !height || isNaN(height) ? 20 : height; @@ -431,7 +424,7 @@ export class DocumentDecorations extends React.Component<{ boundsLeft: number, b if (nwidth / nheight !== width / height && !dragBottom) { height = nheight / nwidth * width; } - if (e.ctrlKey || (!dragBottom || !docView.layoutDoc._fitWidth)) { // ctrl key enables modification of the nativeWidth or nativeHeight durin the interaction + if (e.ctrlKey && !dragBottom) { // ctrl key enables modification of the nativeWidth or nativeHeight durin the interaction if (Math.abs(dW) > Math.abs(dH)) dH = dW * nheight / nwidth; else dW = dH * nwidth / nheight; } @@ -441,10 +434,6 @@ export class DocumentDecorations extends React.Component<{ boundsLeft: number, b doc.x = (doc.x || 0) + dX * (actualdW - width); doc.y = (doc.y || 0) + dY * (actualdH - height); const fixedAspect = (nwidth && nheight); - if (fixedAspect && (!nwidth || !nheight)) { - doc._nativeWidth = nwidth = doc._width || 0; - doc._nativeHeight = nheight = doc._height || 0; - } if (e.ctrlKey && [DocumentType.IMG, DocumentType.SCREENSHOT, DocumentType.VID].includes(doc.type as DocumentType)) { dW !== 0 && runInAction(() => { const dataDoc = doc[DataSym]; @@ -454,29 +443,33 @@ export class DocumentDecorations extends React.Component<{ boundsLeft: number, b Doc.SetNativeHeight(dataDoc, nh + (dW > 0 ? 10 : -10) * nh / nw); }); } - else if (nwidth > 0 && nheight > 0) { - if (Math.abs(dW) > Math.abs(dH) || dragRight) { - if (!fixedAspect || (dragRight && e.ctrlKey)) { + else if (fixedAspect) { + if ((Math.abs(dW) > Math.abs(dH) && (!dragBottom || !e.ctrlKey)) || dragRight) { + if (dragRight && e.ctrlKey) { doc._nativeWidth = actualdW / (doc._width || 1) * Doc.NativeWidth(doc); + } else { + if (!doc._fitWidth) doc._height = nheight / nwidth * actualdW; + else if (!e.ctrlKey) doc._height = actualdH; } doc._width = actualdW; - if (fixedAspect && !doc._fitWidth) doc._height = nheight / nwidth * doc._width; - else if (!fixedAspect || !e.ctrlKey) doc._height = actualdH; } else { - if (!fixedAspect || (dragBottom && (e.ctrlKey || docView.layoutDoc._fitWidth))) { + if (dragBottom && (e.ctrlKey || docView.layoutDoc._fitWidth)) { // frozen web pages and others that fitWidth can't grow horizontally to match a vertical resize so the only choice is to change the nativeheight even if the ctrl key isn't used doc._nativeHeight = actualdH / (doc._height || 1) * Doc.NativeHeight(doc); + } else { + if (!doc._fitWidth) doc._width = nwidth / nheight * actualdH; + else if (!e.ctrlKey) doc._width = actualdW; } doc._height = actualdH; - if (fixedAspect && !doc._fitWidth) doc._width = nwidth / nheight * doc._height; - else if (!fixedAspect || !e.ctrlKey) doc._width = actualdW; } } else { dW && (doc._width = actualdW); dH && (doc._height = actualdH); - dH && this._initialAutoHeight && (doc._autoHeight = this._initialAutoHeight = false); + dH && (doc._autoHeight = false); } } + const val = this._dragHeights.get(docView.layoutDoc); + if (val) this._dragHeights.set(docView.layoutDoc, { start: val.start, lowest: Math.min(val.lowest, NumCast(docView.layoutDoc._height)) }); })); return false; } @@ -484,11 +477,12 @@ export class DocumentDecorations extends React.Component<{ boundsLeft: number, b @action onPointerUp = (e: PointerEvent): void => { SelectionManager.Views().map(dv => { - if (NumCast(dv.layoutDoc._delayAutoHeight) < this._dragHeights.get(dv.layoutDoc)!) { - dv.nativeWidth > 0 && Doc.toggleNativeDimensions(dv.layoutDoc, dv.ContentScale(), dv.props.PanelWidth(), dv.props.PanelHeight()); - dv.layoutDoc._autoHeight = true; + const hgts = this._dragHeights.get(dv.layoutDoc); + if (hgts && hgts.lowest < hgts.start && hgts.lowest <= 20) { + dv.effectiveNativeWidth > 0 && Doc.toggleNativeDimensions(dv.layoutDoc, dv.ContentScale(), dv.props.PanelWidth(), dv.props.PanelHeight()); + if (dv.layoutDoc._autoHeight) dv.layoutDoc._autoHeight = false; + setTimeout(() => dv.layoutDoc._autoHeight = true); } - dv.layoutDoc._delayAutoHeight = undefined; }); this._resizeHdlId = ""; this.Interacting = false; @@ -617,8 +611,8 @@ export class DocumentDecorations extends React.Component<{ boundsLeft: number, b top: bounds.y - this._resizeBorderWidth / 2 - this._titleHeight, }}> {closeIcon} - {titleArea} - {seldoc.props.hideResizeHandles ? (null) : + {seldoc.props.Document.type === DocumentType.EQUATION ? (null) : titleArea} + {seldoc.props.hideResizeHandles || seldoc.props.Document.type === DocumentType.EQUATION ? (null) : <> {SelectionManager.Views().length !== 1 || seldoc.Document.type === DocumentType.INK ? (null) : <Tooltip key="i" title={<div className="dash-tooltip">{`${seldoc.finalLayoutKey.includes("icon") ? "De" : ""}Iconify Document`}</div>} placement="top"> diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index 19b23af13..462d78865 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -233,7 +233,7 @@ export function SetActiveFillColor(value: string) { ActiveInkPen() && (ActiveInk export function SetActiveArrowStart(value: string) { ActiveInkPen() && (ActiveInkPen().activeArrowStart = value); } export function SetActiveArrowEnd(value: string) { ActiveInkPen() && (ActiveInkPen().activeArrowEnd = value); } export function SetActiveDash(dash: string): void { !isNaN(parseInt(dash)) && ActiveInkPen() && (ActiveInkPen().activeDash = dash); } -export function ActiveInkPen(): Doc { return Cast(Doc.UserDoc().activeInkPen, Doc, null); } +export function ActiveInkPen(): Doc { return Doc.UserDoc(); } export function ActiveInkColor(): string { return StrCast(ActiveInkPen()?.activeInkColor, "black"); } export function ActiveFillColor(): string { return StrCast(ActiveInkPen()?.activeFillColor, ""); } export function ActiveArrowStart(): string { return StrCast(ActiveInkPen()?.activeArrowStart, ""); } diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx index babc518ff..3fc72c45c 100644 --- a/src/client/views/LightboxView.tsx +++ b/src/client/views/LightboxView.tsx @@ -46,7 +46,7 @@ export class LightboxView extends React.Component<LightboxViewProps> { this._docFilters && (this._docFilters.length = 0); this._future = this._history = []; } else { - TabDocView.PinDoc(doc, { hidePresBox: true }); + //TabDocView.PinDoc(doc, { hidePresBox: true }); this._history ? this._history.push({ doc, target }) : this._history = [{ doc, target }]; if (doc !== LightboxView.LightboxDoc) { this._savedState = { @@ -247,6 +247,7 @@ export class LightboxView extends React.Component<LightboxViewProps> { ContainingCollectionDoc={undefined} renderDepth={0} /> </div> + {this.navBtn(0, undefined, this.props.PanelHeight / 2 - 12.50, "chevron-left", () => LightboxView.LightboxDoc && LightboxView._history?.length ? "" : "none", e => { e.stopPropagation(); @@ -258,6 +259,10 @@ export class LightboxView extends React.Component<LightboxViewProps> { LightboxView.Next(); })} <LightboxTourBtn navBtn={this.navBtn} future={this.future} stepInto={this.stepInto} tourMap={this.tourMap} /> + <div className="lightboxView-navBtn" title={"toggle fit width"} style={{ position: "absolute", right: 10, top: 10, color: "white" }} + onClick={e => { e.stopPropagation(); LightboxView.LightboxDoc!._fitWidth = !LightboxView.LightboxDoc!._fitWidth }}> + <FontAwesomeIcon icon={"arrows-alt-h"} size="2x" /> + </div> </div>; } } @@ -276,6 +281,6 @@ export class LightboxTourBtn extends React.Component<LightboxTourBtnProps> { this.props.stepInto(); }, StrCast(this.props.tourMap()?.lastElement()?.TourMap) - ) + ); } }
\ No newline at end of file diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 6010eb518..c356aa0f5 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -102,7 +102,7 @@ export class MainView extends React.Component { } new InkStrokeProperties(); this._sidebarContent.proto = undefined; - DocServer.setPlaygroundFields(["x", "y", "dataTransition", "_delayAutoHeight", "_autoHeight", "_showSidebar", "_sidebarWidthPercent", "_width", "_height", "_viewTransition", "_panX", "_panY", "_viewScale", "_scrollTop", "hidden", "_curPage", "_viewType", "_chromeStatus"]); // can play with these fields on someone else's + DocServer.setPlaygroundFields(["x", "y", "dataTransition", "_autoHeight", "_showSidebar", "_sidebarWidthPercent", "_width", "_height", "_viewTransition", "_panX", "_panY", "_viewScale", "_scrollTop", "hidden", "_curPage", "_viewType", "_chromeStatus"]); // can play with these fields on someone else's DocServer.GetRefField("rtfProto").then(proto => (proto instanceof Doc) && reaction(() => StrCast(proto.BROADCAST_MESSAGE), msg => msg && alert(msg))); @@ -230,7 +230,7 @@ export class MainView extends React.Component { createNewPresentation = async () => { if (!await this.userDoc.myPresentations) { this.userDoc.myPresentations = new PrefetchProxy(Docs.Create.TreeDocument([], { - title: "PRESENTATION TRAILS", _height: 100, forceActive: true, boxShadow: "0 0", lockedPosition: true, treeViewOpen: true, system: true + title: "PRESENTATION TRAILS", _height: 100, _forceActive: true, boxShadow: "0 0", _lockedPosition: true, treeViewOpen: true, system: true })); } const pres = Docs.Create.PresDocument(new List<Doc>(), @@ -321,8 +321,8 @@ export class MainView extends React.Component { <div className={`styleProvider-treeView-hide${doc.hidden ? "-active" : ""}`} onClick={e => toggleField(e, doc, "hidden")}> <FontAwesomeIcon icon={doc.hidden ? "eye-slash" : "eye"} size="sm" /> </div> - <div className={`styleProvider-treeView-lock${doc.lockedPosition ? "-active" : ""}`} onClick={e => toggleField(e, doc, "lockedPosition")}> - <FontAwesomeIcon icon={doc.lockedPosition ? "lock" : "unlock"} size="sm" /> + <div className={`styleProvider-treeView-lock${doc._lockedPosition ? "-active" : ""}`} onClick={e => toggleField(e, doc, "_lockedPosition")}> + <FontAwesomeIcon icon={doc._lockedPosition ? "lock" : "unlock"} size="sm" /> </div> </>; } @@ -484,6 +484,7 @@ export class MainView extends React.Component { DataDoc={undefined} fieldKey={"data"} dropAction={"alias"} + setHeight={returnFalse} parentActive={returnFalse} styleProvider={DefaultStyleProvider} layerProvider={undefined} @@ -554,6 +555,7 @@ export class MainView extends React.Component { isSelected={returnTrue} active={returnTrue} select={returnTrue} + setHeight={returnFalse} addDocument={undefined} addDocTab={this.addDocTabFunc} pinToPres={emptyFunction} @@ -591,6 +593,7 @@ export class MainView extends React.Component { styleProvider={undefined} isSelected={returnFalse} select={returnFalse} + setHeight={returnFalse} rootSelected={returnFalse} renderDepth={0} parentActive={returnFalse} @@ -663,6 +666,7 @@ export class MainView extends React.Component { select={returnFalse} rootSelected={returnFalse} renderDepth={0} + setHeight={returnFalse} layerProvider={undefined} styleProvider={undefined} addDocTab={returnFalse} diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx index 6e8f9a2df..f03e1dd9d 100644 --- a/src/client/views/MarqueeAnnotator.tsx +++ b/src/client/views/MarqueeAnnotator.tsx @@ -64,27 +64,24 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> { * This function is used by the AnchorMenu to create an anchor highlight and a new linked text annotation. * It also initiates a Drag/Drop interaction to place the text annotation. */ - AnchorMenu.Instance.StartDrag = action(async (e: PointerEvent, ele: HTMLElement) => { + AnchorMenu.Instance.StartDrag = action((e: PointerEvent, ele: HTMLElement) => { e.preventDefault(); e.stopPropagation(); - const targetCreator = () => { - const target = CurrentUserUtils.GetNewTextDoc("Note linked to " + this.props.rootDoc.title, 0, 0, 100, 100); - FormattedTextBox.SelectOnLoad = target[Id]; - return target; - }; - const anchorCreator = () => { - const annoDoc = this.highlight("rgba(173, 216, 230, 0.75)"); // hyperlink color - annoDoc.isLinkButton = true; // prevents link button from showing up --- maybe not a good thing? + const sourceAnchorCreator = () => { + const annoDoc = this.highlight("rgba(173, 216, 230, 0.75)", true); // hyperlink color this.props.addDocument(annoDoc); return annoDoc; }; - DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(this.props.rootDoc, anchorCreator, targetCreator), e.pageX, e.pageY, { + const targetCreator = (annotationOn: Doc | undefined) => { + const target = CurrentUserUtils.GetNewTextDoc("Note linked to " + this.props.rootDoc.title, 0, 0, 100, 100, undefined, annotationOn); + FormattedTextBox.SelectOnLoad = target[Id]; + return target; + }; + DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(this.props.rootDoc, sourceAnchorCreator, targetCreator), e.pageX, e.pageY, { dragComplete: e => { - if (!e.aborted && e.annoDragData && e.annoDragData.annotationDocument && e.annoDragData.dropDocument && !e.linkDocument) { - e.linkDocument = DocUtils.MakeLink({ doc: e.annoDragData.annotationDocument }, { doc: e.annoDragData.dropDocument }, "Annotation"); - e.annoDragData.annotationDocument.isPushpin = e.annoDragData?.dropDocument.annotationOn === this.props.rootDoc; + if (!e.aborted && e.annoDragData && e.annoDragData.linkSourceDoc && e.annoDragData.dropDocument && e.linkDocument) { + e.annoDragData.linkSourceDoc.isPushpin = e.annoDragData.dropDocument.annotationOn === this.props.rootDoc; } - e.linkDocument && e.annoDragData?.linkDropCallback?.(e as { linkDocument: Doc });// bcz: typescript can't figure out that this is valid even though we tested e.linkDocument } }); }); @@ -141,11 +138,12 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> { } } @action - highlight = (color: string) => { + highlight = (color: string, isLinkButton: boolean) => { // creates annotation documents for current highlights const effectiveAcl = GetEffectiveAcl(this.props.rootDoc[DataSym]); const annotationDoc = [AclAddonly, AclEdit, AclAdmin].includes(effectiveAcl) && this.makeAnnotationDocument(color); annotationDoc && this.props.addDocument(annotationDoc); + annotationDoc && (annotationDoc.isLinkButton = isLinkButton); return annotationDoc as Doc ?? undefined; } @@ -197,7 +195,7 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> { AnchorMenu.Instance.jumpTo(e.clientX, e.clientY); if (AnchorMenu.Instance.Highlighting) {// when highlighter has been toggled when menu is pinned, we auto-highlight immediately on mouse up - this.highlight("rgba(245, 230, 95, 0.75)"); // yellowish highlight color for highlighted text (should match AnchorMenu's highlight color) + this.highlight("rgba(245, 230, 95, 0.75)", false); // yellowish highlight color for highlighted text (should match AnchorMenu's highlight color) } } else { runInAction(() => this._width = this._height = 0); diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx index 5b57ad19f..679a4b81e 100644 --- a/src/client/views/PreviewCursor.tsx +++ b/src/client/views/PreviewCursor.tsx @@ -79,7 +79,7 @@ export class PreviewCursor extends React.Component<{}> { else { // creates text document FormattedTextBox.PasteOnLoad = e; - UndoManager.RunInBatch(() => PreviewCursor._addLiveTextDoc(CurrentUserUtils.GetNewTextDoc("-pasted text-", newPoint[0], newPoint[1], 500)), "paste"); + UndoManager.RunInBatch(() => PreviewCursor._addLiveTextDoc(CurrentUserUtils.GetNewTextDoc("-pasted text-", newPoint[0], newPoint[1], 500, undefined, undefined, undefined, 750)), "paste"); } } else //pasting in images diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx index 8ad5f3f2b..e418a6f3c 100644 --- a/src/client/views/PropertiesButtons.tsx +++ b/src/client/views/PropertiesButtons.tsx @@ -194,12 +194,12 @@ export class PropertiesButtons extends React.Component<{}, {}> { get lockButton() { const targetDoc = this.selectedDoc; return !targetDoc ? (null) : <Tooltip - title={<div className="dash-tooltip">{`${this.selectedDoc?.lockedPosition ? "Unlock" : "Lock"} Position`}</div>} placement="top"> + title={<div className="dash-tooltip">{`${this.selectedDoc?._lockedPosition ? "Unlock" : "Lock"} Position`}</div>} placement="top"> <div> - <div className={`propertiesButtons-linkButton-empty toggle-${targetDoc.lockedPosition ? "on" : "off"}`} onPointerDown={this.onLock} > + <div className={`propertiesButtons-linkButton-empty toggle-${targetDoc._lockedPosition ? "on" : "off"}`} onPointerDown={this.onLock} > <FontAwesomeIcon className="documentdecorations-icon" size="lg" - color={this.selectedDoc?.lockedPosition ? "black" : "white"} - icon={this.selectedDoc?.lockedPosition ? "unlock" : "lock"} /> + color={this.selectedDoc?._lockedPosition ? "black" : "white"} + icon={this.selectedDoc?._lockedPosition ? "unlock" : "lock"} /> </div> <div className="propertiesButtons-title" >Position </div> @@ -260,7 +260,10 @@ export class PropertiesButtons extends React.Component<{}, {}> { @undoBatch @action setCaption = () => { - SelectionManager.Views().forEach(dv => dv.rootDoc._showCaption = dv.rootDoc._showCaption === undefined ? "caption" : undefined); + SelectionManager.Views().forEach(dv => { + dv.rootDoc._showCaption = dv.rootDoc._showCaption === undefined ? "caption" : undefined; + console.log("caption = " + dv.rootDoc._showCaption); + }); } @computed @@ -427,6 +430,14 @@ export class PropertiesButtons extends React.Component<{}, {}> { } @action @undoBatch + changeFitWidth = () => { + if (this.selectedDoc) { + if (SelectionManager.Views().length) SelectionManager.Views().forEach(dv => dv.rootDoc._fitWidth = !dv.rootDoc._fitWidth); + else this.selectedDoc._fitWidth = !this.selectedDoc._fitWidth; + } + } + + @action @undoBatch changeClusters = () => { if (this.selectedDoc) { if (SelectionManager.Views().length) SelectionManager.Views().forEach(dv => dv.rootDoc._useClusters = !dv.rootDoc._useClusters); @@ -448,6 +459,20 @@ export class PropertiesButtons extends React.Component<{}, {}> { </Tooltip>; } + @computed + get fitWidthButton() { + const targetDoc = this.selectedDoc; + return !targetDoc ? (null) : <Tooltip + title={<><div className="dash-tooltip">{this.selectedDoc?._fitWidth ? "Stop Fitting Width" : "Fit Width"}</div></>} placement="top"> + <div> + <div className={`propertiesButtons-linkButton-empty toggle-${targetDoc._fitWidth ? "on" : "off"}`} onPointerDown={this.changeFitWidth}> + <FontAwesomeIcon className="documentdecorations-icon" icon="arrows-alt-h" size="lg" /> + </div> + <div className="propertiesButtons-title"> {this.selectedDoc?._fitWidth ? "unfit" : "fit"} </div> + </div> + </Tooltip>; + } + @undoBatch @action private makeMask = () => { @@ -521,6 +546,9 @@ export class PropertiesButtons extends React.Component<{}, {}> { <div className="propertiesButtons-button" style={{ display: !isFreeForm && !isText ? "none" : "" }}> {this.fitContentButton} </div> + <div className="propertiesButtons-button"> + {this.fitWidthButton} + </div> <div className="propertiesButtons-button" style={{ display: !isInk ? "none" : "" }}> {this.maskButton} </div> diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index e272fbaa6..91667fd64 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -47,9 +47,9 @@ function darkScheme() { return BoolCast(CurrentUserUtils.ActiveDashboard?.darkSc function toggleBackground(doc: Doc) { UndoManager.RunInBatch(() => runInAction(() => { - const layers = StrListCast(doc.layers); + const layers = StrListCast(doc._layerTags); if (!layers.includes(StyleLayers.Background)) { - if (!layers.length) doc.layers = new List<string>([StyleLayers.Background]); + if (!layers.length) doc._layerTags = new List<string>([StyleLayers.Background]); else layers.push(StyleLayers.Background); } else layers.splice(layers.indexOf(StyleLayers.Background), 1); @@ -68,16 +68,15 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps | const selected = property.includes(":selected"); const isCaption = property.includes(":caption"); const isAnchor = property.includes(":anchor"); - const isFooter = property.includes(":footer"); - - const isBackground = () => StrListCast(doc?.layers).includes(StyleLayers.Background); + const isAnnotated = property.includes(":annotated"); + const isBackground = () => StrListCast(doc?._layerTags).includes(StyleLayers.Background); const backgroundCol = () => props?.styleProvider?.(doc, props, StyleProp.BackgroundColor); const opacity = () => props?.styleProvider?.(doc, props, StyleProp.Opacity); switch (property.split(":")[0]) { case StyleProp.TreeViewIcon: return doc ? Doc.toIcon(doc) : "question"; case StyleProp.DocContents: return undefined; - case StyleProp.WidgetColor: return darkScheme() ? "lightgrey" : "dimgrey"; + case StyleProp.WidgetColor: return isAnnotated ? "lightBlue" : darkScheme() ? "lightgrey" : "dimgrey"; case StyleProp.Opacity: return Cast(doc?._opacity, "number", Cast(doc?.opacity, "number", null)); case StyleProp.HideLinkButton: return isAnchor || props?.dontRegisterView || (!selected && (doc?.isLinkButton || doc?.hideLinkButton)); case StyleProp.ShowTitle: return doc && !doc.presentationTargetDoc && StrCast(doc._showTitle, @@ -90,15 +89,15 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps | const colsum = (col.red() + col.green() + col.blue()); if (colsum / col.alpha() > 400 || col.alpha() < 0.25) return "black"; return "white"; - case StyleProp.Hidden: return BoolCast(doc?._hidden, BoolCast(doc?.hidden)); - case StyleProp.BorderRounding: return StrCast(doc?._borderRounding, StrCast(doc?.borderRounding)); + case StyleProp.Hidden: return BoolCast(doc?._hidden); + case StyleProp.BorderRounding: return StrCast(doc?._borderRounding); case StyleProp.TitleHeight: return 15; case StyleProp.HeaderMargin: return ([CollectionViewType.Stacking, CollectionViewType.Masonry].includes(doc?._viewType as any) || doc?.type === DocumentType.RTF) && doc?._showTitle && !StrCast(doc?.showTitle).includes(":hover") ? 15 : 0; case StyleProp.BackgroundColor: { if (isAnchor && docProps) return "transparent"; if (isCaption) return "rgba(0,0,0 ,0.4)"; if (Doc.UserDoc().renderStyle === "comic") return "transparent"; - let docColor: Opt<string> = StrCast(doc?._backgroundColor, StrCast(doc?.backgroundColor)); + let docColor: Opt<string> = StrCast(doc?._backgroundColor); if (!docProps) { if (MainView.Instance.LastButton === doc) return darkScheme() ? "dimgrey" : "lightgrey"; switch (doc?.type) { @@ -115,6 +114,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps | case DocumentType.FILTER: docColor = docColor || (darkScheme() ? "#2d2d2d" : "rgba(105, 105, 105, 0.432)"); break; case DocumentType.INK: docColor = doc?.isInkMask ? "rgba(0,0,0,0.7)" : undefined; break; case DocumentType.SLIDER: break; + case DocumentType.EQUATION: docColor = docColor || "transparent"; break; case DocumentType.LABEL: docColor = docColor || (doc.annotationOn !== undefined ? "rgba(128, 128, 128, 0.18)" : undefined); break; case DocumentType.BUTTON: docColor = docColor || (darkScheme() ? "#2d2d2d" : "lightgray"); break; case DocumentType.LINK: return "transparent"; @@ -163,7 +163,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps | if (isAnchor && docProps) return "none"; if (props?.pointerEvents === "none") return "none"; const layer = doc && props?.layerProvider?.(doc); - if (opacity() === 0 || doc?.type === DocumentType.INK || doc?.isInkMask) return "none"; + if (opacity() === 0 || (doc?.type === DocumentType.INK && !docProps?.treeViewDoc) || doc?.isInkMask) return "none"; if (layer === false && !selected && !SnappingManager.GetIsDragging()) return "none"; if (doc?.type !== DocumentType.INK && layer === true) return "all"; return undefined; @@ -271,15 +271,15 @@ export function DefaultLayerProvider(thisDoc: Doc) { if (assign) { const activeLayer = StrCast(thisDoc?.activeLayer); if (activeLayer) { - const layers = Cast(doc.layers, listSpec("string"), []); + const layers = Cast(doc._layerTags, listSpec("string"), []); if (layers.length && !layers.includes(activeLayer)) layers.push(activeLayer); - else if (!layers.length) doc.layers = new List<string>([activeLayer]); + else if (!layers.length) doc._layerTags = new List<string>([activeLayer]); if (activeLayer === "red" || activeLayer === "green" || activeLayer === "blue") doc._backgroundColor = activeLayer; } return true; } else { if (Doc.AreProtosEqual(doc, thisDoc)) return true; - const layers = StrListCast(doc.layers); + const layers = StrListCast(doc._layerTags); if (!layers.length && !thisDoc?.activeLayer) return true; if (layers.includes(StrCast(thisDoc?.activeLayer))) return true; return false; diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx index d2e0cb265..f39d5ee4c 100644 --- a/src/client/views/TemplateMenu.tsx +++ b/src/client/views/TemplateMenu.tsx @@ -140,6 +140,7 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> { rootSelected={returnFalse} onCheckedClick={this.scriptField} onChildClick={this.scriptField} + setHeight={returnFalse} dropAction={undefined} active={returnTrue} parentActive={returnFalse} diff --git a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx index 4bdd39194..077e50dd2 100644 --- a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx +++ b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx @@ -16,6 +16,8 @@ import { EditableView } from "../EditableView"; import { CollectionStackingView } from "./CollectionStackingView"; import "./CollectionStackingView.scss"; import { SnappingManager } from "../../util/SnappingManager"; +import { FormattedTextBox } from "../nodes/formattedText/FormattedTextBox"; +import { Id } from "../../../fields/FieldSymbols"; const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; @@ -142,8 +144,10 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr if (!value && !forceEmptyNote) return false; this._createAliasSelected = false; const key = StrCast(this.props.parent.props.Document._pivotField); - const newDoc = Docs.Create.TextDocument(value, { _autoHeight: true, _width: 200, title: value }); + const newDoc = Docs.Create.TextDocument("", { _autoHeight: true, _width: 200, title: value }); const onLayoutDoc = this.onLayoutDoc(key); + FormattedTextBox.SelectOnLoad = newDoc[Id]; + FormattedTextBox.SelectOnLoadChar = value; (onLayoutDoc ? newDoc : newDoc[DataSym])[key] = this.getValue(this.props.heading); const docs = this.props.parent.childDocList; return docs ? (docs.splice(0, 0, newDoc) ? true : false) : this.props.parent.props.addDocument?.(newDoc) || false; diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx index 504cf32a0..a265045b8 100644 --- a/src/client/views/collections/CollectionSchemaView.tsx +++ b/src/client/views/collections/CollectionSchemaView.tsx @@ -553,7 +553,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) { </div>; return <div className={"collectionSchemaView" + (this.props.Document._searchDoc ? "-searchContainer" : "-container")} style={{ - overflow: this.props.overflow === true ? "scroll" : undefined, backgroundColor: "white", + overflow: this.props.scrollOverflow === true ? "scroll" : undefined, backgroundColor: "white", pointerEvents: this.props.Document._searchDoc !== undefined && !this.props.active() && !SnappingManager.GetIsDragging() ? "none" : undefined, width: name === "collectionSchemaView-searchContainer" ? "auto" : this.props.PanelWidth() || "100%", height: this.props.PanelHeight() || "100%", position: "relative", }} > diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 77046f5ea..7152f4272 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -29,6 +29,7 @@ import "./CollectionStackingView.scss"; import { CollectionStackingViewFieldColumn } from "./CollectionStackingViewFieldColumn"; import { CollectionSubView } from "./CollectionSubView"; import { CollectionViewType } from "./CollectionView"; +import { LightboxView } from "../LightboxView"; const _global = (window /* browser */ || global /* node */) as any; type StackingDocument = makeInterface<[typeof collectionSchema, typeof documentSchema]>; @@ -140,8 +141,10 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument, () => this.pivotField, () => this.layoutDoc._columnHeaders = new List() ); - this._autoHeightDisposer = reaction(() => this.layoutDoc._autoHeight, this.forceAutoHeight); + this._autoHeightDisposer = reaction(() => this.layoutDoc._autoHeight, + () => this.props.setHeight(this.headerMargin + this.refList.reduce((p, r) => p + Number(getComputedStyle(r).height.replace("px", "")), 0))); } + componentWillUnmount() { super.componentWillUnmount(); this._pivotFieldDisposer?.(); @@ -202,10 +205,10 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument, const dataDoc = (!doc.isTemplateDoc && !doc.isTemplateForField && !doc.PARAMS) ? undefined : this.props.DataDoc; const height = () => this.getDocHeight(doc); - let dref: Opt<HTMLDivElement>; + let dref: Opt<DocumentView>; const stackedDocTransform = () => this.getDocTransform(doc, dref); this._docXfs.push({ stackedDocTransform, width, height }); - return <DocumentView ref={r => dref = r?.ContentDiv ? r.ContentDiv : undefined} + return <DocumentView ref={r => dref = r || undefined} Document={doc} DataDoc={dataDoc || (!Doc.AreProtosEqual(doc[DataSym], doc) && doc[DataSym])} renderDepth={this.props.renderDepth + 1} @@ -245,18 +248,24 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument, />; } + getDocTransform(doc: Doc, dref?: DocumentView) { + const y = this._scroll; // required for document decorations to update when the text box container is scrolled + const { scale, translateX, translateY } = Utils.GetScreenTransform(dref?.ContentDiv || undefined); + // the document view may center its contents and if so, will prepend that onto the screenToLocalTansform. so we have to subtract that off + return new Transform(- translateX - (dref?.centeringX || 0), - translateY - (dref?.centeringY || 0), 1).scale(this.props.ScreenToLocalTransform().Scale * (this.props.scaling?.() || 1)); + } getDocWidth(d?: Doc) { if (!d) return 0; - const layoutDoc = Doc.Layout(d, this.props.childLayoutTemplate?.()); - const nw = Doc.NativeWidth(layoutDoc); - return Math.min(nw && !this.layoutDoc._columnsFill ? d[WidthSym]() : Number.MAX_VALUE, this.columnWidth / this.numGroupColumns); + const childLayoutDoc = Doc.Layout(d, this.props.childLayoutTemplate?.()); + const nw = Doc.NativeWidth(childLayoutDoc) || (childLayoutDoc._fitWidth ? 0 : d[WidthSym]()); + return Math.min(nw && !this.layoutDoc._columnsFill ? (this.props.scaling?.() || 1) * d[WidthSym]() : Number.MAX_VALUE, this.columnWidth / this.numGroupColumns); } getDocHeight(d?: Doc) { if (!d) return 0; const childDataDoc = (!d.isTemplateDoc && !d.isTemplateForField && !d.PARAMS) ? undefined : this.props.DataDoc; const childLayoutDoc = Doc.Layout(d, this.props.childLayoutTemplate?.()); - const nw = Doc.NativeWidth(childLayoutDoc, childDataDoc); - const nh = Doc.NativeHeight(childLayoutDoc, childDataDoc); + const nw = Doc.NativeWidth(childLayoutDoc, childDataDoc) || (childLayoutDoc._fitWidth ? 0 : d[WidthSym]()); + const nh = Doc.NativeHeight(childLayoutDoc, childDataDoc) || (childLayoutDoc._fitWidth ? 0 : d[HeightSym]()); let wid = this.columnWidth / (this.isStackingView ? this.numGroupColumns : 1); if (!this.layoutDoc._columnsFill) wid = Math.min(wid, childLayoutDoc[WidthSym]()); const hllimit = NumCast(this.layoutDoc.childLimitHeight, -1); @@ -266,7 +275,7 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument, return Math.min(hllimit === 0 ? this.props.PanelWidth() : hllimit === -1 ? 10000 : hllimit, wid * aspect); } return childLayoutDoc._fitWidth ? - (!nh ? this.props.PanelHeight() - 2 * this.yMargin : + (!nh ? Math.min(NumCast(childLayoutDoc.height, 10000) * (this.props.scaling?.() || 1), this.props.PanelHeight() - 2 * this.yMargin) : Math.min(wid * nh / (nw || 1), this.layoutDoc._autoHeight ? 100000 : this.props.PanelHeight() - 2 * this.yMargin)) : Math.min(hllimit === 0 ? this.props.PanelWidth() : hllimit === -1 ? 10000 : hllimit, Math.max(20, childLayoutDoc[HeightSym]())); } @@ -359,8 +368,8 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument, const cols = () => this.isStackingView ? 1 : Math.max(1, Math.min(this.filteredChildren.length, Math.floor((this.props.PanelWidth() - 2 * this.xMargin) / (this.columnWidth + this.gridGap)))); return <CollectionStackingViewFieldColumn - unobserveHeight={(ref) => this.refList.splice(this.refList.indexOf(ref), 1)} - observeHeight={(ref) => { + unobserveHeight={ref => this.refList.splice(this.refList.indexOf(ref), 1)} + observeHeight={ref => { if (ref) { this.refList.push(ref); const doc = this.props.DataDoc && this.props.DataDoc.layout === this.layoutDoc ? this.props.DataDoc : this.layoutDoc; @@ -369,10 +378,8 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument, const height = this.headerMargin + Math.min(NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER), Math.max(...this.refList.map(r => NumCast(Doc.Layout(doc)._viewScale, 1) * Number(getComputedStyle(r).height.replace("px", ""))))); - if (this.props.isAnnotationOverlay) { - doc[this.props.fieldKey + "-height"] = height; - } else { - Doc.Layout(doc)._height = height * NumCast(Doc.Layout(doc)._viewScale, 1); + if (!LightboxView.IsLightboxDocView(this.props.docViewPath())) { + this.props.setHeight(height * NumCast(Doc.Layout(doc)._viewScale, 1)); } } })); @@ -392,17 +399,6 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument, />; } - getDocTransform(doc: Doc, dref?: HTMLDivElement) { - const y = this._scroll; // required for document decorations to update when the text box container is scrolled - const { scale, translateX, translateY } = Utils.GetScreenTransform(dref); - return new Transform(-translateX, -translateY, 1).scale(this.props.ScreenToLocalTransform().Scale); - } - - forceAutoHeight = () => { - const doc = this.props.DataDoc && this.props.DataDoc.layout === this.layoutDoc ? this.props.DataDoc : this.layoutDoc; - Doc.Layout(doc)._height = this.headerMargin + this.refList.reduce((p, r) => p + Number(getComputedStyle(r).height.replace("px", "")), 0); - } - sectionMasonry = (heading: SchemaHeaderField | undefined, docList: Doc[], first: boolean) => { const key = this.pivotField; let type: "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" | undefined = undefined; @@ -422,7 +418,7 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument, this.observer = new _global.ResizeObserver(action((entries: any) => { if (this.layoutDoc._autoHeight && ref && this.refList.length && !SnappingManager.GetIsDragging()) { const height = this.refList.reduce((p, r) => p + Number(getComputedStyle(r).height.replace("px", "")), 0); - Doc.Layout(doc)._height = this.headerMargin + Math.max(height, NumCast(doc[this.props.fieldKey + "-height"])); + this.props.setHeight(this.headerMargin + height); } })); this.observer.observe(ref); diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index a9438f8f7..639650b94 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -224,11 +224,14 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?: e.stopPropagation(); return added; } - else if (de.complete.annoDragData && (!this.props.isAnnotationOverlay || de.complete.annoDragData.dragDocument === this.props.Document)) { - e.stopPropagation(); - de.complete.annoDragData.annotationDocument = de.complete.annoDragData.annotationDocCreator(); - de.complete.annoDragData.dropDocument = de.complete.annoDragData.dropDocCreator(); - return de.complete.annoDragData.dropDocument && this.addDocument(de.complete.annoDragData.dropDocument); + else if (de.complete.annoDragData) { + const dropCreator = de.complete.annoDragData.dropDocCreator; + de.complete.annoDragData.dropDocCreator = () => { + const dropped = dropCreator(this.props.isAnnotationOverlay ? this.rootDoc : undefined); + this.addDocument(dropped); + return dropped; + } + return true; } return false; } @@ -253,11 +256,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?: e.stopPropagation(); e.preventDefault(); - const addDocument = (doc: Doc | Doc[]) => { - const docs = doc instanceof Doc ? [doc] : doc; - docs.forEach(doc => Doc.AddDocToList(Cast(Doc.UserDoc().myFileOrphans, Doc, null), "data", doc)); - return this.addDocument(doc); - }; + const addDocument = (doc: Doc | Doc[]) => this.addDocument(doc); if (html) { if (FormattedTextBox.IsFragment(html)) { diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index f305174f1..a217de193 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -153,12 +153,10 @@ export class CollectionView extends Touchable<CollectionViewProps> { const hasContextAnchor = DocListCast(doc.links).some(l => (l.anchor2 === doc && Cast(l.anchor1, Doc, null)?.annotationOn === context) || (l.anchor1 === doc && Cast(l.anchor2, Doc, null)?.annotationOn === context)); if (context && !hasContextAnchor && (context.type === DocumentType.VID || context.type === DocumentType.WEB || context.type === DocumentType.PDF || context.type === DocumentType.IMG)) { const pushpin = Docs.Create.FontIconDocument({ - title: "pushpin", label: "", + title: "pushpin", label: "", annotationOn: Cast(doc.annotationOn, Doc, null), isPushpin: true, icon: "map-pin", x: Cast(doc.x, "number", null), y: Cast(doc.y, "number", null), _backgroundColor: "#0000003d", color: "#ACCEF7", _width: 15, _height: 15, _xPadding: 0, isLinkButton: true, _timecodeToShow: Cast(doc._timecodeToShow, "number", null) }); - pushpin.isPushpin = true; - Doc.GetProto(pushpin).annotationOn = doc.annotationOn; Doc.SetInPlace(doc, "annotationOn", undefined, true); Doc.AddDocToList(context, Doc.LayoutFieldKey(context) + "-annotations", pushpin); const pushpinLink = DocUtils.MakeLink({ doc: pushpin }, { doc: doc }, "pushpin", ""); @@ -191,6 +189,7 @@ export class CollectionView extends Touchable<CollectionViewProps> { const ind = (targetDataDoc[this.props.fieldKey] as List<Doc>).indexOf(doc); if (ind !== -1) { Doc.RemoveDocFromList(targetDataDoc, this.props.fieldKey, doc); + doc.context = undefined; recent && Doc.AddDocToList(recent, "data", doc, undefined, true, true); } }); diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index 3f8794665..f7e067399 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -382,7 +382,8 @@ export class TabMinimapView extends React.Component<TabMinimapViewProps> { } } @computed get renderBounds() { - const bounds = this.props.tabView()?.ComponentView?.freeformData?.(true)?.bounds; + const compView = this.props.tabView()?.ComponentView as CollectionFreeFormView; + const bounds = compView?.freeformData?.(true)?.bounds; if (!bounds) return undefined; const xbounds = bounds.r - bounds.x; const ybounds = bounds.b - bounds.y; @@ -419,6 +420,7 @@ export class TabMinimapView extends React.Component<TabMinimapViewProps> { docViewPath={returnEmptyDoclist} childLayoutTemplate={this.childLayoutTemplate} // bcz: Ugh .. should probably be rendering a CollectionView or the minimap should be part of the collectionFreeFormView to avoid having to set stuff like this. noOverlay={true} // don't render overlay Docs since they won't scale + setHeight={returnFalse} active={returnTrue} select={emptyFunction} dropAction={undefined} diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index 0852b2a46..e383cf176 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -101,13 +101,13 @@ export class TreeView extends React.Component<TreeViewProps> { @observable _dref: DocumentView | undefined | null; get displayName() { return "TreeView(" + this.props.document.title + ")"; } // this makes mobx trace() statements more descriptive get treeViewLockExpandedView() { return this.doc.treeViewLockExpandedView; } - get defaultExpandedView() { return StrCast(this.doc.treeViewDefaultExpandedView, this.fileSysMode ? "data" : Doc.UserDoc().noviceMode || this.outlineMode ? "layout" : "fields"); } + get defaultExpandedView() { return StrCast(this.doc.treeViewDefaultExpandedView, this.fileSysMode ? (this.doc.isFolder ? "data" : "layout") : Doc.UserDoc().noviceMode || this.outlineMode ? "layout" : "fields"); } get treeViewDefaultExpandedView() { return this.treeViewLockExpandedView ? this.defaultExpandedView : (this.childDocs && !this.fileSysMode ? this.fieldKey : this.defaultExpandedView); } @computed get doc() { TraceMobx(); return this.props.document; } @computed get outlineMode() { return this.props.treeView.doc.treeViewType === "outline"; } @computed get fileSysMode() { return this.props.treeView.doc.treeViewType === "fileSystem"; } - @computed get treeViewOpen() { return (!this.props.treeViewPreventOpen && !this.doc.treeViewPreventOpen && BoolCast(this.doc.treeViewOpen)) || this._overrideTreeViewOpen; } + @computed get treeViewOpen() { return (!this.props.treeViewPreventOpen && !this.doc.treeViewPreventOpen && Doc.GetT(this.doc, "treeViewOpen", "boolean", true)) || this._overrideTreeViewOpen; } @computed get treeViewExpandedView() { return StrCast(this.doc.treeViewExpandedView, this.treeViewDefaultExpandedView); } @computed get MAX_EMBED_HEIGHT() { return NumCast(this.props.containingCollection.maxEmbedHeight, 200); } @computed get dataDoc() { return this.doc[DataSym]; } @@ -123,7 +123,7 @@ export class TreeView extends React.Component<TreeViewProps> { const layout = Doc.LayoutField(this.doc) instanceof Doc ? Doc.LayoutField(this.doc) as Doc : undefined; return ((this.props.dataDoc ? DocListCastOrNull(this.props.dataDoc[field]) : undefined) || // if there's a data doc for an expanded template, use it's data field (layout ? DocListCastOrNull(layout[field]) : undefined) || // else if there's a layout doc, display it's fields - DocListCastOrNull(this.doc[field])); // otherwise use the document's data field + DocListCastOrNull(this.doc[field]))?.filter(doc => !this.fileSysMode || field !== "aliases");// || Doc.GetT(doc, "context", Doc, true)); // otherwise use the document's data field } @undoBatch move = (doc: Doc | Doc[], target: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => { return this.doc !== target && this.props.removeDoc?.(doc) === true && addDoc(doc); @@ -254,7 +254,7 @@ export class TreeView extends React.Component<TreeViewProps> { const before = pt[1] < rect.top + rect.height / 2; const inside = this.fileSysMode && !this.doc.isFolder ? false : pt[0] > Math.min(rect.left + 75, rect.left + rect.width * .75) || (!before && this.treeViewOpen && this.childDocList.length); if (de.complete.linkDragData) { - const sourceDoc = de.complete.linkDragData.linkSourceDocument; + const sourceDoc = de.complete.linkDragData.linkSourceGetAnchor(); const destDoc = this.doc; DocUtils.MakeLink({ doc: sourceDoc }, { doc: destDoc }, "tree link", ""); e.stopPropagation(); @@ -477,8 +477,8 @@ export class TreeView extends React.Component<TreeViewProps> { <span className="collectionTreeView-keyHeader" key={this.treeViewExpandedView} onPointerDown={action(() => { if (this.fileSysMode) { - this.doc.treeViewExpandedView = this.doc.isFolder ? this.fieldKey : this.treeViewExpandedView === "layout" ? "fields" : - this.treeViewExpandedView === "fields" ? "aliases" : "layout"; + this.doc.treeViewExpandedView = this.doc.isFolder ? this.fieldKey : this.treeViewExpandedView === "layout" ? "aliases" : + this.treeViewExpandedView === "aliases" && !Doc.UserDoc().noviceMode ? "fields" : "layout"; } else if (this.treeViewOpen) { this.doc.treeViewExpandedView = this.treeViewLockExpandedView ? this.doc.treeViewExpandedView : this.treeViewExpandedView === this.fieldKey ? (Doc.UserDoc().noviceMode || this.outlineMode ? "layout" : "fields") : @@ -495,7 +495,11 @@ export class TreeView extends React.Component<TreeViewProps> { } showContextMenu = (e: React.MouseEvent) => simulateMouseClick(this._docRef?.ContentDiv, e.clientX, e.clientY + 30, e.screenX, e.screenY + 30); - contextMenuItems = () => this.doc.isFolder ? [{ script: ScriptField.MakeFunction(`scriptContext.makeFolder()`, { scriptContext: "any" })!, label: "New Folder" }] : Doc.IsSystem(this.doc) ? [] : [{ script: ScriptField.MakeFunction(`openOnRight(self)`)!, label: "Open" }, { script: ScriptField.MakeFunction(`DocFocus(self)`)!, label: "Focus" }]; + contextMenuItems = () => this.doc.isFolder ? + [{ script: ScriptField.MakeFunction(`scriptContext.makeFolder()`, { scriptContext: "any" })!, label: "New Folder" }] : Doc.IsSystem(this.doc) ? [] : + this.fileSysMode && this.doc === Doc.GetProto(this.doc) ? + [{ script: ScriptField.MakeFunction(`openOnRight(getAlias(self))`)!, label: "Open Alias" }] : + [{ script: ScriptField.MakeFunction(`DocFocusOrOpen(self)`)!, label: "Focus or Open" }] truncateTitleWidth = () => NumCast(this.props.treeView.props.Document.treeViewTruncateTitleWidth, this.props.panelWidth()); onChildClick = () => this.props.onChildClick?.() ?? (this._editTitleScript?.() || ScriptCast(this.doc.treeChildClick)); onChildDoubleClick = () => (!this.outlineMode && this._openScript?.()) || ScriptCast(this.doc.treeChildDoubleClick); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 4040362d8..af8ccb9c5 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -264,7 +264,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P const nd = [Doc.NativeWidth(layoutDoc), Doc.NativeHeight(layoutDoc)]; layoutDoc._width = NumCast(layoutDoc._width, 300); layoutDoc._height = NumCast(layoutDoc._height, nd[0] && nd[1] ? nd[1] / nd[0] * NumCast(layoutDoc._width) : 300); - !StrListCast(d.layers).includes(StyleLayers.Background) && (d._raiseWhenDragged === undefined ? Doc.UserDoc()._raiseWhenDragged : d._raiseWhenDragged) && (d.zIndex = zsorted.length + 1 + i); // bringToFront + !StrListCast(d._layerTags).includes(StyleLayers.Background) && (d._raiseWhenDragged === undefined ? Doc.UserDoc()._raiseWhenDragged : d._raiseWhenDragged) && (d.zIndex = zsorted.length + 1 + i); // bringToFront } this.updateGroupBounds(); @@ -275,16 +275,23 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P @undoBatch internalAnchorAnnoDrop(e: Event, annoDragData: DragManager.AnchorAnnoDragData, xp: number, yp: number) { - annoDragData.dropDocument!.x = xp - annoDragData.offset[0]; - annoDragData.dropDocument!.y = yp - annoDragData.offset[1]; - this.bringToFront(annoDragData.dropDocument!); + const dropCreator = annoDragData.dropDocCreator; + annoDragData.dropDocCreator = (annotationOn: Doc | undefined) => { + const dropDoc = dropCreator(annotationOn); + if (dropDoc) { + dropDoc.x = xp - annoDragData.offset[0]; + dropDoc.y = yp - annoDragData.offset[1]; + this.bringToFront(dropDoc); + } + return dropDoc || this.rootDoc; + } return true; } @undoBatch internalLinkDrop(e: Event, de: DragManager.DropEvent, linkDragData: DragManager.LinkDragData, xp: number, yp: number) { - if (linkDragData.linkSourceDocument === this.props.Document || this.props.Document.annotationOn) return false; - if (!linkDragData.linkSourceDocument.context || StrCast(Cast(linkDragData.linkSourceDocument.context, Doc, null)?.type) === DocumentType.COL) { + if (linkDragData.dragDocument === this.props.Document || this.props.Document.annotationOn) return false; + if (!linkDragData.dragDocument.context || StrCast(Cast(linkDragData.dragDocument.context, Doc, null)?.type) === DocumentType.COL) { // const source = Docs.Create.TextDocument("", { _width: 200, _height: 75, x: xp, y: yp, title: "dropped annotation" }); // this.props.addDocument(source); // linkDragData.linkDocument = DocUtils.MakeLink({ doc: source }, { doc: linkDragData.linkSourceDocument }, "doc annotation"); // TODODO this is where in text links get passed @@ -292,7 +299,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P } else { const source = Docs.Create.TextDocument("", { _width: 200, _height: 75, x: xp, y: yp, title: "dropped annotation" }); this.props.addDocument?.(source); - de.complete.linkDocument = DocUtils.MakeLink({ doc: source }, { doc: linkDragData.linkSourceDocument }, "doc annotation", ""); // TODODO this is where in text links get passed + de.complete.linkDocument = DocUtils.MakeLink({ doc: source }, { doc: linkDragData.linkSourceGetAnchor() }, "doc annotation", ""); // TODODO this is where in text links get passed e.stopPropagation(); return true; } @@ -300,9 +307,9 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P onInternalDrop = (e: Event, de: DragManager.DropEvent) => { const [xp, yp] = this.getTransform().transformPoint(de.x, de.y); - if (this.isAnnotationOverlay !== true && de.complete.linkDragData) return this.internalLinkDrop(e, de, de.complete.linkDragData, xp, yp); if (de.complete.annoDragData?.dragDocument && super.onInternalDrop(e, de)) return this.internalAnchorAnnoDrop(e, de.complete.annoDragData, xp, yp); - if (de.complete.docDragData?.droppedDocuments.length) return this.internalDocDrop(e, de, de.complete.docDragData, xp, yp); + else if (this.isAnnotationOverlay !== true && de.complete.linkDragData) return this.internalLinkDrop(e, de, de.complete.linkDragData, xp, yp); + else if (de.complete.docDragData?.droppedDocuments.length) return this.internalDocDrop(e, de, de.complete.docDragData, xp, yp); return false; } @@ -429,8 +436,8 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P styleProp = colors[cluster % colors.length]; const set = this._clusterSets[cluster]?.filter(s => s.backgroundColor); // override the cluster color with an explicitly set color on a non-background document. then override that with an explicitly set color on a background document - set?.filter(s => !StrListCast(s.layers).includes(StyleLayers.Background)).map(s => styleProp = StrCast(s.backgroundColor)); - set?.filter(s => StrListCast(s.layers).includes(StyleLayers.Background)).map(s => styleProp = StrCast(s.backgroundColor)); + set?.filter(s => !StrListCast(s._layerTags).includes(StyleLayers.Background)).map(s => styleProp = StrCast(s.backgroundColor)); + set?.filter(s => StrListCast(s._layerTags).includes(StyleLayers.Background)).map(s => styleProp = StrCast(s.backgroundColor)); } } //else if (doc && NumCast(doc.group, -1) !== -1) styleProp = "gray"; return styleProp; @@ -869,7 +876,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P @action bringToFront = (doc: Doc, sendToBack?: boolean) => { - if (sendToBack || StrListCast(doc.layers).includes(StyleLayers.Background)) { + if (sendToBack || StrListCast(doc._layerTags).includes(StyleLayers.Background)) { doc.zIndex = 0; } else if (doc.isInkMask) { doc.zIndex = 5000; @@ -1363,7 +1370,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P const isDocInView = (doc: Doc, rect: { left: number, top: number, width: number, height: number }) => intersectRect(docDims(doc), rect); const otherBounds = { left: this.panX(), top: this.panY(), width: Math.abs(size[0]), height: Math.abs(size[1]) }; - let snappableDocs = activeDocs.filter(doc => !StrListCast(doc.layers).includes(StyleLayers.Background) && doc.z === undefined && isDocInView(doc, selRect)); // first see if there are any foreground docs to snap to + let snappableDocs = activeDocs.filter(doc => !StrListCast(doc._layerTags).includes(StyleLayers.Background) && doc.z === undefined && isDocInView(doc, selRect)); // first see if there are any foreground docs to snap to !snappableDocs.length && (snappableDocs = activeDocs.filter(doc => doc.z === undefined && isDocInView(doc, selRect))); // if not, see if there are background docs to snap to !snappableDocs.length && (snappableDocs = activeDocs.filter(doc => doc.z !== undefined && isDocInView(doc, otherBounds))); // if not, then why not snap to floating docs diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 9e442a8c8..2a30e5fd0 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -167,7 +167,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque } else if (!e.ctrlKey && !e.metaKey && SelectionManager.Views().length < 2) { FormattedTextBox.SelectOnLoadChar = FormattedTextBox.DefaultLayout && !this.props.childLayoutString ? e.key : ""; FormattedTextBox.LiveTextUndo = UndoManager.StartBatch("live text batch"); - this.props.addLiveTextDocument(CurrentUserUtils.GetNewTextDoc("-typed text-", x, y, 200, 100, this.props.xMargin === 0)); + this.props.addLiveTextDocument(CurrentUserUtils.GetNewTextDoc("-typed text-", x, y, 200, 100, this.props.xMargin === 0, this.props.isAnnotationOverlay ? this.props.Document : undefined)); e.stopPropagation(); } } @@ -359,11 +359,12 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque const newCollection = creator ? creator(selected, { title: "nested stack", }) : ((doc: Doc) => { Doc.GetProto(doc).data = new List<Doc>(selected); Doc.GetProto(doc).title = makeGroup ? "grouping" : "nested freeform"; + !this.props.isAnnotationOverlay && Doc.AddDocToList(Cast(Doc.UserDoc().myFileOrphans, Doc, null), "data", Doc.GetProto(doc)); doc._panX = doc._panY = 0; return doc; })(Doc.MakeCopy(Doc.UserDoc().emptyCollection as Doc, true)); newCollection.system = undefined; - newCollection.layers = new List<string>(layers); + newCollection._layerTags = new List<string>(layers); newCollection._width = this.Bounds.width; newCollection._height = this.Bounds.height; newCollection._isGroup = makeGroup; @@ -652,7 +653,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque render() { return <div className="marqueeView" style={{ - overflow: (!this.props.ContainingCollectionView && this.props.isAnnotationOverlay) ? "visible" : + overflow: //(!this.props.ContainingCollectionView && this.props.isAnnotationOverlay) ? "visible" : StrCast(this.props.Document._overflow), cursor: "hand" }} diff --git a/src/client/views/collections/collectionGrid/CollectionGridView.tsx b/src/client/views/collections/collectionGrid/CollectionGridView.tsx index 58db080ad..e2feff5ed 100644 --- a/src/client/views/collections/collectionGrid/CollectionGridView.tsx +++ b/src/client/views/collections/collectionGrid/CollectionGridView.tsx @@ -235,7 +235,7 @@ export class CollectionGridView extends CollectionSubView(GridSchema) { i, y, h, x: x + w > this.numCols ? 0 : x, // handles wrapping around of nodes when numCols decreases w: Math.min(w, this.numCols), // reduces width if greater than numCols - static: BoolCast(this.childLayoutPairs.find(({ layout }) => layout[Id] === i)?.layout.lockedPosition, false) // checks if the lock position item has been selected in the context menu + static: BoolCast(this.childLayoutPairs.find(({ layout }) => layout[Id] === i)?.layout._lockedPosition, false) // checks if the lock position item has been selected in the context menu })) : this.savedLayoutList.map((layout, index) => { Object.assign(layout, this.unflexedPosition(index)); return layout; }); } diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx index e51417f64..d23f3309e 100644 --- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx +++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx @@ -217,8 +217,8 @@ export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocu Document={layout} DataDoc={layout.resolvedDataDoc as Doc} styleProvider={this.props.styleProvider} - layerProvider={undefined} - docViewPath={returnEmptyDoclist} + layerProvider={this.props.layerProvider} + docViewPath={this.props.docViewPath} LayoutTemplate={this.props.childLayoutTemplate} LayoutTemplateString={this.props.childLayoutString} freezeDimensions={this.props.childFreezeDimensions} @@ -257,7 +257,7 @@ export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocu const collector: JSX.Element[] = []; for (let i = 0; i < childLayoutPairs.length; i++) { const { layout } = childLayoutPairs[i]; - const dxf = () => this.lookupIndividualTransform(layout).translate(-NumCast(Document._xMargin), -NumCast(Document._yMargin)); + const dxf = () => this.lookupIndividualTransform(layout).translate(-NumCast(Document._xMargin), -NumCast(Document._yMargin)).scale((this.props.scaling?.() || 1)); const width = () => this.lookupPixels(layout); const height = () => PanelHeight() - 2 * NumCast(Document._yMargin) - (BoolCast(Document.showWidthLabels) ? 20 : 0); collector.push( diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx index d61a2bb72..641ae6ce1 100644 --- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx +++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx @@ -217,8 +217,8 @@ export class CollectionMultirowView extends CollectionSubView(MultirowDocument) Document={layout} DataDoc={layout.resolvedDataDoc as Doc} styleProvider={this.props.styleProvider} - layerProvider={undefined} - docViewPath={returnEmptyDoclist} + layerProvider={this.props.layerProvider} + docViewPath={this.props.docViewPath} LayoutTemplate={this.props.childLayoutTemplate} LayoutTemplateString={this.props.childLayoutString} freezeDimensions={this.props.childFreezeDimensions} @@ -257,19 +257,13 @@ export class CollectionMultirowView extends CollectionSubView(MultirowDocument) const collector: JSX.Element[] = []; for (let i = 0; i < childLayoutPairs.length; i++) { const { layout } = childLayoutPairs[i]; - const dxf = () => this.lookupIndividualTransform(layout).translate(-NumCast(Document._xMargin), -NumCast(Document._yMargin)); + const dxf = () => this.lookupIndividualTransform(layout).translate(-NumCast(Document._xMargin), -NumCast(Document._yMargin)).scale((this.props.scaling?.() || 1)); const height = () => this.lookupPixels(layout); const width = () => PanelWidth() - 2 * NumCast(Document._xMargin) - (BoolCast(Document.showWidthLabels) ? 20 : 0); collector.push( - <div - className={"document-wrapper"} - key={"wrapper" + i} - > + <div className={"document-wrapper"} key={"wrapper" + i} > {this.getDisplayDoc(layout, dxf, width, height)} - <HeightLabel - layout={layout} - collectionDoc={Document} - /> + <HeightLabel layout={layout} collectionDoc={Document} /> </div>, <ResizeBar height={resizerHeight} diff --git a/src/client/views/nodes/ColorBox.tsx b/src/client/views/nodes/ColorBox.tsx index d5b6a269e..715ec92f8 100644 --- a/src/client/views/nodes/ColorBox.tsx +++ b/src/client/views/nodes/ColorBox.tsx @@ -11,7 +11,7 @@ import { StrCast } from "../../../fields/Types"; import { SelectionManager } from "../../util/SelectionManager"; import { undoBatch } from "../../util/UndoManager"; import { ViewBoxBaseComponent } from "../DocComponent"; -import { ActiveInkPen, ActiveInkWidth, ActiveInkBezierApprox, SetActiveInkColor, SetActiveInkWidth, SetActiveBezierApprox } from "../InkingStroke"; +import { ActiveInkPen, ActiveInkWidth, ActiveInkBezierApprox, SetActiveInkColor, SetActiveInkWidth, SetActiveBezierApprox, ActiveInkColor } from "../InkingStroke"; import "./ColorBox.scss"; import { FieldView, FieldViewProps } from './FieldView'; import { DocumentType } from "../../documents/DocumentTypes"; @@ -60,8 +60,7 @@ export class ColorBox extends ViewBoxBaseComponent<FieldViewProps, ColorDocument style={{ width: `${100}%`, height: `${100}%` }} > <SketchPicker onChange={ColorBox.switchColor} presetColors={['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505', '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B', '#FFFFFF', '#f1efeb', 'transparent']} - color={StrCast(ActiveInkPen()?.backgroundColor, - StrCast(selDoc?._backgroundColor, StrCast(selDoc?.backgroundColor, "black")))} /> + color={StrCast(selDoc?._backgroundColor, ActiveInkColor())} /> <div style={{ display: "grid", gridTemplateColumns: "20% 80%", paddingTop: "10px" }}> <div> {ActiveInkWidth() ?? 2}</div> diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index 7a8eb5628..02c112745 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -11,6 +11,7 @@ import { CollectionView } from "../collections/CollectionView"; import { YoutubeBox } from "./../../apis/youtube/YoutubeBox"; import { AudioBox } from "./AudioBox"; import { LabelBox } from "./LabelBox"; +import { EquationBox } from "./EquationBox"; import { SliderBox } from "./SliderBox"; import { LinkBox } from "./LinkBox"; import { ScriptingBox } from "./ScriptingBox"; @@ -109,6 +110,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & Fo isSelected: (outsideReaction: boolean) => boolean, select: (ctrl: boolean) => void, scaling?: () => number, + setHeight: (height: number) => void, layoutKey: string, hideOnLeave?: boolean, }> { @@ -219,7 +221,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & Fo blacklistedAttrs={emptyPath} renderInWrapper={false} components={{ - FormattedTextBox, ImageBox, DirectoryImportBox, FontIconBox, LabelBox, SliderBox, FieldView, + FormattedTextBox, ImageBox, DirectoryImportBox, FontIconBox, LabelBox, EquationBox, SliderBox, FieldView, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox, PresBox, YoutubeBox, PresElementBox, SearchBox, FilterBox, ColorBox, DashWebRTCVideo, LinkAnchorBox, InkingStroke, DocHolderBox, LinkBox, ScriptingBox, diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx index 18cabc309..085ffae26 100644 --- a/src/client/views/nodes/DocumentLinksButton.tsx +++ b/src/client/views/nodes/DocumentLinksButton.tsx @@ -51,16 +51,10 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp if (this.props.InMenu && this.props.StartLink) { if (this._linkButton.current !== null) { const linkDrag = UndoManager.StartBatch("Drag Link"); - this.props.View && DragManager.StartLinkDrag(this._linkButton.current, this.props.View.props.Document, e.pageX, e.pageY, { + this.props.View && DragManager.StartLinkDrag(this._linkButton.current, this.props.View.props.Document, this.props.View.ComponentView?.getAnchor, e.pageX, e.pageY, { dragComplete: dropEv => { if (this.props.View && dropEv.linkDocument) {// dropEv.linkDocument equivalent to !dropEve.aborted since linkDocument is only assigned on a completed drop !dropEv.linkDocument.linkRelationship && (Doc.GetProto(dropEv.linkDocument).linkRelationship = "hyperlink"); - - // we want to allow specific views to handle the link creation in their own way (e.g., rich text makes text hyperlinks) - // the dragged view can regiser a linkDropCallback to be notified that the link was made and to update their data structures - // however, the dropped document isn't so accessible. What we do is set the newly created link document on the documentView - // The documentView passes a function prop returning this link doc to its descendants who can react to changes to it. - dropEv.linkDragData?.linkDropCallback?.(dropEv as { linkDocument: Doc }); // bcz: typescript can't figure out that this is valid even though we tested dropEv.linkDocument above } linkDrag?.end(); }, @@ -106,7 +100,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp const alias = Doc.MakeAlias(otherdoc); alias.x = wid; alias.y = 0; - alias.lockedPosition = false; + alias._lockedPosition = false; wid += otherdoc[WidthSym](); Doc.AddDocToList(Doc.GetProto(target), "data", alias); } diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index f07cbfbf5..cd61d20b1 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -1,5 +1,5 @@ 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 { AclAdmin, AclEdit, AclPrivate, DataSym, Doc, DocListCast, Field, Opt, StrListCast } from "../../../fields/Doc"; import { Document } from '../../../fields/documentSchemas'; @@ -275,7 +275,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps this._downX = touch.clientX; this._downY = touch.clientY; if (!e.nativeEvent.cancelBubble) { - if ((this.active || this.layoutDoc.onDragStart || this.onClickHandler) && !e.ctrlKey && !this.layoutDoc.lockedPosition && !CurrentUserUtils.OverlayDocs.includes(this.layoutDoc)) e.stopPropagation(); + if ((this.active || this.layoutDoc.onDragStart || this.onClickHandler) && !e.ctrlKey && !this.layoutDoc._lockedPosition && !CurrentUserUtils.OverlayDocs.includes(this.layoutDoc)) e.stopPropagation(); this.removeMoveListeners(); this.addMoveListeners(); this.removeEndListeners(); @@ -290,7 +290,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps if (e.cancelBubble && this.active) { this.removeMoveListeners(); } - else if (!e.cancelBubble && (this.props.isSelected(true) || this.props.parentActive(true) || this.layoutDoc.onDragStart || this.onClickHandler) && !this.layoutDoc.lockedPosition && !CurrentUserUtils.OverlayDocs.includes(this.layoutDoc)) { + else if (!e.cancelBubble && (this.props.isSelected(true) || this.props.parentActive(true) || this.layoutDoc.onDragStart || this.onClickHandler) && !this.layoutDoc._lockedPosition && !CurrentUserUtils.OverlayDocs.includes(this.layoutDoc)) { const touch = me.touchEvent.changedTouches.item(0); if (touch && (Math.abs(this._downX - touch.clientX) > 3 || Math.abs(this._downY - touch.clientY) > 3)) { if (!e.altKey && (!this.topMost || this.layoutDoc.onDragStart || this.onClickHandler)) { @@ -429,7 +429,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD)) { let stopPropagate = true; let preventDefault = true; - !StrListCast(this.props.Document.layers).includes(StyleLayers.Background) && (this.rootDoc._raiseWhenDragged === undefined ? Doc.UserDoc()._raiseWhenDragged : this.rootDoc._raiseWhenDragged) && this.props.bringToFront(this.rootDoc); + !StrListCast(this.props.Document._layerTags).includes(StyleLayers.Background) && (this.rootDoc._raiseWhenDragged === undefined ? Doc.UserDoc()._raiseWhenDragged : this.rootDoc._raiseWhenDragged) && this.props.bringToFront(this.rootDoc); if (this._doubleTap && (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); @@ -528,7 +528,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps if (e.cancelBubble && this.active) { document.removeEventListener("pointermove", this.onPointerMove); // stop listening to pointerMove if something else has stopPropagated it (e.g., the MarqueeView) } - else if (!e.cancelBubble && (this.props.isSelected(true) || this.props.parentActive(true) || this.layoutDoc.onDragStart) && !this.layoutDoc.lockedPosition && !CurrentUserUtils.OverlayDocs.includes(this.layoutDoc)) { + else if (!e.cancelBubble && (this.props.isSelected(true) || this.props.parentActive(true) || this.layoutDoc.onDragStart) && !this.layoutDoc._lockedPosition && !CurrentUserUtils.OverlayDocs.includes(this.layoutDoc)) { if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3) { if (!e.altKey && (!this.topMost || this.layoutDoc.onDragStart || this.onClickHandler) && (e.buttons === 1 || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE))) { document.removeEventListener("pointermove", this.onPointerMove); @@ -596,7 +596,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps @undoBatch deleteClicked = () => this.props.removeDocument?.(this.props.Document); @undoBatch toggleDetail = () => this.Document.onClick = ScriptField.MakeScript(`toggleDetail(self, "${this.Document.layoutKey}")`); - @undoBatch toggleLockPosition = () => this.Document.lockedPosition = this.Document.lockedPosition ? undefined : true; + @undoBatch toggleLockPosition = () => this.Document._lockedPosition = this.Document._lockedPosition ? undefined : true; @undoBatch @action drop = async (e: Event, de: DragManager.DropEvent) => { @@ -607,13 +607,17 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps "Linking to document tabs not yet supported. Drop link on document content."); return; } - if (de.complete.annoDragData) de.complete.annoDragData.annotationDocument = de.complete.annoDragData.annotationDocCreator(); - const linkSource = de.complete.annoDragData ? de.complete.annoDragData.annotationDocument : de.complete.linkDragData ? de.complete.linkDragData.linkSourceDocument : undefined; - if (linkSource && linkSource !== this.props.Document) { + const linkdrag = de.complete.annoDragData ?? de.complete.linkDragData; + if (linkdrag) linkdrag.linkSourceDoc = linkdrag.linkSourceGetAnchor(); + if (linkdrag?.linkSourceDoc) { e.stopPropagation(); - const dropDoc = this._componentView?.getAnchor?.() || this.rootDoc; - if (de.complete.annoDragData) de.complete.annoDragData.dropDocument = dropDoc; - de.complete.linkDocument = DocUtils.MakeLink({ doc: linkSource }, { doc: dropDoc }, "link", undefined, undefined, undefined, [de.x, de.y]); + if (de.complete.annoDragData && !de.complete.annoDragData.dropDocument) { + de.complete.annoDragData.dropDocument = de.complete.annoDragData.dropDocCreator(undefined); + } + if (de.complete.annoDragData || this.rootDoc !== linkdrag.linkSourceDoc.context) { + const dropDoc = de.complete.annoDragData?.dropDocument ?? this._componentView?.getAnchor?.() ?? this.props.Document; + de.complete.linkDocument = DocUtils.MakeLink({ doc: linkdrag.linkSourceDoc }, { doc: dropDoc }, "link", undefined, undefined, undefined, [de.x, de.y]); + } } } @@ -687,7 +691,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps if (!this.Document.annotationOn) { const options = cm.findByDescription("Options..."); const optionItems: ContextMenuProps[] = options && "subitems" in options ? options.subitems : []; - 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" }); + 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" }); onClicks.push({ description: this.Document.ignoreClick ? "Select" : "Do Nothing", event: () => this.Document.ignoreClick = !this.Document.ignoreClick, icon: this.Document.ignoreClick ? "unlock" : "lock" }); @@ -755,6 +759,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps screenToLocal = () => this.props.ScreenToLocalTransform().translate(0, -this.headerMargin); contentScaling = () => this.ContentScale; onClickFunc = () => this.onClickHandler; + setHeight = (height: number) => this.rootDoc._height = height; setContentView = (view: { getAnchor?: () => Doc, forward?: () => boolean, back?: () => boolean }) => this._componentView = view; @observable contentsActive: () => boolean = returnFalse; @action setContentsActive = (setActive: () => boolean) => this.contentsActive = setActive; @@ -780,6 +785,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps setContentView={this.setContentView} scaling={this.contentScaling} PanelHeight={this.panelHeight} + setHeight={this.setHeight} contentsActive={this.setContentsActive} parentActive={this.parentActive} ScreenToLocalTransform={this.screenToLocal} @@ -953,7 +959,8 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps ["transparent", "#65350c", "#65350c", "yellow", "magenta", "cyan", "orange"] : ["transparent", "maroon", "maroon", "yellow", "magenta", "cyan", "orange"])[highlightIndex]; const highlightStyle = ["solid", "dashed", "solid", "solid", "solid", "solid", "solid"][highlightIndex]; - let highlighting = !this.props.cantBrush && highlightIndex && ![DocumentType.FONTICON, DocumentType.INK].includes(this.layoutDoc.type as any) && this.layoutDoc._viewType !== CollectionViewType.Linear; + const excludeTypes = !this.props.treeViewDoc ? [DocumentType.FONTICON, DocumentType.INK] : [DocumentType.FONTICON]; + let highlighting = !this.props.cantBrush && highlightIndex && !excludeTypes.includes(this.layoutDoc.type as any) && this.layoutDoc._viewType !== CollectionViewType.Linear; highlighting = highlighting && this.props.focus !== emptyFunction && this.layoutDoc.title !== "[pres element template]"; // bcz: hack to turn off highlighting onsidebar panel documents. need to flag a document as not highlightable in a more direct way const boxShadow = highlighting && this.borderRounding && highlightStyle !== "dashed" ? `0 0 0 ${highlightIndex}px ${highlightColor}` : @@ -982,6 +989,7 @@ export class DocumentView extends React.Component<DocumentViewProps> { public static ROOT_DIV = "documentView-effectsWrapper"; public get displayName() { return "DocumentView(" + this.props.Document?.title + ")"; } // this makes mobx trace() statements more descriptive public ContentRef = React.createRef<HTMLDivElement>(); + private _disposers: { [name: string]: IReactionDisposer } = {}; @observable public docView: DocumentViewInternal | undefined | null; @@ -997,30 +1005,40 @@ export class DocumentView extends React.Component<DocumentViewProps> { @computed get docViewPath() { return this.props.docViewPath ? [...this.props.docViewPath(), this] : [this]; } @computed get layoutDoc() { return Doc.Layout(this.Document, this.props.LayoutTemplate?.()); } - @computed get nativeWidth() { return this.docView?._componentView?.reverseNativeScaling?.() ? 0 : returnVal(this.props.NativeWidth?.(), Doc.NativeWidth(this.layoutDoc, this.props.DataDoc, this.props.freezeDimensions)); } - @computed get nativeHeight() { return this.docView?._componentView?.reverseNativeScaling?.() ? 0 : returnVal(this.props.NativeHeight?.(), Doc.NativeHeight(this.layoutDoc, this.props.DataDoc, this.props.freezeDimensions) || 0); } + @computed get nativeWidth() { + return this.docView?._componentView?.reverseNativeScaling?.() ? 0 : + returnVal(this.props.NativeWidth?.(), Doc.NativeWidth(this.layoutDoc, this.props.DataDoc, this.props.freezeDimensions)); + } + @computed get nativeHeight() { + return this.docView?._componentView?.reverseNativeScaling?.() ? 0 : + returnVal(this.props.NativeHeight?.(), Doc.NativeHeight(this.layoutDoc, this.props.DataDoc, this.props.freezeDimensions)); + } + shouldNotScale = () => this.layoutDoc._fitWidth || [CollectionViewType.Docking, CollectionViewType.Tree].includes(this.Document._viewType as any); + @computed get effectiveNativeWidth() { return this.nativeWidth || (this.shouldNotScale() ? 0 : NumCast(this.layoutDoc.width)); } + @computed get effectiveNativeHeight() { return this.nativeHeight || (this.shouldNotScale() ? 0 : NumCast(this.layoutDoc.height)); } @computed get nativeScaling() { - if (this.nativeWidth && (this.layoutDoc?._fitWidth || this.props.PanelHeight() / this.nativeHeight > this.props.PanelWidth() / this.nativeWidth)) { - return this.props.PanelWidth() / this.nativeWidth; // width-limited or fitWidth + const minTextScale = this.Document.type === DocumentType.RTF ? 0.1 : 0; + if (this.effectiveNativeWidth && (this.layoutDoc?._fitWidth || this.props.PanelHeight() / this.effectiveNativeHeight > this.props.PanelWidth() / this.effectiveNativeWidth)) { + return Math.max(minTextScale, this.props.PanelWidth() / this.effectiveNativeWidth); // width-limited or fitWidth } - return this.nativeWidth && this.nativeHeight ? this.props.PanelHeight() / this.nativeHeight : 1; // height-limited or unscaled + return this.effectiveNativeWidth && this.effectiveNativeHeight ? Math.max(minTextScale, this.props.PanelHeight() / this.effectiveNativeHeight) : 1; // height-limited or unscaled } - @computed get panelWidth() { return this.nativeWidth ? this.nativeWidth * this.nativeScaling : this.props.PanelWidth(); } + @computed get panelWidth() { return this.effectiveNativeWidth ? this.effectiveNativeWidth * this.nativeScaling : this.props.PanelWidth(); } @computed get panelHeight() { - if (this.nativeHeight) { + if (this.effectiveNativeHeight) { return Math.min(this.props.PanelHeight(), this.props.Document._fitWidth ? - Math.max(NumCast(this.props.Document._height), NumCast(((this.props.Document.scrollHeight || 0) as number) * this.props.PanelWidth() / this.nativeWidth, this.props.PanelHeight())) : - this.nativeHeight * this.nativeScaling + Math.max(NumCast(this.props.Document._height), NumCast(((this.props.Document.scrollHeight || 0) as number) * this.props.PanelWidth() / this.effectiveNativeWidth, this.props.PanelHeight())) : + this.effectiveNativeHeight * this.nativeScaling ); } return this.props.PanelHeight(); } - @computed get Xshift() { return this.nativeWidth ? (this.props.PanelWidth() - this.nativeWidth * this.nativeScaling) / 2 : 0; } - @computed get YShift() { return this.nativeWidth && this.nativeHeight && Math.abs(this.Xshift) < 0.001 ? (this.props.PanelHeight() - this.nativeHeight * this.nativeScaling) / 2 : 0; } + @computed get Xshift() { return this.effectiveNativeWidth ? (this.props.PanelWidth() - this.effectiveNativeWidth * this.nativeScaling) / 2 : 0; } + @computed get Yshift() { return this.effectiveNativeWidth && this.effectiveNativeHeight && Math.abs(this.Xshift) < 0.001 ? (this.props.PanelHeight() - this.effectiveNativeHeight * this.nativeScaling) / 2 : 0; } @computed get centeringX() { return this.props.dontCenter?.includes("x") ? 0 : this.Xshift; } - @computed get centeringY() { return this.props.Document._fitWidth || this.props.dontCenter?.includes("y") ? 0 : this.YShift; } + @computed get centeringY() { return this.props.Document._fitWidth || this.props.dontCenter?.includes("y") ? 0 : this.Yshift; } toggleNativeDimensions = () => this.docView && Doc.toggleNativeDimensions(this.layoutDoc, this.docView.ContentScale, this.props.PanelWidth(), this.props.PanelHeight()); contentsActive = () => this.docView?.contentsActive(); @@ -1067,8 +1085,8 @@ export class DocumentView extends React.Component<DocumentViewProps> { docViewPathFunc = () => this.docViewPath; isSelected = (outsideReaction?: boolean) => SelectionManager.IsSelected(this, outsideReaction); select = (extendSelection: boolean) => SelectionManager.SelectView(this, !SelectionManager.Views().some(v => v.props.Document === this.props.ContainingCollectionDoc) && extendSelection); - NativeWidth = () => this.nativeWidth; - NativeHeight = () => this.nativeHeight; + NativeWidth = () => this.effectiveNativeWidth; + NativeHeight = () => this.effectiveNativeHeight; PanelWidth = () => this.panelWidth; PanelHeight = () => this.panelHeight; ContentScale = () => this.nativeScaling; @@ -1076,41 +1094,49 @@ export class DocumentView extends React.Component<DocumentViewProps> { screenToLocalTransform = () => { return this.props.ScreenToLocalTransform().translate(-this.centeringX, -this.centeringY).scale(1 / this.nativeScaling); } - componentDidMount() { - !BoolCast(this.props.Document?.dontRegisterView, this.props.dontRegisterView) && DocumentManager.Instance.AddView(this); + this._disposers.height = reaction( + () => NumCast(this.layoutDoc._height), + action(height => { + const docMax = NumCast(this.layoutDoc.docMaxAutoHeight); + if (docMax && docMax < height) this.layoutDoc.docMaxAutoHeight = height; + }) + ); + !BoolCast(this.props.Document.dontRegisterView, this.props.dontRegisterView) && DocumentManager.Instance.AddView(this); } componentWillUnmount() { + Object.values(this._disposers).forEach(disposer => disposer?.()); !this.props.dontRegisterView && DocumentManager.Instance.RemoveView(this); } render() { TraceMobx(); - const internalProps = { - ...this.props, - DocumentView: this.selfView, - viewPath: this.docViewPathFunc, - PanelWidth: this.PanelWidth, - PanelHeight: this.PanelHeight, - NativeWidth: this.NativeWidth, - NativeHeight: this.NativeHeight, - isSelected: this.isSelected, - select: this.select, - ContentScaling: this.ContentScale, - ScreenToLocalTransform: this.screenToLocalTransform, - focus: this.props.focus || emptyFunction, - bringToFront: emptyFunction, - }; + const xshift = this.props.Document.isInkMask ? InkingStroke.MaskDim : Math.abs(this.Xshift) <= 0.001 ? this.props.PanelWidth() : undefined; + const yshift = this.props.Document.isInkMask ? InkingStroke.MaskDim : Math.abs(this.Yshift) <= 0.001 ? this.props.PanelHeight() : undefined; return (<div className="contentFittingDocumentView"> {!this.props.Document || !this.props.PanelWidth() ? (null) : ( <div className="contentFittingDocumentView-previewDoc" ref={this.ContentRef} style={{ position: this.props.Document.isInkMask ? "absolute" : undefined, transform: `translate(${this.centeringX}px, ${this.centeringY}px)`, - width: this.props.Document.isInkMask ? InkingStroke.MaskDim : Math.abs(this.Xshift) > 0.001 ? `${100 * (this.props.PanelWidth() - this.Xshift * 2) / this.props.PanelWidth()}%` : this.props.PanelWidth(), - height: this.props.Document.isInkMask ? InkingStroke.MaskDim : Math.abs(this.YShift) > 0.001 ? this.props.Document._fitWidth ? `${this.panelHeight}px` : `${100 * this.nativeHeight / this.nativeWidth * this.props.PanelWidth() / this.props.PanelHeight()}%` : this.props.PanelHeight(), + width: xshift ?? `${100 * (this.props.PanelWidth() - this.Xshift * 2) / this.props.PanelWidth()}%`, + height: yshift ?? this.props.Document._fitWidth ? `${this.panelHeight}px` : + `${100 * this.effectiveNativeHeight / this.effectiveNativeWidth * this.props.PanelWidth() / this.props.PanelHeight()}%`, }}> - <DocumentViewInternal {...this.props} {...internalProps} ref={action((r: DocumentViewInternal | null) => this.docView = r)} /> + <DocumentViewInternal {...this.props} + DocumentView={this.selfView} + viewPath={this.docViewPathFunc} + PanelWidth={this.PanelWidth} + PanelHeight={this.PanelHeight} + NativeWidth={this.NativeWidth} + NativeHeight={this.NativeHeight} + isSelected={this.isSelected} + select={this.select} + ContentScaling={this.ContentScale} + ScreenToLocalTransform={this.screenToLocalTransform} + focus={this.props.focus || emptyFunction} + bringToFront={emptyFunction} + ref={action((r: DocumentViewInternal | null) => this.docView = r)} /> </div>)} </div>); } diff --git a/src/client/views/nodes/EquationBox.scss b/src/client/views/nodes/EquationBox.scss new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/src/client/views/nodes/EquationBox.scss diff --git a/src/client/views/nodes/EquationBox.tsx b/src/client/views/nodes/EquationBox.tsx new file mode 100644 index 000000000..5bc73d5d9 --- /dev/null +++ b/src/client/views/nodes/EquationBox.tsx @@ -0,0 +1,82 @@ +import EquationEditor from 'equation-editor-react'; +import { observer } from 'mobx-react'; +import * as React from 'react'; +import { documentSchema } from '../../../fields/documentSchemas'; +import { createSchema, makeInterface } from '../../../fields/Schema'; +import { StrCast, NumCast } from '../../../fields/Types'; +import { ViewBoxBaseComponent } from '../DocComponent'; +import { FieldView, FieldViewProps } from './FieldView'; +import './LabelBox.scss'; +import { Id } from '../../../fields/FieldSymbols'; +import { simulateMouseClick } from '../../../Utils'; +import { TraceMobx } from '../../../fields/util'; +import { reaction, action } from 'mobx'; +import { Docs } from '../../documents/Documents'; +import { LightboxView } from '../LightboxView'; + +const EquationSchema = createSchema({}); + +type EquationDocument = makeInterface<[typeof EquationSchema, typeof documentSchema]>; +const EquationDocument = makeInterface(EquationSchema, documentSchema); + +@observer +export class EquationBox extends ViewBoxBaseComponent<FieldViewProps, EquationDocument>(EquationDocument) { + public static LayoutString(fieldKey: string) { return FieldView.LayoutString(EquationBox, fieldKey); } + public static SelectOnLoad: string = ""; + _ref: React.RefObject<EquationEditor> = React.createRef(); + componentDidMount() { + if (EquationBox.SelectOnLoad === this.rootDoc[Id] && (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this.props.docViewPath()))) { + this.props.select(false); + + this._ref.current!.mathField.focus(); + this._ref.current!.mathField.select(); + } + reaction(() => this.props.isSelected(), + selected => { + if (this._ref.current) { + if (selected) this._ref.current.element.current.children[0].addEventListener("keydown", this.keyPressed, true); + else this._ref.current.element.current.children[0].removeEventListener("keydown", this.keyPressed); + } + }, { fireImmediately: true }); + } + @action + keyPressed = (e: KeyboardEvent) => { + const _height = Number(getComputedStyle(this._ref.current!.element.current).height.replace("px", "")); + const _width = Number(getComputedStyle(this._ref.current!.element.current).width.replace("px", "")); + if (e.key === "Enter" || e.key === "Tab") { + const nextEq = Docs.Create.EquationDocument({ + title: "# math", text: StrCast(this.dataDoc.text), _width, _height: 25, + x: NumCast(this.layoutDoc.x) + (e.key === "Tab" ? _width + 10 : 0), y: NumCast(this.layoutDoc.y) + (e.key === "Enter" ? _height + 10 : 0) + }); + EquationBox.SelectOnLoad = nextEq[Id]; + this.props.addDocument?.(nextEq); + e.stopPropagation(); + } + if (e.key === "Backspace" && !this.dataDoc.text) this.props.removeDocument?.(this.rootDoc); + } + onChange = (str: string) => { + this.dataDoc.text = str; + const style = this._ref.current && getComputedStyle(this._ref.current.element.current); + if (style) { + const _height = Number(style.height.replace("px", "")); + const _width = Number(style.width.replace("px", "")); + this.layoutDoc._width = Math.max(35, _width); + this.layoutDoc._height = Math.max(25, _height); + } + } + render() { + TraceMobx(); + return (<div onPointerDown={e => !e.ctrlKey && e.stopPropagation()} + style={{ + pointerEvents: !this.props.isSelected() ? "none" : undefined, + }} + > + <EquationEditor ref={this._ref} + value={this.dataDoc.text || "y"} + spaceBehavesLikeTab={true} + onChange={this.onChange} + autoCommands="pi theta sqrt sum prod alpha beta gamma rho" + autoOperatorNames="sin cos tan" /></div> + ); + } +}
\ No newline at end of file diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx index 1b4119210..465c18309 100644 --- a/src/client/views/nodes/FieldView.tsx +++ b/src/client/views/nodes/FieldView.tsx @@ -16,12 +16,13 @@ import { VideoBox } from "./VideoBox"; export interface FieldViewProps extends DocumentViewSharedProps { // FieldView specific props that are not part of DocumentView props fieldKey: string; - overflow?: boolean; // bcz: would like to think this can be avoided -- need to look at further + scrollOverflow?: boolean; // bcz: would like to think this can be avoided -- need to look at further active: (outsideReaction?: boolean) => boolean; select: (isCtrlPressed: boolean) => void; isSelected: (outsideReaction?: boolean) => boolean; scaling?: () => number; + setHeight: (height: number) => void; // properties intended to be used from within layout strings (otherwise use the function equivalents that work more efficiently with React) pointerEvents?: string; diff --git a/src/client/views/nodes/FilterBox.tsx b/src/client/views/nodes/FilterBox.tsx index a3a3ec662..b25d78a1e 100644 --- a/src/client/views/nodes/FilterBox.tsx +++ b/src/client/views/nodes/FilterBox.tsx @@ -190,8 +190,8 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps, FilterBoxDoc } }); let newFacet: Opt<Doc>; - if (facetHeader === "text") {//} || facetValues.rtFields / allCollectionDocs.length > 0.1) { - newFacet = Docs.Create.TextDocument("", { _width: 100, _height: 25, system: true, _stayInCollection: true, _hideContextMenu: true, treeViewExpandedView: "layout", title: facetHeader, treeViewOpen: true, forceActive: true, ignoreClick: true }); + if (facetHeader === "text" || facetValues.rtFields / allCollectionDocs.length > 0.1) { + newFacet = Docs.Create.TextDocument("", { _width: 100, _height: 25, system: true, _stayInCollection: true, _hideContextMenu: true, 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")`; @@ -352,6 +352,7 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps, FilterBoxDoc fieldKey={this.props.fieldKey} CollectionView={undefined} cantBrush={true} + setHeight={returnFalse} // if the tree view can trigger the height of the filter box to change, then this needs to be filled in. onChildClick={this.suppressChildClick} docFilters={returnEmptyFilter} docRangeFilters={returnEmptyFilter} diff --git a/src/client/views/nodes/FontIconBox.tsx b/src/client/views/nodes/FontIconBox.tsx index 121b9f26c..56c79cde9 100644 --- a/src/client/views/nodes/FontIconBox.tsx +++ b/src/client/views/nodes/FontIconBox.tsx @@ -50,7 +50,6 @@ export class FontIconBox extends DocComponent<FieldViewProps, FontIconDocument>( style={{ width: presSize, height: presSize, filter: `invert(${color === "white" ? "100%" : "0%"})`, marginBottom: "5px" }} />; const button = <button className={`menuButton-${shape}`} onContextMenu={this.specificContextMenu} style={{ - boxShadow: this.layoutDoc.ischecked ? `4px 4px 12px black` : undefined, backgroundColor: this.layoutDoc.iconShape === "square" ? backgroundColor : "", }}> <div className="menuButton-wrap"> diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 4c3031ae2..240aa1659 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -62,13 +62,10 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD componentDidMount() { this._disposers.selection = reaction(() => this.props.isSelected(), - selected => { - if (!selected) { - this._savedAnnotations.values().forEach(v => v.forEach(a => a.remove())); - this._savedAnnotations.clear(); - } - }, - { fireImmediately: true }); + selected => !selected && setTimeout(() => { + this._savedAnnotations.values().forEach(v => v.forEach(a => a.remove())); + this._savedAnnotations.clear(); + })); this._disposers.path = reaction(() => ({ nativeSize: this.nativeSize, width: this.layoutDoc[WidthSym]() }), action(({ nativeSize, width }) => { if (!this.layoutDoc._height) { diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx index ebb953dad..83a49a393 100644 --- a/src/client/views/nodes/KeyValuePair.tsx +++ b/src/client/views/nodes/KeyValuePair.tsx @@ -67,6 +67,7 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> { fieldKey: this.props.keyName, rootSelected: returnFalse, isSelected: returnFalse, + setHeight: returnFalse, select: emptyFunction, dropAction: "alias", bringToFront: emptyFunction, diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx index 87d5b07a2..6a7793ff0 100644 --- a/src/client/views/nodes/LabelBox.tsx +++ b/src/client/views/nodes/LabelBox.tsx @@ -59,9 +59,8 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps, LabelDocument } @observable _mouseOver = false; - @computed get backColor() { return this.clicked || this._mouseOver ? StrCast(this.layoutDoc.hovercolor) : "unset"; } + @computed get hoverColor() { return this._mouseOver ? StrCast(this.layoutDoc._hoverBackgroundColor) : "unset"; } - @observable clicked = false; // (!missingParams || !missingParams.length ? "" : "(" + missingParams.map(m => m + ":").join(" ") + ")") render() { const params = Cast(this.paramsDoc["onClick-paramFieldKeys"], listSpec("string"), []); @@ -70,15 +69,12 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps, LabelDocument const label = typeof this.rootDoc[this.fieldKey] === "string" ? StrCast(this.rootDoc[this.fieldKey]) : StrCast(this.rootDoc.title); return ( <div className="labelBox-outerDiv" - onClick={action(() => this.clicked = !this.clicked)} onMouseLeave={action(() => this._mouseOver = false)} onMouseOver={action(() => this._mouseOver = true)} ref={this.createDropTarget} onContextMenu={this.specificContextMenu} style={{ boxShadow: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BoxShadow) }}> <div className="labelBox-mainButton" style={{ - background: StrCast(this.layoutDoc.backgroundColor), - backgroundColor: this.backColor, - color: StrCast(this.layoutDoc.color, "inherit"), + backgroundColor: this.hoverColor, fontSize: StrCast(this.layoutDoc._fontSize) || "inherit", fontFamily: StrCast(this.layoutDoc._fontFamily) || "inherit", letterSpacing: StrCast(this.layoutDoc.letterSpacing), diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx index d76b61502..392565402 100644 --- a/src/client/views/nodes/LinkAnchorBox.tsx +++ b/src/client/views/nodes/LinkAnchorBox.tsx @@ -94,7 +94,7 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps, LinkAnch openLinkTargetOnRight = (e: React.MouseEvent) => { const alias = Doc.MakeAlias(Cast(this.layoutDoc[this.fieldKey], Doc, null)); alias.isLinkButton = undefined; - alias.layers = undefined; + alias._layerTags = undefined; alias.layoutKey = "layout"; this.props.addDocTab(alias, "add:right"); } diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index ac67949f9..6afc2258a 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -204,13 +204,10 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD componentDidMount() { this.props.setContentView?.(this); // this tells the DocumentView that this AudioBox is the "content" of the document. this allows the DocumentView to indirectly call getAnchor() on the AudioBox when making a link. this._disposers.selection = reaction(() => this.props.isSelected(), - selected => { - if (!selected) { - this._savedAnnotations.values().forEach(v => v.forEach(a => a.remove())); - this._savedAnnotations.clear(); - } - }, - { fireImmediately: true }); + selected => !selected && setTimeout(() => { + this._savedAnnotations.values().forEach(v => v.forEach(a => a.remove())); + this._savedAnnotations.clear(); + })); this._disposers.triggerVideo = reaction( () => !LinkDocPreview.LinkInfo && this.props.renderDepth !== -1 ? NumCast(this.Document._triggerVideo, null) : undefined, time => time !== undefined && setTimeout(() => { @@ -485,7 +482,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD playing = () => this._playing; isActiveChild = () => this._isChildActive; - timelineWhenActiveChanged = (isActive: boolean) => this.props.whenActiveChanged(runInAction(() => this._isChildActive = isActive)); + timelineWhenActiveChanged = action((isActive: boolean) => this.props.whenActiveChanged(this._isChildActive = isActive)); timelineScreenToLocal = () => this.props.ScreenToLocalTransform().scale(this.scaling()).translate(0, -this.heightPercent / 100 * this.props.PanelHeight()); setAnchorTime = (time: number) => this.player!.currentTime = this.layoutDoc._currentTimecode = time; timelineHeight = () => this.props.PanelHeight() * (100 - this.heightPercent) / 100; diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index 2cd6f5f33..0cf052501 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -140,13 +140,10 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum runInAction(() => this._url = urlField?.url.toString() || ""); this._disposers.selection = reaction(() => this.props.isSelected(), - selected => { - if (!selected) { - this._savedAnnotations.values().forEach(v => v.forEach(a => a.remove())); - this._savedAnnotations.clear(); - } - }, - { fireImmediately: true }); + selected => !selected && setTimeout(() => { + this._savedAnnotations.values().forEach(v => v.forEach(a => a.remove())); + this._savedAnnotations.clear(); + })); document.addEventListener("pointerup", this.onLongPressUp); document.addEventListener("pointermove", this.onLongPressMove); diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx index b39a845db..62f65cdae 100644 --- a/src/client/views/nodes/formattedText/DashFieldView.tsx +++ b/src/client/views/nodes/formattedText/DashFieldView.tsx @@ -1,18 +1,18 @@ -import { IReactionDisposer, observable, computed, action } from "mobx"; -import { Doc, DocListCast, Field, DataSym } from "../../../../fields/Doc"; +import { action, computed, IReactionDisposer, observable } from "mobx"; +import { observer } from "mobx-react"; +import * as ReactDOM from 'react-dom'; +import { DataSym, Doc, DocListCast, Field } from "../../../../fields/Doc"; import { List } from "../../../../fields/List"; import { listSpec } from "../../../../fields/Schema"; import { SchemaHeaderField } from "../../../../fields/SchemaHeaderField"; import { ComputedField } from "../../../../fields/ScriptField"; import { Cast, StrCast } from "../../../../fields/Types"; import { DocServer } from "../../../DocServer"; +import { DocUtils } from "../../../documents/Documents"; import { CollectionViewType } from "../../collections/CollectionView"; +import "./DashFieldView.scss"; import { FormattedTextBox } from "./FormattedTextBox"; import React = require("react"); -import * as ReactDOM from 'react-dom'; -import "./DashFieldView.scss"; -import { observer } from "mobx-react"; -import { DocUtils } from "../../../documents/Documents"; export class DashFieldView { _fieldWrapper: HTMLDivElement; // container for label and value diff --git a/src/client/views/nodes/formattedText/EquationView.tsx b/src/client/views/nodes/formattedText/EquationView.tsx new file mode 100644 index 000000000..eff018635 --- /dev/null +++ b/src/client/views/nodes/formattedText/EquationView.tsx @@ -0,0 +1,75 @@ +import EquationEditor from "equation-editor-react"; +import { IReactionDisposer } from "mobx"; +import { observer } from "mobx-react"; +import * as ReactDOM from 'react-dom'; +import { Doc } from "../../../../fields/Doc"; +import { StrCast } from "../../../../fields/Types"; +import "./DashFieldView.scss"; +import { FormattedTextBox } from "./FormattedTextBox"; +import React = require("react"); + +export class EquationView { + _fieldWrapper: HTMLDivElement; // container for label and value + + constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) { + this._fieldWrapper = document.createElement("div"); + this._fieldWrapper.style.width = node.attrs.width; + this._fieldWrapper.style.height = node.attrs.height; + this._fieldWrapper.style.fontWeight = "bold"; + this._fieldWrapper.style.position = "relative"; + this._fieldWrapper.style.display = "inline-block"; + this._fieldWrapper.onkeypress = function (e: any) { e.stopPropagation(); }; + this._fieldWrapper.onkeydown = function (e: any) { e.stopPropagation(); }; + this._fieldWrapper.onkeyup = function (e: any) { e.stopPropagation(); }; + this._fieldWrapper.onmousedown = function (e: any) { e.stopPropagation(); }; + + ReactDOM.render(<EquationViewInternal + fieldKey={node.attrs.fieldKey} + width={node.attrs.width} + height={node.attrs.height} + tbox={tbox} + />, this._fieldWrapper); + (this as any).dom = this._fieldWrapper; + } + destroy() { ReactDOM.unmountComponentAtNode(this._fieldWrapper); } + selectNode() { } +} + +interface IEquationViewInternal { + fieldKey: string; + tbox: FormattedTextBox; + width: number; + height: number; +} + +@observer +export class EquationViewInternal extends React.Component<IEquationViewInternal> { + _reactionDisposer: IReactionDisposer | undefined; + _textBoxDoc: Doc; + _fieldKey: string; + + constructor(props: IEquationViewInternal) { + super(props); + this._fieldKey = this.props.fieldKey; + this._textBoxDoc = this.props.tbox.props.Document; + } + + componentWillUnmount() { this._reactionDisposer?.(); } + + render() { + return <div className="equationView" style={{ + position: "relative", + display: "inline-block", + width: this.props.width, + height: this.props.height, + }}> + <EquationEditor + value={StrCast(this._textBoxDoc[this._fieldKey], "y=")} + onChange={str => this._textBoxDoc[this._fieldKey] = str} + autoCommands="pi theta sqrt sum prod alpha beta gamma rho" + autoOperatorNames="sin cos tan" + spaceBehavesLikeTab={true} + /> + </div >; + } +}
\ No newline at end of file diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index b4fbda9f4..104d60fff 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -40,6 +40,7 @@ import { DashDocView } from "./RichTextSchema"; import { DashDocCommentView } from "./DashDocCommentView"; import { DashFieldView } from "./DashFieldView"; +import { EquationView } from "./EquationView"; import { SummaryView } from "./SummaryView"; import { OrderedListView } from "./OrderedListView"; import { FootnoteView } from "./FootnoteView"; @@ -47,7 +48,7 @@ import { FootnoteView } from "./FootnoteView"; import { schema } from "./schema_rts"; import { SelectionManager } from "../../../util/SelectionManager"; import { undoBatch, UndoManager } from "../../../util/UndoManager"; -import { CollectionFreeFormView, collectionFreeformViewProps } from '../../collections/collectionFreeForm/CollectionFreeFormView'; +import { CollectionFreeFormView } from '../../collections/collectionFreeForm/CollectionFreeFormView'; import { ContextMenu } from '../../ContextMenu'; import { ContextMenuProps } from '../../ContextMenuItem'; import { ViewBoxAnnotatableComponent } from "../../DocComponent"; @@ -58,16 +59,13 @@ import "./FormattedTextBox.scss"; import { FormattedTextBoxComment, findLinkMark } from './FormattedTextBoxComment'; import React = require("react"); import { CollectionStackingView } from '../../collections/CollectionStackingView'; -import { CollectionViewType } from '../../collections/CollectionView'; import { SnappingManager } from '../../../util/SnappingManager'; import { LinkDocPreview } from '../LinkDocPreview'; -import { SubCollectionViewProps } from '../../collections/CollectionSubView'; import { StyleProp } from '../../StyleProvider'; import { AnchorMenu } from '../../pdf/AnchorMenu'; import { CurrentUserUtils } from '../../../util/CurrentUserUtils'; import { DocumentManager } from '../../../util/DocumentManager'; import { LightboxView } from '../../LightboxView'; -import { DocAfterFocusFunc } from '../DocumentView'; const translateGoogleApi = require("translate-google-api"); export interface FormattedTextBoxProps { @@ -89,24 +87,38 @@ type PullHandler = (exportState: Opt<GoogleApiClientUtils.Docs.ImportResult>, da export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProps & FormattedTextBoxProps), RichTextDocument>(RichTextDocument) { public static LayoutString(fieldStr: string) { return FieldView.LayoutString(FormattedTextBox, fieldStr); } public static blankState = () => EditorState.create(FormattedTextBox.Instance.config); + public static get DefaultLayout() { + return Cast(Doc.UserDoc().defaultTextLayout, Doc, null) || StrCast(Doc.UserDoc().defaultTextLayout, null); + } public static CanAnnotate = true; public static Instance: FormattedTextBox; public ProseRef?: HTMLDivElement; public get EditorView() { return this._editorView; } public get SidebarKey() { return this.fieldKey + "-sidebar"; } - private _boxRef: React.RefObject<HTMLDivElement> = React.createRef(); private _ref: React.RefObject<HTMLDivElement> = React.createRef(); private _scrollRef: React.RefObject<HTMLDivElement> = React.createRef(); private _editorView: Opt<EditorView>; private _applyingChange: string = ""; private _searchIndex = 0; + private _lastTimedMark: Mark | undefined = undefined; private _cachedLinks: Doc[] = []; private _undoTyping?: UndoManager.Batch; private _disposers: { [name: string]: IReactionDisposer } = {}; private _dropDisposer?: DragManager.DragDropDisposer; private _recordingStart: number = 0; private _ignoreScroll = false; + private _lastText = ""; + private _focusSpeed: Opt<number>; + private _keymap: any = undefined; + private _rules: RichTextRules | undefined; + @computed get sidebarWidthPercent() { return StrCast(this.layoutDoc._sidebarWidthPercent, "0%"); } + @computed get sidebarColor() { return StrCast(this.layoutDoc.sidebarColor, StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], "#e4e4e4")); } + @computed get autoHeight() { return this.layoutDoc._autoHeight && !this.props.ignoreAutoHeight; } + @computed get textHeight() { return NumCast(this.rootDoc[this.fieldKey + "-height"]); } + @computed get scrollHeight() { return NumCast(this.rootDoc[this.fieldKey + "-scrollHeight"]); } + @computed get sidebarHeight() { return NumCast(this.rootDoc[this.SidebarKey + "-height"]); } + @computed get titleHeight() { return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.HeaderMargin) || 0; } @computed get _recording() { return this.dataDoc?.audioState === "recording"; } set _recording(value) { this.dataDoc.audioState = value ? "recording" : undefined; } @@ -114,9 +126,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp public static SelectOnLoad = ""; public static PasteOnLoad: ClipboardEvent | undefined; public static SelectOnLoadChar = ""; - public static IsFragment(html: string) { - return html.indexOf("data-pm-slice") !== -1; - } + public static IsFragment(html: string) { return html.indexOf("data-pm-slice") !== -1; } public static GetHref(html: string): string { const parser = new DOMParser(); const parsedHtml = parser.parseFromString(html, 'text/html'); @@ -127,24 +137,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp return ""; } public static GetDocFromUrl(url: string) { - if (url.startsWith(document.location.origin)) { - const split = new URL(url).pathname.split("doc/"); - const docid = split[split.length - 1]; - return docid; - } - return ""; - } - - @undoBatch - public setFontColor(color: string) { - const view = this._editorView!; - if (view.state.selection.from === view.state.selection.to) return false; - if (view.state.selection.to - view.state.selection.from > view.state.doc.nodeSize - 3) { - this.layoutDoc.color = color; - } - const colorMark = view.state.schema.mark(view.state.schema.marks.pFontColor, { color: color }); - view.dispatch(view.state.tr.addMark(view.state.selection.from, view.state.selection.to, colorMark)); - return true; + return url.startsWith(document.location.origin) ? new URL(url).pathname.split("doc/").lastElement() : ""; // docid } constructor(props: any) { @@ -154,8 +147,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp this._recordingStart = Date.now(); } - public get CurrentDiv(): HTMLDivElement { return this._ref.current!; } - // removes all hyperlink anchors for the removed linkDoc // TODO: bcz: Argh... if a section of text has multiple anchors, this should just remove the intended one. // but since removing one anchor from the list of attr anchors isn't implemented, this will end up removing nothing. @@ -192,35 +183,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp getAnchor = () => this.makeLinkAnchor(undefined, "add:right", undefined, "Anchored Selection"); - linkOnDeselect: Map<string, string> = new Map(); - - doLinkOnDeselect() { - - Array.from(this.linkOnDeselect.entries()).map(entry => { - const key = entry[0]; - const value = entry[1]; - - const id = Utils.GenerateDeterministicGuid(this.dataDoc[Id] + key); - DocServer.GetRefField(value).then(doc => { - DocServer.GetRefField(id).then(linkDoc => { - this.dataDoc[key] = doc || Docs.Create.FreeformDocument([], { title: value, _width: 500, _height: 500 }, value); - DocUtils.Publish(this.dataDoc[key] as Doc, value, this.props.addDocument, this.props.removeDocument); - if (linkDoc) { - (linkDoc as Doc).anchor2 = this.dataDoc[key] as Doc; - } else { - DocUtils.MakeLink({ doc: this.rootDoc }, { doc: this.dataDoc[key] as Doc }, "portal link", "link to named target", id); - } - }); - }); - }); - - this.linkOnDeselect.clear(); - } - @action setupAnchorMenu = () => { AnchorMenu.Instance.Status = "marquee"; - AnchorMenu.Instance.Highlight = action((color: string) => { + AnchorMenu.Instance.Highlight = action((color: string, isLinkButton: boolean) => { this._editorView?.state && RichTextMenu.Instance.insertHighlight(color, this._editorView.state, this._editorView?.dispatch); return undefined; }); @@ -231,55 +197,21 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp AnchorMenu.Instance.StartDrag = action(async (e: PointerEvent, ele: HTMLElement) => { e.preventDefault(); e.stopPropagation(); - const targetCreator = () => { - const target = CurrentUserUtils.GetNewTextDoc("Note linked to " + this.rootDoc.title, 0, 0, 100, 100); + const targetCreator = (annotationOn?: Doc) => { + const target = CurrentUserUtils.GetNewTextDoc("Note linked to " + this.rootDoc.title, 0, 0, 100, 100, undefined, annotationOn); FormattedTextBox.SelectOnLoad = target[Id]; return target; }; - DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(this.rootDoc, () => this.rootDoc, targetCreator), e.pageX, e.pageY, { - dragComplete: e => { - const anchor = this.makeLinkAnchor(undefined, "add:right", undefined, "a link"); - if (!e.aborted && e.annoDragData && e.annoDragData.annotationDocument && e.annoDragData.dropDocument && !e.linkDocument) { - e.linkDocument = DocUtils.MakeLink({ doc: anchor }, { doc: e.annoDragData.dropDocument }, "hyperlink", "link to note"); - e.annoDragData.annotationDocument.isPushpin = e.annoDragData?.dropDocument.annotationOn === this.rootDoc; - } - e.linkDocument && e.annoDragData?.linkDropCallback?.(e as { linkDocument: Doc });// bcz: typescript can't figure out that this is valid even though we tested e.linkDocument - } - }); + DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(this.rootDoc, this.getAnchor, targetCreator), e.pageX, e.pageY); }); const coordsT = this._editorView!.coordsAtPos(this._editorView!.state.selection.to); const coordsB = this._editorView!.coordsAtPos(this._editorView!.state.selection.to); this.props.isSelected(true) && AnchorMenu.Instance.jumpTo(Math.min(coordsT.left, coordsB.left), Math.max(coordsT.bottom, coordsB.bottom)); } - _lastText = ""; dispatchTransaction = (tx: Transaction) => { if (this._editorView) { - const metadata = tx.selection.$from.marks().find((m: Mark) => m.type === schema.marks.metadata); - if (metadata) { - const range = tx.selection.$from.blockRange(tx.selection.$to); - let text = range ? tx.doc.textBetween(range.start, range.end) : ""; - let textEndSelection = tx.selection.to; - for (; textEndSelection < range!.end && text[textEndSelection - range!.start] !== " "; textEndSelection++) { } - text = text.substr(0, textEndSelection - range!.start); - text = text.split(" ")[text.split(" ").length - 1]; - const split = text.split("::"); - if (split.length > 1 && split[1]) { - const key = split[0]; - const value = split[split.length - 1]; - this.linkOnDeselect.set(key, value); - - const id = Utils.GenerateDeterministicGuid(this.dataDoc[Id] + key); - const allAnchors = [{ href: Utils.prepend("/doc/" + id), title: value, anchorId: id }]; - const link = this._editorView.state.schema.marks.linkAnchor.create({ allAnchors, 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); - this.dataDoc[key] = value; - } - } - const state = this._editorView.state.apply(tx); this._editorView.updateState(state); @@ -290,14 +222,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const curProto = Cast(Cast(this.dataDoc.proto, Doc, null)?.[this.fieldKey], RichTextField, null); // the default text inherited from a prototype const curLayout = this.rootDoc !== this.layoutDoc ? Cast(this.layoutDoc[this.fieldKey], RichTextField, null) : undefined; // the default text stored in a layout template const json = JSON.stringify(state.toJSON()); - let unchanged = true; const effectiveAcl = GetEffectiveAcl(this.dataDoc); - const removeSelection = (json: string | undefined) => { - return json?.indexOf("\"storedMarks\"") === -1 ? json?.replace(/"selection":.*/, "") : json?.replace(/"selection":"\"storedMarks\""/, "\"storedMarks\""); - }; + const removeSelection = (json: string | undefined) => json?.indexOf("\"storedMarks\"") === -1 ? + json?.replace(/"selection":.*/, "") : json?.replace(/"selection":"\"storedMarks\""/, "\"storedMarks\""); if (effectiveAcl === AclEdit || effectiveAcl === AclAdmin) { + let unchanged = true; if (this._applyingChange !== this.fieldKey && removeSelection(json) !== removeSelection(curProto?.Data)) { this._applyingChange = this.fieldKey; (curText !== Cast(this.dataDoc[this.fieldKey], RichTextField)?.Text) && (this.dataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now()))); @@ -309,9 +240,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp this.dataDoc[this.props.fieldKey + "-noTemplate"] = true;//(curTemp?.Text || "") !== curText; // mark the data field as being split from the template if it has been edited ScriptCast(this.layoutDoc.onTextChanged, null)?.script.run({ this: this.layoutDoc, self: this.rootDoc, text: curText }); unchanged = false; - } - } else { // if we've deleted all the text in a note driven by a template, then restore the template data this.dataDoc[this.props.fieldKey] = undefined; this._editorView.updateState(EditorState.fromJSON(this.config, JSON.parse((curProto || curTemp).Data))); @@ -321,7 +250,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp this._applyingChange = ""; if (!unchanged) { this.updateTitle(); - this.tryUpdateHeight(); + this.tryUpdateScrollHeight(); } } } else { @@ -381,25 +310,29 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp let node = this._editorView.state.doc; while (node.firstChild && node.firstChild.type.name !== "text") node = node.firstChild; const str = node.textContent; - const titlestr = str.substr(0, Math.min(40, str.length)); - this.dataDoc.title = "-" + titlestr + (str.length > 40 ? "..." : ""); + this.dataDoc.title = "-" + str.substr(0, Math.min(40, str.length)) + (str.length > 40 ? "..." : ""); } } // needs a better API for taking in a set of words with target documents instead of just one target public hyperlinkTerms = (terms: string[], target: Doc) => { if (this._editorView && (this._editorView as any).docView && terms.some(t => t)) { - const res = terms.filter(t => t).map(term => this.findInNode(this._editorView!, this._editorView!.state.doc, term)); - const tr = this._editorView.state.tr; - const flattened: TextSelection[] = []; - res.map(r => r.map(h => flattened.push(h))); - const lastSel = Math.min(flattened.length - 1, this._searchIndex); - this._searchIndex = ++this._searchIndex > flattened.length - 1 ? 0 : this._searchIndex; - const anchor = new Doc(); - const alink = DocUtils.MakeLink({ doc: anchor }, { doc: target }, "automatic")!; - const allAnchors = [{ href: Utils.prepend("/doc/" + anchor[Id]), title: "a link", anchorId: anchor[Id] }]; - const link = this._editorView.state.schema.marks.linkAnchor.create({ allAnchors, title: "a link", location }); - this._editorView.dispatch(tr.addMark(flattened[lastSel].from, flattened[lastSel].to, link)); + const res1 = terms.filter(t => t).map(term => this.findInNode(this._editorView!, this._editorView!.state.doc, term)); + let tr = this._editorView.state.tr; + const flattened1: TextSelection[] = []; + res1.map(r => r.map(h => flattened1.push(h))); + flattened1.forEach((flat, i) => { + const flattened: TextSelection[] = []; + const res = terms.filter(t => t).map(term => this.findInNode(this._editorView!, this._editorView!.state.doc, term)); + res.map(r => r.map(h => flattened.push(h))); + this._searchIndex = ++this._searchIndex > flattened.length - 1 ? 0 : this._searchIndex; + const anchor = Docs.Create.TextanchorDocument(); + const alink = DocUtils.MakeLink({ doc: anchor }, { doc: target }, "automatic")!; + const allAnchors = [{ href: Utils.prepend("/doc/" + anchor[Id]), title: "a link", anchorId: anchor[Id] }]; + const link = this._editorView!.state.schema.marks.linkAnchor.create({ allAnchors, title: "auto link", location }); + tr = tr.addMark(flattened[i].from, flattened[i].to, link); + }); + this._editorView.dispatch(tr); } } public highlightSearchTerms = (terms: string[], backward: boolean) => { @@ -460,7 +393,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp @undoBatch @action - drop = async (e: Event, de: DragManager.DropEvent) => { + drop = (e: Event, de: DragManager.DropEvent) => { + if (de.complete.annoDragData) de.complete.annoDragData.dropDocCreator = this.getAnchor; const dragData = de.complete.docDragData; if (dragData) { const draggedDoc = dragData.draggedDocuments.length && dragData.draggedDocuments[0]; @@ -483,22 +417,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp }); const view = this._editorView!; view.dispatch(view.state.tr.insert(view.posAtCoords({ left: de.x, top: de.y })!.pos, node)); - this.tryUpdateHeight(); e.stopPropagation(); // } } // otherwise, fall through to outer collection to handle drop - } else if (de.complete.linkDragData) { - de.complete.linkDragData.linkDropCallback = this.linkDrop; - } - else if (de.complete.annoDragData) { - de.complete.annoDragData.linkDropCallback = this.linkDrop; } } - linkDrop = (data: { linkDocument: Doc }) => { - const anchor1Title = data.linkDocument.anchor1 instanceof Doc ? StrCast(data.linkDocument.anchor1.title) : "-untitled-"; - const anchor1 = data.linkDocument.anchor1 instanceof Doc ? data.linkDocument.anchor1 : undefined; - this.makeLinkAnchor(anchor1, "add:right", undefined, anchor1Title); - } getNodeEndpoints(context: Node, node: Node): { from: number, to: number } | null { let offset = 0; @@ -523,7 +446,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } } - //Recursively finds matches within a given node findInNode(pm: EditorView, node: Node, find: string) { let ret: TextSelection[] = []; @@ -544,8 +466,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } return ret; } - static _highlights: string[] = ["Audio Tags", "Text from Others", "Todo Items", "Important Items", "Disagree Items", "Ignore Items"]; + static _highlights: string[] = ["Audio Tags", "Text from Others", "Todo Items", "Important Items", "Disagree Items", "Ignore Items"]; updateHighlights = () => { clearStyleSheetRules(FormattedTextBox._userStyleSheet); if (FormattedTextBox._highlights.indexOf("Audio Tags") === -1) { @@ -591,22 +513,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp })), false); } sidebarMove = (e: PointerEvent, down: number[], delta: number[]) => { - const bounds = this.CurrentDiv.getBoundingClientRect(); + const bounds = this._ref.current!.getBoundingClientRect(); this.layoutDoc._sidebarWidthPercent = "" + 100 * Math.max(0, (1 - (e.clientX - bounds.left) / bounds.width)) + "%"; this.layoutDoc._showSidebar = this.layoutDoc._sidebarWidthPercent !== "0%"; e.preventDefault(); return false; } - @undoBatch - @action - toggleNativeDimensions = () => { - alert("need to fix"); - // Doc.toggleNativeDimensions(this.layoutDoc, 1, this.props.NativeWidth?.() || 0, this.props.NativeHeight?.() || 0); - } - public static get DefaultLayout(): Doc | string | undefined { - return Cast(Doc.UserDoc().defaultTextLayout, Doc, null) || StrCast(Doc.UserDoc().defaultTextLayout, null); - } specificContextMenu = (e: React.MouseEvent): void => { const cm = ContextMenu.Instance; @@ -680,78 +593,17 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp Doc.AddDocToList(Cast(Doc.UserDoc()["template-notes"], Doc, null), "data", this.rootDoc); }, icon: "eye" }); - !Doc.UserDoc().noviceMode && appearanceItems.push({ description: "Create progressivized slide...", event: this.progressivizeText, icon: "desktop" }); cm.addItem({ description: "Appearance...", subitems: appearanceItems, icon: "eye" }); const options = cm.findByDescription("Options..."); const optionItems = options && "subitems" in options ? options.subitems : []; - !Doc.UserDoc().noviceMode && optionItems.push({ description: !this.Document._singleLine ? "Make Single Line" : "Make Multi Line", event: () => this.layoutDoc._singleLine = !this.layoutDoc._singleLine, icon: "expand-arrows-alt" }); + optionItems.push({ description: !this.Document._noSidebar ? "Hide Sidebar Handle" : "Show Sidebar Handle", event: () => this.layoutDoc._noSidebar = !this.layoutDoc._noSidebar, icon: "expand-arrows-alt" }); + optionItems.push({ description: !this.Document._singleLine ? "Make Single Line" : "Make Multi Line", event: () => this.layoutDoc._singleLine = !this.layoutDoc._singleLine, icon: "expand-arrows-alt" }); optionItems.push({ description: `${this.Document._autoHeight ? "Lock" : "Auto"} Height`, event: () => this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight, icon: "plus" }); - optionItems.push({ description: `${!Doc.NativeWidth(this.layoutDoc) || !Doc.NativeHeight(this.layoutDoc) ? "Lock" : "Unlock"} Aspect`, event: this.toggleNativeDimensions, icon: "snowflake" }); !options && cm.addItem({ description: "Options...", subitems: optionItems, icon: "eye" }); this._downX = this._downY = Number.NaN; } - progressivizeText = () => { - const list = this.ProseRef?.getElementsByTagName("li"); - const mainBulletText: string[] = []; - const mainBulletList: Doc[] = []; - if (list) { - const newBullets: Doc[] = this.recursiveProgressivize(1, list)[0]; - mainBulletList.push.apply(mainBulletList, newBullets); - } - 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, { - title: StrCast(this.rootDoc.title), - x: NumCast(this.props.Document.x), y: NumCast(this.props.Document.y) + NumCast(this.props.Document._height) + 10, - _width: 400, _height: 225, _fitToBox: true, - }); - this.props.addDocument?.(doc); - } - - recursiveProgressivize = (nestDepth: number, list: HTMLCollectionOf<HTMLLIElement>, d?: number, y?: number, before?: string): [Doc[], number] => { - const mainBulletList: Doc[] = []; - let b = d ? d : 0; - let yLoc = y ? y : 0; - let nestCount = 0; - let count: string = before ? before : ''; - const fontSize: string = (16 - (nestDepth * 2)) + 'pt'; - const xLoc: number = (nestDepth * 20); - const width: number = 390 - xLoc; - const height: number = 55 - (nestDepth * 5); - Array.from(list).forEach(listItem => { - const mainBullets: number = Number(listItem.getAttribute("data-bulletstyle")); - if (mainBullets === nestDepth) { - if (listItem.childElementCount > 1) { - b++; - nestCount++; - yLoc += height; - count = before ? count + nestCount + "." : nestCount + "."; - const text = listItem.getElementsByTagName("p")[0].innerText; - const length = text.length; - const bullet1 = Docs.Create.TextDocument(count + " " + text, { title: "Slide text", _width: width, _autoHeight: true, x: xLoc, y: (yLoc), _fontSize: fontSize, backgroundColor: "rgba(0,0,0,0)", appearFrame: d ? d : b }); - // yLoc += NumCast(bullet1._height); - mainBulletList.push(bullet1); - const newList = this.recursiveProgressivize(nestDepth + 1, listItem.getElementsByTagName("li"), b, yLoc, count); - mainBulletList.push.apply(mainBulletList, newList[0]); - yLoc += newList.length * (55 - ((nestDepth + 1) * 5)); - } else { - b++; - nestCount++; - yLoc += height; - count = before ? count + nestCount + "." : nestCount + "."; - const text = listItem.innerText; - const length = text.length; - const bullet1 = Docs.Create.TextDocument(count + " " + text, { title: "Slide text", _width: width, _autoHeight: true, x: xLoc, y: (yLoc), _fontSize: fontSize, backgroundColor: "rgba(0,0,0,0)", appearFrame: d ? d : b }); - // yLoc += NumCast(bullet1._height); - mainBulletList.push(bullet1); - } - } - }); - return [mainBulletList, yLoc]; - } - recordDictation = () => { DictationManager.Controls.listen({ interimHandler: this.setDictationContent, @@ -791,8 +643,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } } - _keymap: any = undefined; - _rules: RichTextRules | undefined; @computed get config() { this._keymap = buildKeymap(schema, this.props); this._rules = new RichTextRules(this.props.Document, this); @@ -846,7 +696,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp return this.active();//this.props.isSelected() || this._isChildActive || this.props.renderDepth === 0; } - focusSpeed: Opt<number>; scrollFocus = (doc: Doc, smooth: boolean) => { const anchorId = doc[Id]; const findAnchorFrag = (frag: Fragment, editor: EditorView) => { @@ -878,7 +727,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const ret = findAnchorFrag(editor.state.doc.content, editor); if (ret.frag.size > 2 && ret.start >= 0) { - smooth && (this.focusSpeed = 500); + smooth && (this._focusSpeed = 500); let selection = TextSelection.near(editor.state.doc.resolve(ret.start)); // default to near the start if (ret.frag.firstChild) { selection = TextSelection.between(editor.state.doc.resolve(ret.start), editor.state.doc.resolve(ret.start + ret.frag.firstChild.nodeSize)); // bcz: looks better to not have the target selected @@ -886,23 +735,32 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp editor.dispatch(editor.state.tr.setSelection(new TextSelection(selection.$from, selection.$from)).scrollIntoView()); const escAnchorId = anchorId[0] >= '0' && anchorId[0] <= '9' ? `\\3${anchorId[0]} ${anchorId.substr(1)}` : anchorId; addStyleSheetRule(FormattedTextBox._highlightStyleSheet, `${escAnchorId}`, { background: "yellow" }); - setTimeout(() => this.focusSpeed = undefined, this.focusSpeed); - setTimeout(() => clearStyleSheetRules(FormattedTextBox._highlightStyleSheet), Math.max(this.focusSpeed || 0, 1500)); + setTimeout(() => this._focusSpeed = undefined, this._focusSpeed); + setTimeout(() => clearStyleSheetRules(FormattedTextBox._highlightStyleSheet), Math.max(this._focusSpeed || 0, 1500)); } } - return this.focusSpeed; + return this._focusSpeed; + } + + // if the scroll height has changed and we're in autoHeight mode, then we need to update the textHeight component of the doc. + // Since we also monitor all component height changes, this will update the document's height. + resetNativeHeight = (scrollHeight: number) => { + const nh = this.layoutDoc.isTemplateForField ? 0 : NumCast(this.layoutDoc._nativeHeight); + this.rootDoc[this.fieldKey + "-height"] = scrollHeight + this.titleHeight; + if (nh) this.layoutDoc._nativeHeight = scrollHeight; } componentDidMount() { this.props.setContentView?.(this); // this tells the DocumentView that this AudioBox is the "content" of the document. this allows the DocumentView to indirectly call getAnchor() on the AudioBox when making a link. this.props.contentsActive?.(this.IsActive); this._cachedLinks = DocListCast(this.Document.links); - this._disposers.sidebarheight = reaction( - () => ({ annoHeight: NumCast(this.rootDoc[this.annotationKey + "-height"]), textHeight: NumCast(this.rootDoc[this.fieldKey + "-height"]) }), - ({ annoHeight, textHeight }) => { - this.layoutDoc._autoHeight && (this.rootDoc._height = Math.max(annoHeight, textHeight)); - }); + this._disposers.autoHeight = reaction(() => ({ scrollHeight: this.scrollHeight, autoHeight: this.autoHeight, width: NumCast(this.layoutDoc._width) }), + ({ width, autoHeight, scrollHeight }) => width && autoHeight && this.resetNativeHeight(scrollHeight) + ); + this._disposers.componentHeights = reaction( // set the document height when one of the component heights changes and autoHeight is on + () => ({ sidebarHeight: this.sidebarHeight, textHeight: this.textHeight, autoHeight: this.autoHeight }), + ({ sidebarHeight, textHeight, autoHeight }) => autoHeight && this.props.setHeight(Math.max(sidebarHeight, textHeight))); this._disposers.links = reaction(() => DocListCast(this.Document.links), // if a link is deleted, then remove all hyperlinks that reference it from the text's marks newLinks => { this._cachedLinks.forEach(l => !newLinks.includes(l) && this.RemoveLinkFromDoc(l)); @@ -930,7 +788,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const updatedState = JSON.parse(incomingValue.data.Data); if (JSON.stringify(this._editorView.state.toJSON()) !== JSON.stringify(updatedState)) { this._editorView.updateState(EditorState.fromJSON(this.config, updatedState)); - this.tryUpdateHeight(); + this.tryUpdateScrollHeight(); } } else if (incomingValue?.str) { selectAll(this._editorView.state, tx => this._editorView?.dispatch(tx.insertText(incomingValue.str))); @@ -957,21 +815,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } } ); - this._disposers.autoHeight = reaction( - () => ({ - width: NumCast(this.layoutDoc._width), - autoHeight: this.layoutDoc?._autoHeight - }), - ({ width, autoHeight }) => width !== undefined && setTimeout(() => this.tryUpdateHeight(), 0) - ); - this._disposers.height = reaction( - () => Cast(this.layoutDoc._height, "number", null), - action(height => { - if (height !== undefined && height <= 20 && height < NumCast(this.layoutDoc._delayAutoHeight, 20)) { - this.layoutDoc._delayAutoHeight = height; - } - }) - ); this.setupEditor(this.config, this.props.fieldKey); @@ -1016,8 +859,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp }, { fireImmediately: true } ); quickScroll = undefined; - - setTimeout(() => this.tryUpdateHeight(NumCast(this.layoutDoc.limitHeight))); + this.tryUpdateScrollHeight(); } pushToGoogleDoc = async () => { @@ -1114,10 +956,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp return text; } - sliceSingleNode(slice: Slice) { - return slice.openStart === 0 && slice.openEnd === 0 && slice.content.childCount === 1 ? slice.content.firstChild : null; - } - handlePaste = (view: EditorView, event: Event, slice: Slice): boolean => { const cbe = event as ClipboardEvent; const pdfDocId = cbe.clipboardData?.getData("dash/pdfOrigin"); @@ -1180,7 +1018,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } } - private isActiveTab(el: Element | null | undefined) { + isActiveTab(el: Element | null | undefined) { while (el && el !== document.body) { if (getComputedStyle(el).display === "none") return false; el = el.parentNode as any; @@ -1188,7 +1026,16 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp return true; } - private setupEditor(config: any, fieldKey: string) { + richTextMenuPlugin() { + const self = this; + return new Plugin({ + view(newView) { + runInAction(() => self.props.isSelected(true) && RichTextMenu.Instance && (RichTextMenu.Instance.view = newView)); + return new RichTextMenuPlugin({ editorProps: this.props }); + } + }); + } + setupEditor(config: any, fieldKey: string) { const curText = Cast(this.dataDoc[this.props.fieldKey], RichTextField, null); const rtfField = Cast((!curText?.Text && this.layoutDoc[this.props.fieldKey]) || this.dataDoc[fieldKey], RichTextField); if (this.ProseRef) { @@ -1202,8 +1049,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const scrollRef = self._scrollRef.current; if ((docPos.top < viewRect.top || docPos.top > viewRect.bottom) && scrollRef) { const scrollPos = scrollRef.scrollTop + (docPos.top - viewRect.top) * self.props.ScreenToLocalTransform().Scale; - if (this.focusSpeed !== undefined) { - scrollPos && smoothScroll(this.focusSpeed, scrollRef, scrollPos); + if (this._focusSpeed !== undefined) { + scrollPos && smoothScroll(this._focusSpeed, scrollRef, scrollPos); } else { scrollRef.scrollTo({ top: scrollPos }); } @@ -1215,6 +1062,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp dashComment(node, view, getPos) { return new DashDocCommentView(node, view, getPos); }, dashDoc(node, view, getPos) { return new DashDocView(node, view, getPos, self); }, dashField(node, view, getPos) { return new DashFieldView(node, view, getPos, self); }, + equation(node, view, getPos) { return new EquationView(node, view, getPos, self); }, summary(node, view, getPos) { return new SummaryView(node, view, getPos); }, ordered_list(node, view, getPos) { return new OrderedListView(); }, footnote(node, view, getPos) { return new FootnoteView(node, view, getPos); } @@ -1254,18 +1102,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp this._editorView!.state.storedMarks = [...(this._editorView!.state.storedMarks ?? []), schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) })]; } } - getFont(font: string) { - switch (font) { - case "Arial": return schema.marks.arial.create(); - case "Times New Roman": return schema.marks.timesNewRoman.create(); - case "Georgia": return schema.marks.georgia.create(); - case "Comic Sans MS": return schema.marks.comicSans.create(); - case "Tahoma": return schema.marks.tahoma.create(); - case "Impact": return schema.marks.impact.create(); - case "ACrimson Textrial": return schema.marks.crimson.create(); - } - return schema.marks.arial.create(); - } componentWillUnmount() { Object.values(this._disposers).forEach(disposer => disposer?.()); @@ -1308,7 +1144,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } this._downX = e.clientX; this._downY = e.clientY; - this.doLinkOnDeselect(); this._downEvent = true; FormattedTextBoxComment.textBox = this; if (e.button === 0 && (this.props.rootSelected(true) || this.props.isSelected(true)) && !e.altKey && !e.ctrlKey && !e.metaKey) { @@ -1331,7 +1166,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } onPointerUp = (e: React.PointerEvent): void => { - FormattedTextBox.CanAnnotate = true; if (!this._editorView?.state.selection.empty && FormattedTextBox.CanAnnotate) this.setupAnchorMenu(); if (!this._downEvent) return; this._downEvent = false; @@ -1339,7 +1173,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const editor = this._editorView!; const pcords = editor.posAtCoords({ left: e.clientX, top: e.clientY }); !this.props.isSelected(true) && editor.dispatch(editor.state.tr.setSelection(new TextSelection(editor.state.doc.resolve(pcords?.pos || 0)))); - const target = (e.target as any).parentElement; // hrefs are store don the database of the <a> node that wraps the hyerlink <span> + let target = (e.target as any).parentElement; // hrefs are stored on the database of the <a> node that wraps the hyerlink <span> + while (target && !target.dataset?.targethrefs) target = target.parentElement; FormattedTextBoxComment.update(this, editor, undefined, target?.dataset?.targethrefs); } (e.nativeEvent as any).formattedHandled = true; @@ -1351,7 +1186,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp @action onDoubleClick = (e: React.MouseEvent): void => { - this.doLinkOnDeselect(); FormattedTextBoxComment.textBox = this; if (e.button === 0 && this.props.isSelected(true) && !e.altKey && !e.ctrlKey && !e.metaKey) { if (e.clientX < this.ProseRef!.getBoundingClientRect().right) { // stop propagation if not in sidebar @@ -1373,7 +1207,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp @action onFocused = (e: React.FocusEvent): void => { FormattedTextBox.FocusedBox = this; - this.tryUpdateHeight(); //applyDevTools.applyDevTools(this._editorView); // see if we need to preserve the insertion point @@ -1495,18 +1328,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } } - menuPlugin: any; - - richTextMenuPlugin() { - const self = this; - return new Plugin({ - view(newView) { - runInAction(() => self.props.isSelected(true) && RichTextMenu.Instance && (RichTextMenu.Instance.view = newView)); - return self.menuPlugin = new RichTextMenuPlugin({ editorProps: this.props }); - } - }); - } - public startUndoTypingBatch() { !this._undoTyping && (this._undoTyping = UndoManager.StartBatch("undoTyping")); } @@ -1525,7 +1346,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } FormattedTextBox.HadSelection = window.getSelection()?.toString() !== ""; this.endUndoTypingBatch(); - this.doLinkOnDeselect(); FormattedTextBox.LiveTextUndo?.end(); FormattedTextBox.LiveTextUndo = undefined; @@ -1544,7 +1364,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } } - _lastTimedMark: Mark | undefined = undefined; onKeyDown = (e: React.KeyboardEvent) => { // single line text boxes need to pass through tab/enter/backspace so that their containers can respond (eg, an outline container) if (this.rootDoc._singleLine && ((e.key === "Backspace" && !this.dataDoc[this.fieldKey]?.Text) || ["Tab", "Enter"].includes(e.key))) { @@ -1573,9 +1392,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp RichTextMenu.Instance.updateMenu(undefined, undefined, undefined); } else { if (e.key === "Tab" || e.key === "Enter") { - if (e.key === "Enter") { - this.insertTime(); - } + if (e.key === "Enter") this.insertTime(); e.preventDefault(); } if (e.key === " " || this._lastTimedMark?.attrs.userid !== Doc.CurrentUserEmail) { @@ -1598,99 +1415,73 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } } - get titleHeight() { return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.HeaderMargin) || 0; } - - @action - tryUpdateHeight(limitHeight?: number) { - let scrollHeight = this.ProseRef?.scrollHeight || 0; - if (this.props.renderDepth && this.layoutDoc._autoHeight && !this.props.ignoreAutoHeight && scrollHeight && !this.props.dontRegisterView) { // if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation - scrollHeight = scrollHeight * NumCast(this.layoutDoc._viewScale, 1); - if (limitHeight && scrollHeight > limitHeight) { - scrollHeight = limitHeight; - this.layoutDoc.limitHeight = undefined; - this.layoutDoc._autoHeight = false; - } - const nh = this.layoutDoc.isTemplateForField ? 0 : NumCast(this.layoutDoc._nativeHeight); - const dh = NumCast(this.rootDoc._height); - const newHeight = Math.max(10, (nh ? dh / nh * scrollHeight : scrollHeight) + this.titleHeight); - if (this.rootDoc !== this.layoutDoc.doc && !this.layoutDoc.resolvedDataDoc) { - // if we have a template that hasn't been resolved yet, we can't set the height or we'd be setting it on the unresolved template. So set a timeout and hope its arrived... - console.log("Delayed height adjustment..."); - setTimeout(() => { - this.rootDoc._height = newHeight; - this.layoutDoc._nativeHeight = nh ? scrollHeight : undefined; - }, 10); - } else { - try { - const boxHeight = Number(getComputedStyle(this._boxRef.current!).height.replace("px", "")) * NumCast(this.Document._viewScale, 1); - const outer = this.rootDoc[HeightSym]() - boxHeight - this.titleHeight; - this.rootDoc[this.fieldKey + "-height"] = newHeight + Math.max(0, outer); - } catch (e) { console.log("Error in tryUpdateHeight"); } - } - } //else this.rootDoc[this.fieldKey + "-height"] = 0; + tryUpdateScrollHeight() { + const proseHeight = this.ProseRef?.scrollHeight || 0; + const scrollHeight = this.ProseRef && Math.min(NumCast(this.layoutDoc.docMaxAutoHeight, proseHeight), proseHeight); + if (scrollHeight && this.props.renderDepth && !this.props.dontRegisterView) { // if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation + const setScrollHeight = () => this.rootDoc[this.fieldKey + "-scrollHeight"] = scrollHeight; + if (this.rootDoc === this.layoutDoc.doc || this.layoutDoc.resolvedDataDoc) { + setScrollHeight(); + } else setTimeout(setScrollHeight, 10); // if we have a template that hasn't been resolved yet, we can't set the height or we'd be setting it on the unresolved template. So set a timeout and hope its arrived... + } } + fitToBox = () => this.props.Document._fitToBox; + sidebarContentScaling = () => (this.props.scaling?.() || 1) * NumCast(this.layoutDoc._viewScale, 1); + sidebarAddDocument = (doc: Doc | Doc[]) => this.addDocument(doc, this.SidebarKey); + sidebarMoveDocument = (doc: Doc | Doc[], targetCollection: Doc, addDocument: (doc: Doc | Doc[]) => boolean) => this.moveDocument(doc, targetCollection, addDocument, this.SidebarKey); + sidebarRemDocument = (doc: Doc | Doc[]) => this.removeDocument(doc, this.SidebarKey); + setSidebarHeight = (height: number) => this.rootDoc[this.SidebarKey + "-height"] = height; + sidebarWidth = () => Number(this.sidebarWidthPercent.substring(0, this.sidebarWidthPercent.length - 1)) / 100 * this.props.PanelWidth(); + sidebarScreenToLocal = () => this.props.ScreenToLocalTransform().translate(-(this.props.PanelWidth() - this.sidebarWidth()) / (this.props.scaling?.() || 1), 0).scale(1 / NumCast(this.layoutDoc._viewScale, 1)); + @computed get audioHandle() { - return !this.layoutDoc._showAudio ? (null) : - <div className="formattedTextBox-dictation" onClick={action(e => this._recording = !this._recording)} > - <FontAwesomeIcon className="formattedTextBox-audioFont" style={{ color: this._recording ? "red" : "blue", transitionDelay: "0.6s", opacity: this._recording ? 1 : 0.25, }} icon={"microphone"} size="sm" /> - </div>; + return <div className="formattedTextBox-dictation" onClick={action(e => this._recording = !this._recording)} > + <FontAwesomeIcon className="formattedTextBox-audioFont" style={{ color: this._recording ? "red" : "blue", transitionDelay: "0.6s", opacity: this._recording ? 1 : 0.25, }} icon={"microphone"} size="sm" /> + </div>; } - @computed get sidebarHandle() { const annotated = DocListCast(this.dataDoc[this.SidebarKey]).filter(d => d?.author).length; - return this.props.noSidebar || (!this.props.isSelected() && !(annotated && !this.sidebarWidth())) ? (null) : - <div className="formattedTextBox-sidebar-handle" - style={{ left: `max(0px, calc(100% - ${this.sidebarWidthPercent} ${this.sidebarWidth() ? "- 5px" : "- 10px"}))`, background: annotated ? "lightblue" : this.props.styleProvider?.(this.props.Document, this.props, StyleProp.WidgetColor) }} - onPointerDown={this.sidebarDown} - />; + return <div className="formattedTextBox-sidebar-handle" onPointerDown={this.sidebarDown} + style={{ + left: `max(0px, calc(100% - ${this.sidebarWidthPercent} ${this.sidebarWidth() ? "- 5px" : "- 10px"}))`, + background: this.props.styleProvider?.(this.props.Document, this.props, StyleProp.WidgetColor + (annotated ? ":annotated" : "")) + }} />; } - - sidebarContentScaling = () => (this.props.scaling?.() || 1) * NumCast(this.layoutDoc._viewScale, 1); - fitToBox = () => this.props.Document._fitToBox; - sidebarAddDocument = (doc: Doc | Doc[]) => this.addDocument(doc, this.SidebarKey); - sidebarMoveDocument = (doc: Doc | Doc[], targetCollection: Doc, addDocument: (doc: Doc | Doc[]) => boolean) => this.moveDocument(doc, targetCollection, addDocument, this.SidebarKey); - sidebarRemDocument = (doc: Doc | Doc[]) => this.removeDocument(doc, this.SidebarKey); @computed get sidebarCollection() { - const collectionProps: SubCollectionViewProps & collectionFreeformViewProps = { - ...OmitKeys(this.props, ["NativeWidth", "NativeHeight"]).omit, - NativeWidth: returnZero, - NativeHeight: returnZero, - PanelHeight: this.props.PanelHeight, - PanelWidth: this.sidebarWidth, - xMargin: 0, - yMargin: 0, - chromeStatus: "enabled", - scaleField: this.SidebarKey + "-scale", - isAnnotationOverlay: false, - fieldKey: this.SidebarKey, - fitContentsToDoc: this.fitToBox, - select: emptyFunction, - active: this.annotationsActive, - scaling: this.sidebarContentScaling, - whenActiveChanged: this.whenActiveChanged, - removeDocument: this.sidebarRemDocument, - moveDocument: this.sidebarMoveDocument, - addDocument: this.sidebarAddDocument, - CollectionView: undefined, - ScreenToLocalTransform: this.sidebarScreenToLocal, - renderDepth: this.props.renderDepth + 1, + const renderComponent = (tag: string) => { + const ComponentTag = tag === "freeform" ? CollectionFreeFormView : tag === "translation" ? FormattedTextBox : CollectionStackingView; + return <ComponentTag + {...OmitKeys(this.props, ["NativeWidth", "NativeHeight"]).omit} + NativeWidth={returnZero} + NativeHeight={returnZero} + PanelHeight={this.props.PanelHeight} + PanelWidth={this.sidebarWidth} + xMargin={0} + yMargin={0} + chromeStatus={"enabled"} + scaleField={this.SidebarKey + "-scale"} + isAnnotationOverlay={false} + setHeight={this.setSidebarHeight} + fitContentsToDoc={this.fitToBox} + select={emptyFunction} + active={this.annotationsActive} + scaling={this.sidebarContentScaling} + whenActiveChanged={this.whenActiveChanged} + removeDocument={this.sidebarRemDocument} + moveDocument={this.sidebarMoveDocument} + addDocument={this.sidebarAddDocument} + CollectionView={undefined} + ScreenToLocalTransform={this.sidebarScreenToLocal} + renderDepth={this.props.renderDepth + 1} + noSidebar={true} + fieldKey={this.layoutDoc.sidebarViewType === "translation" ? `${this.fieldKey}-translation` : this.SidebarKey} />; }; - return this.props.noSidebar || !this.layoutDoc._showSidebar || this.sidebarWidthPercent === "0%" ? (null) : - <div className={"formattedTextBox-sidebar" + (Doc.GetSelectedTool() !== InkTool.None ? "-inking" : "")} - style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}> - {this.layoutDoc.sidebarViewType === "translation" ? - <FormattedTextBox {...collectionProps} noSidebar={true} fieldKey={`${this.fieldKey}-translation`} /> : - this.layoutDoc.sidebarViewType === CollectionViewType.Freeform ? - <CollectionFreeFormView {...collectionProps} /> : - <CollectionStackingView {...collectionProps} />} - </div>; + return <div className={"formattedTextBox-sidebar" + (Doc.GetSelectedTool() !== InkTool.None ? "-inking" : "")} + style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}> + {renderComponent(StrCast(this.layoutDoc.sidebarViewType))} + </div>; } - - @computed get sidebarWidthPercent() { return StrCast(this.layoutDoc._sidebarWidthPercent, "0%"); } - sidebarWidth = () => Number(this.sidebarWidthPercent.substring(0, this.sidebarWidthPercent.length - 1)) / 100 * this.props.PanelWidth(); - sidebarScreenToLocal = () => this.props.ScreenToLocalTransform().translate(-(this.props.PanelWidth() - this.sidebarWidth()) / (this.props.scaling?.() || 1), 0).scale(1 / NumCast(this.layoutDoc._viewScale, 1)); - @computed get sidebarColor() { return StrCast(this.layoutDoc.sidebarColor, StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], "#e4e4e4")); } render() { TraceMobx(); const selected = this.props.isSelected(); @@ -1705,19 +1496,19 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const padding = Math.max(margins + ((selected && !this.layoutDoc._singleLine) || minimal ? -selPad : 0), 0); const selPaddingClass = selected && !this.layoutDoc._singleLine && margins >= 10 ? "-selected" : ""; return ( - <div className="formattedTextBox-cont" ref={this._boxRef} + <div className="formattedTextBox-cont" style={{ transform: `scale(${scale})`, transformOrigin: "top left", width: `${100 / scale}%`, height: `${100 / scale}%`, - overflowY: this.layoutDoc._autoHeight ? "hidden" : undefined, + // overflowY: this.layoutDoc._autoHeight ? "hidden" : undefined, ...this.styleFromLayoutString(scale) // this converts any expressions in the format string to style props. e.g., <FormattedTextBox height='{this._headerHeight}px' > }}> <div className={`formattedTextBox-cont`} ref={this._ref} style={{ - overflow: this.layoutDoc._autoHeight ? "hidden" : undefined, - height: this.props.height || (this.layoutDoc._autoHeight && this.props.renderDepth ? "max-content" : undefined), + overflow: this.autoHeight ? "hidden" : undefined, + height: this.props.height || (this.autoHeight && this.props.renderDepth ? "max-content" : undefined), background: this.props.background ? this.props.background : StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor)), color: this.props.color ? this.props.color : StrCast(this.layoutDoc[this.props.fieldKey + "-color"], this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Color)), pointerEvents: interactive ? undefined : "none", @@ -1751,9 +1542,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp }} /> </div> - {this.sidebarCollection} - {this.sidebarHandle} - {this.audioHandle} + {(this.props.noSidebar || this.Document._noSidebar) || !this.layoutDoc._showSidebar || this.sidebarWidthPercent === "0%" ? (null) : this.sidebarCollection} + {(this.props.noSidebar || this.Document._noSidebar) || this.Document._singleLine ? (null) : this.sidebarHandle} + {!this.layoutDoc._showAudio ? (null) : this.audioHandle} </div> </div> ); diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx index 15c669338..6821935a0 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx @@ -85,7 +85,7 @@ export class FormattedTextBoxComment { static update(textBox: FormattedTextBox, view: EditorView, lastState?: EditorState, hrefs: string = "") { FormattedTextBoxComment.textBox = textBox; if ((hrefs || !lastState?.doc.eq(view.state.doc) || !lastState?.selection.eq(view.state.selection))) { - FormattedTextBoxComment.setupPreview(view, textBox, hrefs ? hrefs.trim().split(" ") : undefined); + FormattedTextBoxComment.setupPreview(view, textBox, hrefs?.trim().split(" ")); } } diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts index 243cfc6de..aa51a3a64 100644 --- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts +++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts @@ -144,6 +144,10 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey bind("Alt-\\", setBlockType(schema.nodes.paragraph)); bind("Shift-Ctrl-\\", setBlockType(schema.nodes.code_block)); + bind("Ctrl-m", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => { + dispatch(state.tr.replaceSelectionWith(schema.nodes.equation.create({ fieldKey: "math" + Utils.GenerateGuid() }))); + }) + for (let i = 1; i <= 6; i++) { bind("Shift-Ctrl-" + i, setBlockType(schema.nodes.heading, { level: i })); } @@ -255,36 +259,6 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey // bind("Mod-Enter", cmd); bind("Shift-Enter", cmd); - - bind(":", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => { - const range = state.selection.$from.blockRange(state.selection.$to, (node: any) => { - return !node.marks || !node.marks.find((m: any) => m.type === schema.marks.metadata); - }); - - const path = (state.doc.resolve(state.selection.from - 1) as any).path; - - const spaceSeparator = path[path.length - 3].childCount > 1 ? 0 : -1; - - const anchor = range!.end - path[path.length - 3].lastChild.nodeSize + spaceSeparator; - - if (anchor >= 0) { - - const textsel = TextSelection.create(state.doc, anchor, range!.end); - - const text = range ? state.doc.textBetween(textsel.from, textsel.to) : ""; - - let whitespace = text.length - 1; - - for (; whitespace >= 0 && text[whitespace] !== " "; whitespace--) { } - if (text.endsWith(":")) { - dispatch(state.tr.addMark(textsel.from + whitespace + 1, textsel.to, schema.marks.metadata.create() as any). - addMark(textsel.from + whitespace + 1, textsel.to - 2, schema.marks.metadataKey.create() as any)); - } - } - - return false; - }); - return keys; } diff --git a/src/client/views/nodes/formattedText/nodes_rts.ts b/src/client/views/nodes/formattedText/nodes_rts.ts index 5d9c8b56d..3bd41fa7d 100644 --- a/src/client/views/nodes/formattedText/nodes_rts.ts +++ b/src/client/views/nodes/formattedText/nodes_rts.ts @@ -242,6 +242,19 @@ export const nodes: { [index: string]: NodeSpec } = { } }, + equation: { + inline: true, + attrs: { + fieldKey: { default: "" }, + }, + group: "inline", + draggable: false, + toDOM(node) { + const attrs = { style: `width: ${node.attrs.width}, height: ${node.attrs.height}` }; + return ["div", { ...node.attrs, ...attrs }]; + } + }, + video: { inline: true, attrs: { diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx index d1fdc6c44..e3d14d620 100644 --- a/src/client/views/pdf/AnchorMenu.tsx +++ b/src/client/views/pdf/AnchorMenu.tsx @@ -43,7 +43,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> { @observable public Status: "marquee" | "annotation" | "" = ""; public StartDrag: (e: PointerEvent, ele: HTMLElement) => void = unimplementedFunction; - public Highlight: (color: string) => Opt<Doc> = (color: string) => undefined; + public Highlight: (color: string, isPushpin: boolean) => Opt<Doc> = (color: string, isPushpin: boolean) => undefined; public Delete: () => void = unimplementedFunction; public AddTag: (key: string, value: string) => boolean = returnFalse; public PinToPres: () => void = unimplementedFunction; @@ -76,7 +76,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> { @action highlightClicked = (e: React.MouseEvent) => { - if (!this.Highlight(this.highlightColor) && this.Pinned) { + if (!this.Highlight(this.highlightColor, false) && this.Pinned) { this.Highlighting = !this.Highlighting; } } diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index ca6dc87ae..720d2d92e 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -123,6 +123,12 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu this.props.startupLive && this.setupPdfJsViewer(); this._mainCont.current?.addEventListener("scroll", e => (e.target as any).scrollLeft = 0); + this._disposers.autoHeight = reaction(() => this.layoutDoc._autoHeight, + () => { + this.layoutDoc._nativeHeight = NumCast(this.props.Document[this.fieldKey + "-nativeHeight"]); + this.props.setHeight(NumCast(this.props.Document[this.fieldKey + "-nativeHeight"]) * (this.props.scaling?.() || 1)); + }); + this._disposers.searchMatch = reaction(() => Doc.IsSearchMatch(this.rootDoc), m => { if (m) (this._lastSearch = true) && this.search(Doc.SearchQuery(), m.searchMatch > 0); diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx index 553443931..c82d03fce 100644 --- a/src/client/views/search/SearchBox.tsx +++ b/src/client/views/search/SearchBox.tsx @@ -565,7 +565,7 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc removeDocument={returnFalse} PanelHeight={this.open ? this.returnHeight : returnZero} PanelWidth={this.open ? this.returnLength : returnZero} - overflow={length > window.innerWidth || this.children > 6 ? true : false} + scrollOverflow={length > window.innerWidth || this.children > 6 ? true : false} focus={this.selectElement} ScreenToLocalTransform={Transform.Identity} /> |
