From b8d4b08716791246847d2a647a9df1f37508b87f Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 8 Nov 2022 14:43:47 -0500 Subject: making pasting form pdf work again with backlinks. text fixes for equationViews and dashField views -- trying to get docref linkAnchor to work. --- src/client/views/pdf/PDFViewer.tsx | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'src/client/views/pdf/PDFViewer.tsx') diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index abc7336bd..7f99c30e3 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -131,6 +131,10 @@ export class PDFViewer extends React.Component { copy = (e: ClipboardEvent) => { if (this.props.isContentActive() && e.clipboardData) { e.clipboardData.setData('text/plain', this._selectionText); + const anchor = this._getAnchor(); + if (anchor) { + e.clipboardData.setData('dash/pdfAnchor', anchor[Id]); + } e.preventDefault(); } }; -- cgit v1.2.3-70-g09d2 From 13002bb819e54f3e2f2d25c4b043abf1c15386bb Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 9 Nov 2022 12:22:04 -0500 Subject: fixed treeViews again to make room for hover buttons properly. fixed copying text from pdf to highlight copied regions without always displaying anchor. --- src/client/util/CurrentUserUtils.ts | 8 ++++---- src/client/views/LightboxView.tsx | 2 +- .../views/collections/CollectionTreeView.scss | 3 ++- .../views/collections/CollectionTreeView.tsx | 2 +- src/client/views/collections/TreeView.scss | 3 ++- src/client/views/collections/TreeView.tsx | 24 ++++++++++++++-------- src/client/views/pdf/Annotation.tsx | 2 +- src/client/views/pdf/PDFViewer.tsx | 2 ++ src/fields/util.ts | 1 - 9 files changed, 29 insertions(+), 18 deletions(-) (limited to 'src/client/views/pdf/PDFViewer.tsx') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 114dbac93..f2d851a40 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -214,7 +214,7 @@ export class CurrentUserUtils { title: string, toolTip: string, icon: string, ignoreClick?: boolean, dragFactory?: Doc, backgroundColor?: string, clickFactory?: Doc, scripts?: { onClick?: string, onDragStart?: string}, funcs?: {onDragStart?:string, hidden?: string}, }[] { - const standardOps = (key:string) => ({ title : "Untitled "+ key, _fitWidth: true, system: true, "dragFactory-count": 0, cloneFieldFilter: new List(["system"]) }); + const standardOps = (key:string) => ({ title : "Untitled "+ key, _fitWidth: false, system: true, "dragFactory-count": 0, cloneFieldFilter: new List(["system"]) }); const json = { doc: { type: "doc", @@ -260,7 +260,7 @@ export class CurrentUserUtils { {key: "Note", creator: opts => Docs.Create.TextDocument("", opts), opts: { _width: 200, _autoHeight: true }}, {key: "Noteboard", creator: opts => Docs.Create.NoteTakingDocument([], opts), opts: { _width: 250, _height: 200 }}, {key: "Collection", creator: opts => Docs.Create.FreeformDocument([], opts), opts: { _width: 150, _height: 100 }}, - {key: "Equation", creator: opts => Docs.Create.EquationDocument(opts), opts: { _width: 300, _height: 35, _fitWidth:false, _backgroundGridShow: true, }}, + {key: "Equation", creator: opts => Docs.Create.EquationDocument(opts), opts: { _width: 300, _height: 35, _backgroundGridShow: true, }}, {key: "Webpage", creator: opts => Docs.Create.WebDocument("",opts), opts: { _width: 400, _height: 512, _nativeWidth: 850, useCors: true, }}, {key: "Comparison", creator: Docs.Create.ComparisonDocument, opts: { _width: 300, _height: 300 }}, {key: "Audio", creator: opts => Docs.Create.AudioDocument(nullAudio, opts),opts: { _width: 200, _height: 100, }}, @@ -271,8 +271,8 @@ export class CurrentUserUtils { {key: "Script", creator: opts => Docs.Create.ScriptingDocument(null, opts), opts: { _width: 200, _height: 250, }}, // {key: "DataViz", creator: opts => Docs.Create.DataVizDocument(opts), opts: { _width: 300, _height: 300 }}, {key: "Header", creator: headerTemplate, opts: { _width: 300, _height: 70, _headerPointerEvents: "all", _headerHeight: 12, _headerFontSize: 9, _autoHeight: true,}}, - {key: "Trail", creator: Docs.Create.PresDocument, opts: { _width: 400, _height: 30, _viewType: CollectionViewType.Stacking, targetDropAction: "alias" as any, treeViewHideTitle: true, _chromeHidden: true, boxShadow: "0 0" }}, - {key: "Tab", creator: opts => Docs.Create.FreeformDocument([], opts), opts: { _width: 500, _height: 800, _backgroundGridShow: true, }}, + {key: "Trail", creator: Docs.Create.PresDocument, opts: { _width: 400, _height: 30, _viewType: CollectionViewType.Stacking, targetDropAction: "alias" as any, treeViewHideTitle: true, _fitWidth:true, _chromeHidden: true, boxShadow: "0 0" }}, + {key: "Tab", creator: opts => Docs.Create.FreeformDocument([], opts), opts: { _width: 500, _height: 800, _fitWidth: true, _backgroundGridShow: true, }}, {key: "Slide", creator: opts => Docs.Create.TreeDocument([], opts), opts: { _width: 300, _height: 200, _viewType: CollectionViewType.Tree, treeViewHasOverlay: true, _fontSize: "20px", _autoHeight: true, allowOverlayDrop: true, treeViewType: TreeViewType.outline, diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx index a9fba3688..91773419a 100644 --- a/src/client/views/LightboxView.tsx +++ b/src/client/views/LightboxView.tsx @@ -292,7 +292,7 @@ export class LightboxView extends React.Component { PanelWidth={this.lightboxWidth} PanelHeight={this.lightboxHeight} LayoutTemplate={LightboxView.LightboxDocTemplate} - isDocumentActive={returnTrue} + isDocumentActive={returnTrue} // without this being true, sidebar annotations need to be activated before text can be selected. isContentActive={returnTrue} styleProvider={DefaultStyleProvider} ScreenToLocalTransform={this.lightboxScreenToLocal} diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss index 3785b7d61..273b08247 100644 --- a/src/client/views/collections/CollectionTreeView.scss +++ b/src/client/views/collections/CollectionTreeView.scss @@ -67,7 +67,8 @@ font-style: italic; font-size: 8pt; margin-left: 3px; - display: none; + opacity: 0; + pointer-events: none; } .collectionTreeView-contents { diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 0ff89c5a7..1a265af4a 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -115,7 +115,7 @@ export class CollectionTreeView extends CollectionSubView p + Number(getComputedStyle(r).height.replace('px', '')), this.marginBot()) + 6; this.layoutDoc._autoHeightMargins = bodyHeight; - this.props.setHeight?.(bodyHeight + titleHeight); + !this.props.dontRegisterView && this.props.setHeight?.(bodyHeight + titleHeight); } }; unobserveHeight = (ref: any) => { diff --git a/src/client/views/collections/TreeView.scss b/src/client/views/collections/TreeView.scss index 83fee013a..7eab03e1d 100644 --- a/src/client/views/collections/TreeView.scss +++ b/src/client/views/collections/TreeView.scss @@ -173,7 +173,8 @@ .treeView-header:hover { .collectionTreeView-keyHeader { - display: inherit; + opacity: unset; + pointer-events: unset; } .treeView-openRight { diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index ac8562d5a..13cf64558 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -416,7 +416,6 @@ export class TreeView extends React.Component { })() ); }; - @computed get expandedField() { const ids: { [key: string]: string } = {}; const rows: JSX.Element[] = []; @@ -428,6 +427,8 @@ export class TreeView extends React.Component { const contents = doc[key]; let contentElement: (JSX.Element | null)[] | JSX.Element = []; + let leftOffset = observable({ width: 0 }); + const expandedWidth = () => this.props.panelWidth() - leftOffset.width; if (contents instanceof Doc || (Cast(contents, listSpec(Doc)) && Cast(contents, listSpec(Doc))!.length && Cast(contents, listSpec(Doc))![0] instanceof Doc)) { const remDoc = (doc: Doc | Doc[]) => this.remove(doc, key); const localAdd = (doc: Doc, addBefore?: Doc, before?: boolean) => { @@ -452,7 +453,7 @@ export class TreeView extends React.Component { this.titleStyleProvider, this.props.ScreenToLocalTransform, this.props.isContentActive, - this.props.panelWidth, + expandedWidth, this.props.renderDepth, this.props.treeViewHideHeaderFields, [...this.props.renderedIds, doc[Id]], @@ -483,15 +484,21 @@ export class TreeView extends React.Component { ); } rows.push( -
- {key + ':'} -   +
+ { + if (r) leftOffset.width = r.getBoundingClientRect().width; + })} + style={{ fontWeight: 'bold' }}> + {key + ':'} +   + {contentElement}
); } rows.push( -
+
{ this.treeViewOpen = true; }; + @observable headerEleWidth = 0; @computed get headerElements() { return this.props.treeViewHideHeaderFields() || this.doc.treeViewHideHeaderFields ? null : ( <> @@ -822,7 +830,7 @@ export class TreeView extends React.Component { } return false; }; - titleWidth = () => Math.max(20, Math.min(this.props.treeView.truncateTitleWidth(), this.props.panelWidth())) / (this.props.treeView.props.NativeDimScaling?.() || 1) - 3 * treeBulletWidth(); + titleWidth = () => Math.max(20, Math.min(this.props.treeView.truncateTitleWidth(), this.props.panelWidth())) / (this.props.treeView.props.NativeDimScaling?.() || 1) - this.headerEleWidth - treeBulletWidth(); return18 = () => 18; /** @@ -923,7 +931,7 @@ export class TreeView extends React.Component { }}> {view}
-
+
r && (this.headerEleWidth = r.getBoundingClientRect().width))}> {buttons} {/* hide and lock buttons */} {this.headerElements}
diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx index ee418a02f..9af0949eb 100644 --- a/src/client/views/pdf/Annotation.tsx +++ b/src/client/views/pdf/Annotation.tsx @@ -22,7 +22,7 @@ interface IAnnotationProps extends FieldViewProps { export class Annotation extends React.Component { render() { return ( -
+
{DocListCast(this.props.anno.textInlineAnnotations).map(a => ( ))} diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 7f99c30e3..6ff87ef9f 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -133,6 +133,7 @@ export class PDFViewer extends React.Component { e.clipboardData.setData('text/plain', this._selectionText); const anchor = this._getAnchor(); if (anchor) { + anchor.textCopied = true; e.clipboardData.setData('dash/pdfAnchor', anchor[Id]); } e.preventDefault(); @@ -473,6 +474,7 @@ export class PDFViewer extends React.Component {
{this.inlineTextAnnotations .sort((a, b) => NumCast(a.y) - NumCast(b.y)) + .filter(anno => !anno.hidden) .map(anno => ( ))} diff --git a/src/fields/util.ts b/src/fields/util.ts index 4a62a6a1f..f222e4555 100644 --- a/src/fields/util.ts +++ b/src/fields/util.ts @@ -4,7 +4,6 @@ import { DocServer } from '../client/DocServer'; import { CollectionViewType } from '../client/documents/DocumentTypes'; import { SerializationHelper } from '../client/util/SerializationHelper'; import { UndoManager } from '../client/util/UndoManager'; -import { CollectionDockingView } from '../client/views/collections/CollectionDockingView'; import { returnZero } from '../Utils'; import CursorField from './CursorField'; import { -- cgit v1.2.3-70-g09d2 From ae324ff50865929be836edf3bbf129207638a9c9 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 16 Nov 2022 16:26:31 -0500 Subject: big changes to make link following use the same code as pinning docs for trails. --- src/client/documents/Documents.ts | 1 + src/client/util/DocumentManager.ts | 30 ++--- src/client/util/LinkFollower.ts | 7 +- src/client/views/DocumentButtonBar.tsx | 6 +- src/client/views/GlobalKeyHandler.ts | 4 +- src/client/views/MarqueeAnnotator.tsx | 1 + src/client/views/StyleProvider.tsx | 2 +- .../collections/CollectionStackedTimeline.tsx | 2 +- src/client/views/collections/TabDocView.tsx | 4 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 18 ++- src/client/views/nodes/ComparisonBox.tsx | 2 +- src/client/views/nodes/DocumentView.tsx | 8 +- src/client/views/nodes/ImageBox.tsx | 60 ++++------ src/client/views/nodes/PDFBox.tsx | 16 ++- .../views/nodes/formattedText/FormattedTextBox.tsx | 2 +- src/client/views/nodes/trails/PresBox.tsx | 131 +++++++++++++-------- src/client/views/pdf/Annotation.tsx | 2 +- src/client/views/pdf/PDFViewer.tsx | 11 +- 18 files changed, 178 insertions(+), 129 deletions(-) (limited to 'src/client/views/pdf/PDFViewer.tsx') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 8f45802fe..e68b9e27b 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -237,6 +237,7 @@ export class DocumentOptions { childContextMenuScripts?: List; childContextMenuLabels?: List; childContextMenuIcons?: List; + followLinkZoom?: boolean; // whether to zoom to the target of a link hideLinkButton?: boolean; // whether the blue link counter button should be hidden hideDecorationTitle?: boolean; hideOpenButton?: boolean; diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 9336717c0..a60c1ed6b 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -151,6 +151,21 @@ export class DocumentManager { return toReturn; } + static playAudioAnno(doc: Doc) { + const anno = Cast(doc[Doc.LayoutFieldKey(doc) + '-audioAnnotations'], listSpec(AudioField), null)?.lastElement(); + if (anno) { + if (anno instanceof AudioField) { + new Howl({ + src: [anno.url.href], + format: ['mp3'], + autoplay: true, + loop: false, + volume: 0.5, + }); + } + } + } + static addView = (doc: Doc, finished?: () => void) => { CollectionDockingView.AddSplit(doc, 'right'); finished?.(); @@ -189,20 +204,6 @@ export class DocumentManager { } else { finalTargetDoc.hidden && (finalTargetDoc.hidden = undefined); !noSelect && docView?.select(false); - if (originatingDoc?.followLinkAudio) { - const anno = Cast(finalTargetDoc[Doc.LayoutFieldKey(finalTargetDoc) + '-audioAnnotations'], listSpec(AudioField), null)?.lastElement(); - if (anno) { - if (anno instanceof AudioField) { - new Howl({ - src: [anno.url.href], - format: ['mp3'], - autoplay: true, - loop: false, - volume: 0.5, - }); - } - } - } } finished?.(); }; @@ -233,6 +234,7 @@ export class DocumentManager { } if (focusView) { !noSelect && Doc.linkFollowHighlight(focusView.rootDoc); //TODO:glr make this a setting in PresBox + if (originatingDoc?.followLinkAudio) DocumentManager.playAudioAnno(focusView.rootDoc); const doFocus = (forceDidFocus: boolean) => focusView.focus(originalTarget ?? targetDoc, { originalTarget, diff --git a/src/client/util/LinkFollower.ts b/src/client/util/LinkFollower.ts index c6fc7b372..68716a207 100644 --- a/src/client/util/LinkFollower.ts +++ b/src/client/util/LinkFollower.ts @@ -6,6 +6,7 @@ import { DocumentDecorations } from '../views/DocumentDecorations'; import { LightboxView } from '../views/LightboxView'; import { DocumentViewSharedProps, ViewAdjustment } from '../views/nodes/DocumentView'; import { DocumentManager } from './DocumentManager'; +import { LinkManager } from './LinkManager'; import { UndoManager } from './UndoManager'; type CreateViewFunc = (doc: Doc, followLinkLocation: string, finished?: () => void) => void; @@ -25,7 +26,7 @@ export class LinkFollower { // follows a link - if the target is on screen, it highlights/pans to it. // if the target isn't onscreen, then it will open up the target in the lightbox, or in place // depending on the followLinkLocation property of the source (or the link itself as a fallback); - public static FollowLink = (linkDoc: Opt, sourceDoc: Doc, docViewProps: DocumentViewSharedProps, altKey: boolean, zoom: boolean = false) => { + public static FollowLink = (linkDoc: Opt, sourceDoc: Doc, docViewProps: DocumentViewSharedProps, altKey: boolean) => { const batch = UndoManager.StartBatch('follow link click'); // open up target if it's not already in view ... const createViewFunc = (doc: Doc, followLoc: string, finished?: Opt<() => void>) => { @@ -63,7 +64,6 @@ export class LinkFollower { linkDoc, sourceDoc, createViewFunc, - BoolCast(sourceDoc.followLinkZoom, zoom), docViewProps.ContainingCollectionDoc, action(() => { batch.end(); @@ -73,7 +73,7 @@ export class LinkFollower { ); }; - public static traverseLink(link: Opt, sourceDoc: Doc, createViewFunc: CreateViewFunc, zoom = false, currentContext?: Doc, finished?: () => void, traverseBacklink?: boolean) { + public static traverseLink(link: Opt, sourceDoc: Doc, createViewFunc: CreateViewFunc, currentContext?: Doc, finished?: () => void, traverseBacklink?: boolean) { const linkDocs = link ? [link] : DocListCast(sourceDoc.links); const firstDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor1 as Doc, sourceDoc) || Doc.AreProtosEqual((linkDoc.anchor1 as Doc).annotationOn as Doc, sourceDoc)); // link docs where 'doc' is anchor1 const secondDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor2 as Doc, sourceDoc) || Doc.AreProtosEqual((linkDoc.anchor2 as Doc).annotationOn as Doc, sourceDoc)); // link docs where 'doc' is anchor2 @@ -96,6 +96,7 @@ export class LinkFollower { : linkDoc.anchor1 ) as Doc; if (target) { + const zoom = BoolCast(LinkManager.getOppositeAnchor(linkDoc, target)?.followLinkZoom, false); if (target.TourMap) { const fieldKey = Doc.LayoutFieldKey(target); const tour = DocListCast(target[fieldKey]).reverse(); diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index 794b51cc5..681349ccf 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -284,7 +284,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV .views() .filter(v => v) .map(dv => dv!.rootDoc); - TabDocView.PinDoc(docs, { pinDocLayout, pinDocContent, activeFrame: Cast(docs.lastElement()?.activeFrame, 'number', null) }); + TabDocView.PinDoc(docs, { pinAudioPlay: true, pinDocLayout, pinDocContent, activeFrame: Cast(docs.lastElement()?.activeFrame, 'number', null) }); e.stopPropagation(); }} /> @@ -301,7 +301,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV .views() .filter(v => v) .map(dv => dv!.rootDoc); - TabDocView.PinDoc(docs, { pinDocLayout: e.shiftKey, pinDocContent: e.altKey, activeFrame: Cast(docs.lastElement()?.activeFrame, 'number', null) }); + TabDocView.PinDoc(docs, { pinAudioPlay: true, pinDocLayout: e.shiftKey, pinDocContent: e.altKey, activeFrame: Cast(docs.lastElement()?.activeFrame, 'number', null) }); e.stopPropagation(); }}>
@@ -488,7 +488,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV Doc.noviceMode ? null :
{this.templateButton}
/*
{this.metadataButton}
*/ } - {Doc.noviceMode || !SelectionManager.Views()?.some(v => v.allLinks.length) ? null :
{this.followLinkButton}
} + {!SelectionManager.Views()?.some(v => v.allLinks.length) ? null :
{this.followLinkButton}
}
{this.pinButton}
{this.recordButton}
{!Doc.UserDoc()['documentLinksButton-fullMenu'] ? null :
{this.shareButton}
} diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts index 5a6caf995..4890d9624 100644 --- a/src/client/views/GlobalKeyHandler.ts +++ b/src/client/views/GlobalKeyHandler.ts @@ -6,7 +6,7 @@ import { Id } from '../../fields/FieldSymbols'; import { InkTool } from '../../fields/InkField'; import { List } from '../../fields/List'; import { ScriptField } from '../../fields/ScriptField'; -import { Cast, PromiseValue } from '../../fields/Types'; +import { Cast, DocCast, PromiseValue } from '../../fields/Types'; import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager'; import { DocServer } from '../DocServer'; import { DocumentType } from '../documents/DocumentTypes'; @@ -245,7 +245,7 @@ export class KeyManager { if (SelectionManager.Views().length === 1 && SelectionManager.Views()[0].ComponentView?.search) { SelectionManager.Views()[0].ComponentView?.search?.('', false, false); } else { - const searchBtn = Doc.MySearcher; + const searchBtn = DocListCast(Doc.MyLeftSidebarMenu.data).find(d => d.target === Doc.MySearcher); if (searchBtn) { MainView.Instance.selectMenu(searchBtn); } diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx index c0dd62a05..1162cde50 100644 --- a/src/client/views/MarqueeAnnotator.tsx +++ b/src/client/views/MarqueeAnnotator.tsx @@ -93,6 +93,7 @@ export class MarqueeAnnotator extends React.Component { dragComplete: e => { 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.annoDragData.linkSourceDoc.followLinkZoom = false; } }, }); diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index a04f4a4f4..8ee5ebdb6 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -236,7 +236,7 @@ export function DefaultStyleProvider(doc: Opt, props: Opt 0 ? Doc.UserDoc().activeCollectionNestedBackground : Doc.UserDoc().activeCollectionBackground, 'string') ?? (darkScheme() ? Colors.BLACK : 'linear-gradient(#065fff, #85c1f9)')); diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx index 7bf798656..39ae470b6 100644 --- a/src/client/views/collections/CollectionStackedTimeline.tsx +++ b/src/client/views/collections/CollectionStackedTimeline.tsx @@ -748,7 +748,7 @@ class StackedTimelineAnchor extends React.Component time < NumCast(this.props.mark[this.props.endTag]) && this._lastTimecode < NumCast(this.props.mark[this.props.startTag]) - 1e-5 ) { - LinkFollower.FollowLink(undefined, this.props.mark, this.props as any as DocumentViewProps, false, true); + LinkFollower.FollowLink(undefined, this.props.mark, this.props as any as DocumentViewProps, false); } this._lastTimecode = time; } diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index 1a9006356..e21649648 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -229,7 +229,7 @@ export class TabDocView extends React.Component { * Adds a document to the presentation view **/ @action - public static PinDoc(docs: Doc | Doc[], pinProps?: PinProps) { + public static PinDoc(docs: Doc | Doc[], pinProps: PinProps) { const docList = docs instanceof Doc ? [docs] : docs; const batch = UndoManager.StartBatch('pinning doc'); @@ -271,7 +271,7 @@ export class TabDocView extends React.Component { pinDoc.presStartTime = NumCast(doc.clipStart); pinDoc.presEndTime = NumCast(doc.clipEnd, duration); } - PresBox.pinDocView(pinDoc, pinProps, doc); + PresBox.pinDocView(pinDoc, pinProps.pinDocContent ? { ...pinProps, pinData: PresBox.pinDataTypes(doc) } : pinProps, doc); pinDoc.onClick = ScriptField.MakeFunction('navigateToDoc(self.presentationTargetDoc, self)'); Doc.AddDocToList(curPres, 'data', pinDoc, presSelected); //save position diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 932bd789d..8a97797c7 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1530,11 +1530,27 @@ export class CollectionFreeFormView extends CollectionSubView { + let focusSpeed: Opt; + PresBox.restoreTargetDocView( + this.rootDoc, // + { pinDocLayout: BoolCast(anchor.presPinDocLayout) }, + anchor, + (focusSpeed = !smooth ? 0 : NumCast(anchor.presTransition)), + { + pannable: anchor.presPinData ? true : false, + } + ); + return focusSpeed; + }; // sets viewing information for a componentview, typically when following a link. 'preview' tells the view to use the values without writing to the document + getAnchor = () => { if (this.props.Document.annotationOn) { return this.rootDoc; } - const anchor = Docs.Create.TextanchorDocument({ title: 'ViewSpec - ' + StrCast(this.layoutDoc._viewType), annotationOn: this.rootDoc }); + const anchor = Docs.Create.TextanchorDocument({ title: 'ViewSpec - ' + StrCast(this.layoutDoc._viewType), presTransition: 500, annotationOn: this.rootDoc }); + PresBox.pinDocView(anchor, { pinData: { pannable: true } }, this.rootDoc); const proto = Doc.GetProto(anchor); proto[ViewSpecPrefix + '_viewType'] = this.layoutDoc._viewType; proto.docFilters = ObjectField.MakeCopy(this.layoutDoc.docFilters as ObjectField) || new List([]); diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx index d74da9748..dd03b9b99 100644 --- a/src/client/views/nodes/ComparisonBox.tsx +++ b/src/client/views/nodes/ComparisonBox.tsx @@ -103,7 +103,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent { - whichDoc !== targetDoc && r?.focus(whichDoc, {}); + whichDoc !== targetDoc && r?.focus(whichDoc, { instant: true }); }} {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight']).omit} isContentActive={returnFalse} diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 84cacd919..dc468cf89 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -51,7 +51,7 @@ import { LinkAnchorBox } from './LinkAnchorBox'; import { LinkDocPreview } from './LinkDocPreview'; import { RadialMenu } from './RadialMenu'; import { ScriptingBox } from './ScriptingBox'; -import { PresBox } from './trails/PresBox'; +import { PinProps, PresBox } from './trails/PresBox'; import React = require('react'); const { Howl } = require('howler'); @@ -144,7 +144,7 @@ export interface DocumentViewSharedProps { addDocument?: (doc: Doc | Doc[]) => boolean; removeDocument?: (doc: Doc | Doc[]) => boolean; moveDocument?: (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[]) => boolean) => boolean; - pinToPres: (document: Doc) => void; + pinToPres: (document: Doc, pinProps: PinProps) => void; ScreenToLocalTransform: () => Transform; bringToFront: (doc: Doc, sendToBack?: boolean) => void; canEmbedOnDrag?: boolean; @@ -486,7 +486,7 @@ export class DocumentViewInternal extends DocComponent this.props.addDocTab(this.props.Document, "add:right"), icon: "trash", selected: -1 }); - RadialMenu.Instance.addItem({ description: 'Pin', event: () => this.props.pinToPres(this.props.Document), icon: 'map-pin', selected: -1 }); + RadialMenu.Instance.addItem({ description: 'Pin', event: () => this.props.pinToPres(this.props.Document, {}), icon: 'map-pin', selected: -1 }); RadialMenu.Instance.addItem({ description: 'Open', event: () => MobileInterface.Instance.handleClick(this.props.Document), icon: 'trash', selected: -1 }); SelectionManager.DeselectAll(); @@ -545,7 +545,7 @@ export class DocumentViewInternal extends DocComponent (this.layoutDoc[spec.replace(ViewSpecPrefix, '')] = (field => (field instanceof ObjectField ? ObjectField.MakeCopy(field) : field))(anchor[spec]))); // after a render the general viewSpec should have created the right _componentView, so after a timeout, call the componentview to update its specific view specs setTimeout(() => this._componentView?.setViewSpec?.(anchor, LinkDocPreview.LinkInfo ? true : false)); - const focusSpeed = this._componentView?.scrollFocus?.(anchor, options?.instant === false || !LinkDocPreview.LinkInfo); + const focusSpeed = this._componentView?.scrollFocus?.(anchor, options?.instant !== true && !LinkDocPreview.LinkInfo); const endFocus = focusSpeed === undefined ? options?.afterFocus : async (moved: boolean) => options?.afterFocus?.(true) ?? ViewAdjustment.doNothing; this.props.focus(options?.docTransform ? anchor : this.rootDoc, { ...options, diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 76ba7765c..2e594d96a 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -30,6 +30,8 @@ import { FaceRectangles } from './FaceRectangles'; import { FieldView, FieldViewProps } from './FieldView'; import './ImageBox.scss'; import React = require('react'); +import { PresBox } from './trails'; +import { DocumentViewProps } from './DocumentView'; export const pageSchema = createSchema({ googlePhotosUrl: 'string', @@ -49,23 +51,10 @@ export class ImageBox extends ViewBoxAnnotatableComponent) => Opt = () => undefined; @observable _curSuffix = ''; @observable _uploadIcon = uploadIcons.idle; - @observable _focusViewScale: number | undefined = 1; - @observable _focusPanX: number | undefined = 0; - @observable _focusPanY: number | undefined = 0; - get viewScale() { - return this._focusViewScale || StrCast(this.layoutDoc._viewScale); - } - get panX() { - return this._focusPanX || StrCast(this.layoutDoc._panX); - } - get panY() { - return this._focusPanY || StrCast(this.layoutDoc._panY); - } protected createDropTarget = (ele: HTMLDivElement) => { this._dropDisposer?.(); @@ -73,33 +62,30 @@ export class ImageBox extends ViewBoxAnnotatableComponent { - if (preview && anchor._viewScale !== undefined) { - this._focusViewScale = Cast(anchor._viewScale, 'number', null); - this._focusPanX = Cast(anchor._panX, 'number', null); - this._focusPanY = Cast(anchor._panX, 'number', null); - } else if (anchor._viewScale !== undefined) { - const smoothTime = NumCast(anchor.viewTransitionTime); - this.layoutDoc.viewTransition = `all ${smoothTime}ms`; - this.layoutDoc._panX = NumCast(anchor._panX, NumCast(this.layoutDoc._panY)); - this.layoutDoc._panY = NumCast(anchor._panY, NumCast(this.layoutDoc._panX)); - this.layoutDoc._viewScale = NumCast(anchor._viewScale, NumCast(this.layoutDoc._viewScale)); - this.layoutDoc[this.fieldKey + '-useAlt'] = Cast(anchor._useAlt, 'boolean', null); - if (anchor.type === DocumentType.MARKER) { - this.dataDoc[this.annotationKey] = new List(DocListCast(anchor._annotations)); - } - clearTimeout(this._transitioning); - this._transitioning = setTimeout(() => (this.layoutDoc.viewTransition = undefined), smoothTime); - } + scrollFocus = (anchor: Doc, smooth: boolean) => { + let focusSpeed: Opt; + PresBox.restoreTargetDocView( + this.rootDoc, // + { pinDocLayout: BoolCast(anchor.presPinDocLayout) }, + anchor, + (focusSpeed = !smooth ? 0 : NumCast(anchor.presTransition)), + !anchor.presPinData + ? {} + : { + pannable: true, + dataannos: anchor.presAnnotations !== undefined, + dataview: true, + } + ); + return focusSpeed; }; // sets viewing information for a componentview, typically when following a link. 'preview' tells the view to use the values without writing to the document getAnchor = () => { const anchor = this._getAnchor?.(this._savedAnnotations) ?? // use marquee anchor, otherwise, save zoom/pan as anchor - Docs.Create.ImageanchorDocument({ viewTransitionTime: 1000, unrendered: true, annotationOn: this.rootDoc, _viewScale: NumCast(this.layoutDoc._viewScale, 1), _panX: NumCast(this.layoutDoc._panX), _panY: NumCast(this.layoutDoc._panY) }); + Docs.Create.ImageanchorDocument({ presTransition: 1000, unrendered: true, annotationOn: this.rootDoc }); if (anchor) { - anchor._useAlt = Cast(this.layoutDoc[this.fieldKey + '-useAlt'], 'boolean', null); - anchor._annotations = new List(DocListCast(this.dataDoc[this.annotationKey])); + PresBox.pinDocView(anchor, { pinData: { pannable: true, dataview: true, dataannos: true } }, this.rootDoc); this.addDocument(anchor); return anchor; } @@ -130,7 +116,6 @@ export class ImageBox extends ViewBoxAnnotatableComponent disposer?.()); } @@ -425,6 +410,10 @@ export class ImageBox extends ViewBoxAnnotatableComponent this._savedAnnotations; + styleProvider = (doc: Opt, props: Opt, property: string): any => { + if (property === StyleProp.BoxShadow) return undefined; + return this.props.styleProvider?.(doc, props, property); + }; render() { TraceMobx(); @@ -445,6 +434,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { @@ -207,16 +208,19 @@ export class PDFBox extends ViewBoxAnnotatableComponent { - const anchor = - this._pdfViewer?._getAnchor(this._pdfViewer.savedAnnotations()) ?? - Docs.Create.TextanchorDocument({ + const docAnchor = () => { + const anchor = Docs.Create.TextanchorDocument({ title: StrCast(this.rootDoc.title + '@' + NumCast(this.layoutDoc._scrollTop)?.toFixed(0)), - y: NumCast(this.layoutDoc._scrollTop), unrendered: true, }); + PresBox.pinDocView(anchor, { pinData: { scrollable: true, pannable: true } }, this.rootDoc); + return anchor; + }; + const anchor = this._pdfViewer?._getAnchor(this._pdfViewer.savedAnnotations()) ?? docAnchor(); this.addDocument(anchor); return anchor; }; diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 2984feba5..fdd61463d 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -686,7 +686,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent this.props.pinToPres(anchor); + pinToPres = (anchor: Doc) => this.props.pinToPres(anchor, {}); @undoBatch makePushpin = (anchor: Doc) => (anchor.isPushpin = !anchor.isPushpin); diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index 8d805c663..10f2dc016 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -12,7 +12,8 @@ import { List } from '../../../../fields/List'; import { ObjectField } from '../../../../fields/ObjectField'; import { listSpec } from '../../../../fields/Schema'; import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../../fields/Types'; -import { emptyFunction, returnFalse, returnOne, returnTrue, setupMoveUpEvents, StopEvent } from '../../../../Utils'; +import { AudioField } from '../../../../fields/URLField'; +import { emptyFunction, returnFalse, returnOne, setupMoveUpEvents, StopEvent } from '../../../../Utils'; import { DocServer } from '../../../DocServer'; import { Docs } from '../../../documents/Documents'; import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes'; @@ -33,6 +34,7 @@ import { FieldView, FieldViewProps } from '../FieldView'; import { ScriptingBox } from '../ScriptingBox'; import './PresBox.scss'; import { PresEffect, PresMovement, PresStatus } from './PresEnums'; +const { Howl } = require('howler'); export interface PinProps { audioRange?: boolean; @@ -41,6 +43,17 @@ export interface PinProps { pinViewport?: MarqueeViewBounds; // pin a specific viewport on a freeform view (use MarqueeView.CurViewBounds to compute if no region has been selected) pinDocLayout?: boolean; // pin layout info (width/height/x/y) pinDocContent?: boolean; // pin data info (scroll/pan/zoom/text) + pinAudioPlay?: boolean; // pin audio annotation + pinData?: { + scrollable?: boolean | undefined; + pannable?: boolean | undefined; + temporal?: boolean | undefined; + clippable?: boolean | undefined; + dataview?: boolean | undefined; + textview?: boolean | undefined; + poslayoutview?: boolean | undefined; + dataannos?: boolean | undefined; + }; } @observer @@ -333,26 +346,37 @@ export class PresBox extends ViewBoxBaseComponent() { this.onHideDocument(); //Handles hide after/before } }); - static pinDataTypes(target: Doc) { + static pinDataTypes(target: Doc): { scrollable?: boolean; pannable?: boolean; temporal?: boolean; clippable?: boolean; dataview?: boolean; textview?: boolean; poslayoutview?: boolean; dataannos?: boolean } { const scrollable = [DocumentType.PDF, DocumentType.RTF, DocumentType.WEB].includes(target.type as any) || target._viewType === CollectionViewType.Stacking; - const pannable = [DocumentType.IMG].includes(target.type as any) || (target.type === DocumentType.COL && target._viewType === CollectionViewType.Freeform); + const pannable = [DocumentType.IMG, DocumentType.PDF].includes(target.type as any) || (target.type === DocumentType.COL && target._viewType === CollectionViewType.Freeform); const temporal = [DocumentType.AUDIO, DocumentType.VID].includes(target.type as any); const clippable = [DocumentType.COMPARISON].includes(target.type as any); const dataview = [DocumentType.INK, DocumentType.COL, DocumentType.IMG].includes(target.type as any) && target.activeFrame === undefined; const poslayoutview = [DocumentType.COL].includes(target.type as any) && target.activeFrame === undefined; const textview = [DocumentType.RTF].includes(target.type as any) && target.activeFrame === undefined; - return { scrollable, pannable, temporal, clippable, dataview, textview, poslayoutview }; + const dataannos = false; + return { scrollable, pannable, temporal, clippable, dataview, textview, poslayoutview, dataannos }; } @action - static restoreTargetDocView(bestTarget: Doc, activeItem: Doc) { - const transTime = NumCast(activeItem.presTransition, 500); + playAnnotation = (anno: AudioField) => {}; + @action + static restoreTargetDocView(bestTarget: Doc, pinProps: PinProps | undefined, activeItem: Doc, transTime: number, pinDataTypes = this.pinDataTypes(bestTarget)) { const presTransitionTime = `all ${transTime}ms`; - const { scrollable, pannable, temporal, clippable, dataview, textview, poslayoutview } = this.pinDataTypes(bestTarget); bestTarget._viewTransition = presTransitionTime; - if (clippable) bestTarget._clipWidth = activeItem.presPinClipWidth; - if (temporal) bestTarget._currentTimecode = activeItem.presStartTime; - if (scrollable) { + if (pinProps?.pinDocLayout) { + const transTime = NumCast(activeItem.presTransition, 500); + bestTarget._dataTransition = `all ${transTime}ms`; + bestTarget.x = NumCast(activeItem.presX, NumCast(bestTarget.x)); + bestTarget.y = NumCast(activeItem.presY, NumCast(bestTarget.y)); + bestTarget.rotation = NumCast(activeItem.presRot, NumCast(bestTarget.rotation)); + bestTarget.width = NumCast(activeItem.presWidth, NumCast(bestTarget.width)); + bestTarget.height = NumCast(activeItem.presHeight, NumCast(bestTarget.height)); + setTimeout(() => (bestTarget._dataTransition = undefined), transTime + 10); + } + if (pinDataTypes.clippable) bestTarget._clipWidth = activeItem.presPinClipWidth; + if (pinDataTypes.temporal) bestTarget._currentTimecode = activeItem.presStartTime; + if (pinDataTypes.scrollable) { bestTarget._scrollTop = activeItem.presPinViewScroll; const contentBounds = Cast(activeItem.presPinViewBounds, listSpec('number')); if (contentBounds) { @@ -360,13 +384,17 @@ export class PresBox extends ViewBoxBaseComponent() { dv?.brushView?.({ panX: (contentBounds[0] + contentBounds[2]) / 2, panY: (contentBounds[1] + contentBounds[3]) / 2, width: contentBounds[2] - contentBounds[0], height: contentBounds[3] - contentBounds[1] }); } } - if (dataview && activeItem.presData !== undefined) { + if (pinDataTypes.dataannos) { + const fkey = Doc.LayoutFieldKey(bestTarget); + Doc.GetProto(bestTarget)[fkey + '-annotations'] = new List(DocListCast(activeItem.presAnnotations)); + } + if (pinDataTypes.dataview && activeItem.presData !== undefined) { const fkey = Doc.LayoutFieldKey(bestTarget); Doc.GetProto(bestTarget)[fkey] = activeItem.presData instanceof ObjectField ? activeItem.presData[Copy]() : activeItem.presData; bestTarget[fkey + '-useAlt'] = activeItem.presUseAlt; } - if (textview && activeItem.presData !== undefined) Doc.GetProto(bestTarget)[Doc.LayoutFieldKey(bestTarget)] = activeItem.presData instanceof ObjectField ? activeItem.presData[Copy]() : activeItem.presData; - if (poslayoutview) { + if (pinDataTypes.textview && activeItem.presData !== undefined) Doc.GetProto(bestTarget)[Doc.LayoutFieldKey(bestTarget)] = activeItem.presData instanceof ObjectField ? activeItem.presData[Copy]() : activeItem.presData; + if (pinDataTypes.poslayoutview) { StrListCast(activeItem.presPinLayoutData) .map(str => JSON.parse(str) as { id: string; x: number; y: number; w: number; h: number }) .forEach(data => { @@ -385,7 +413,7 @@ export class PresBox extends ViewBoxBaseComponent() { transTime + 10 ); } - if (pannable) { + if (pinDataTypes.pannable) { const contentBounds = Cast(activeItem.presPinViewBounds, listSpec('number')); if (contentBounds) { const viewport = { panX: (contentBounds[0] + contentBounds[2]) / 2, panY: (contentBounds[1] + contentBounds[3]) / 2, width: contentBounds[2] - contentBounds[0], height: contentBounds[3] - contentBounds[1] }; @@ -410,9 +438,8 @@ export class PresBox extends ViewBoxBaseComponent() { /// reserved fields on the pinDoc so that those values can be restored to the /// target doc when navigating to it. @action - static pinDocView(pinDoc: Doc, pinProps: PinProps | undefined, targetDoc: Doc) { - const { scrollable, pannable, temporal, clippable, dataview, textview, poslayoutview } = this.pinDataTypes(pinDoc); - if (pinProps?.pinDocLayout) { + static pinDocView(pinDoc: Doc, pinProps: PinProps, targetDoc: Doc) { + if (pinProps.pinDocLayout) { pinDoc.presPinLayout = true; pinDoc.presX = NumCast(targetDoc.x); pinDoc.presY = NumCast(targetDoc.y); @@ -420,33 +447,46 @@ export class PresBox extends ViewBoxBaseComponent() { pinDoc.presWidth = NumCast(targetDoc.width); pinDoc.presHeight = NumCast(targetDoc.height); } - if (pinProps?.pinDocContent) { - pinDoc.presPinData = scrollable || temporal || pannable || clippable || dataview || textview || poslayoutview || pinProps.activeFrame !== undefined; - if (dataview) { + if (pinProps.pinAudioPlay) pinDoc.followLinkAudio = true; + if (pinProps.pinData) { + pinDoc.presPinData = + pinProps.pinData.scrollable || + pinProps.pinData.temporal || + pinProps.pinData.pannable || + pinProps.pinData.clippable || + pinProps.pinData.dataview || + pinProps.pinData.textview || + pinProps.pinData.poslayoutview || + pinProps?.activeFrame !== undefined; + if (pinProps.pinData.dataview) { const fkey = Doc.LayoutFieldKey(targetDoc); pinDoc.presUseAlt = targetDoc[fkey + '-useAlt']; pinDoc.presData = targetDoc[fkey] instanceof ObjectField ? (targetDoc[fkey] as ObjectField)[Copy]() : targetDoc.data; } - if (textview) pinDoc.presData = targetDoc[Doc.LayoutFieldKey(targetDoc)] instanceof ObjectField ? (targetDoc[Doc.LayoutFieldKey(targetDoc)] as ObjectField)[Copy]() : targetDoc.text; - if (scrollable) pinDoc.presPinViewScroll = pinDoc._scrollTop; - if (clippable) pinDoc.presPinClipWidth = pinDoc._clipWidth; - if (poslayoutview) pinDoc.presPinLayoutData = new List(DocListCast(pinDoc.presData).map(d => JSON.stringify({ id: d[Id], x: NumCast(d.x), y: NumCast(d.y), w: NumCast(d._width), h: NumCast(d._height) }))); - if (pannable) { - pinDoc.presPinViewX = NumCast(pinDoc._panX); - pinDoc.presPinViewY = NumCast(pinDoc._panY); - pinDoc.presPinViewScale = NumCast(pinDoc._viewScale, 1); + if (pinProps.pinData.dataannos) { + const fkey = Doc.LayoutFieldKey(targetDoc); + pinDoc.presAnnotations = new List(DocListCast(Doc.GetProto(targetDoc)[fkey + '-annotations'])); + } + if (pinProps.pinData.textview) pinDoc.presData = targetDoc[Doc.LayoutFieldKey(targetDoc)] instanceof ObjectField ? (targetDoc[Doc.LayoutFieldKey(targetDoc)] as ObjectField)[Copy]() : targetDoc.text; + if (pinProps.pinData.scrollable) pinDoc.presPinViewScroll = targetDoc._scrollTop; + if (pinProps.pinData.clippable) pinDoc.presPinClipWidth = targetDoc._clipWidth; + if (pinProps.pinData.poslayoutview) pinDoc.presPinLayoutData = new List(DocListCast(targetDoc.presData).map(d => JSON.stringify({ id: d[Id], x: NumCast(d.x), y: NumCast(d.y), w: NumCast(d._width), h: NumCast(d._height) }))); + if (pinProps.pinData.pannable) { + pinDoc.presPinViewX = NumCast(targetDoc._panX); + pinDoc.presPinViewY = NumCast(targetDoc._panY); + pinDoc.presPinViewScale = NumCast(targetDoc._viewScale, 1); } - if (temporal) { - pinDoc.presStartTime = pinDoc._currentTimecode; - const duration = NumCast(pinDoc[`${Doc.LayoutFieldKey(pinDoc)}-duration`], NumCast(pinDoc.presStartTime) + 0.1); - pinDoc.presEndTime = NumCast(pinDoc.clipEnd, duration); + if (pinProps.pinData.temporal) { + pinDoc.presStartTime = targetDoc._currentTimecode; + const duration = NumCast(pinDoc[`${Doc.LayoutFieldKey(pinDoc)}-duration`], NumCast(targetDoc.presStartTime) + 0.1); + pinDoc.presEndTime = NumCast(targetDoc.clipEnd, duration); } } if (pinProps?.pinViewport) { // If pinWithView option set then update scale and x / y props of slide const bounds = pinProps.pinViewport; pinDoc.presPinView = true; - pinDoc.presPinViewScale = NumCast(pinDoc._viewScale, 1); + pinDoc.presPinViewScale = NumCast(targetDoc._viewScale, 1); pinDoc.presPinViewX = bounds.left + bounds.width / 2; pinDoc.presPinViewY = bounds.top + bounds.height / 2; pinDoc.presPinViewBounds = new List([bounds.left, bounds.top, bounds.left + bounds.width, bounds.top + bounds.height]); @@ -507,35 +547,24 @@ export class PresBox extends ViewBoxBaseComponent() { }; static NavigateToTarget(targetDoc: Doc, activeItem: Doc, openInTab: any, srcContext: Doc, finished?: () => void) { - if ((activeItem.presPinLayout || activeItem.presPinView) && DocCast(targetDoc.context)?._currentFrame === undefined) { - const transTime = NumCast(activeItem.presTransition, 500); - targetDoc._dataTransition = `all ${transTime}ms`; - targetDoc.x = NumCast(activeItem.presX, NumCast(targetDoc.x)); - targetDoc.y = NumCast(activeItem.presY, NumCast(targetDoc.y)); - targetDoc.rotation = NumCast(activeItem.presRot, NumCast(targetDoc.rotation)); - targetDoc.width = NumCast(activeItem.presWidth, NumCast(targetDoc.width)); - targetDoc.height = NumCast(activeItem.presHeight, NumCast(targetDoc.height)); - setTimeout(() => (targetDoc._dataTransition = undefined), transTime + 10); - } // If openDocument is selected then it should open the document for the user if (activeItem.openDocument) { LightboxView.SetLightboxDoc(targetDoc); // openInTab(targetDoc); } else if (targetDoc && activeItem.presMovement !== PresMovement.None) { LightboxView.SetLightboxDoc(undefined); const zooming = activeItem.presMovement !== PresMovement.Pan; - DocumentManager.Instance.jumpToDocument(targetDoc, zooming, openInTab, srcContext ? [srcContext] : [], undefined, undefined, undefined, finished, undefined, true, NumCast(activeItem.presZoom, 1)); + DocumentManager.Instance.jumpToDocument(targetDoc, zooming, openInTab, srcContext ? [srcContext] : [], undefined, undefined, activeItem, finished, undefined, true, NumCast(activeItem.presZoom, 1)); } else if (activeItem.presMovement === PresMovement.None && targetDoc.type === DocumentType.SCRIPTING) { (DocumentManager.Instance.getFirstDocumentView(targetDoc)?.ComponentView as ScriptingBox)?.onRun?.(); } // After navigating to the document, if it is added as a presPinView then it will // adjust the pan and scale to that of the pinView when it was added. - if (activeItem.presPinData || activeItem.presPinView) { + const pinDocLayout = (BoolCast(activeItem.presPinLayout) || BoolCast(activeItem.presPinView)) && DocCast(targetDoc.context)?._currentFrame === undefined; + if (activeItem.presPinData || activeItem.presPinView || pinDocLayout) { clearTimeout(PresBox._navTimer); // targetDoc may or may not be displayed. this gets the first available document (or alias) view that matches targetDoc const bestTargetView = DocumentManager.Instance.getFirstDocumentView(targetDoc); - const bestTarget = bestTargetView?.props.Document; - if (bestTarget) PresBox._navTimer = PresBox.restoreTargetDocView(bestTarget, activeItem); - activeItem.presPinAudioPlay && bestTargetView?.docView?.playAnnotation(); + if (bestTargetView?.props.Document) PresBox._navTimer = PresBox.restoreTargetDocView(bestTargetView?.props.Document, { pinDocLayout }, activeItem, NumCast(activeItem.presTransition, 500)); } } @@ -747,7 +776,7 @@ export class PresBox extends ViewBoxBaseComponent() { } else { if (!doc.aliasOf) { const original = Doc.MakeAlias(doc); - TabDocView.PinDoc(original); + TabDocView.PinDoc(original, {}); setTimeout(() => this.removeDocument(doc), 0); return false; } else { @@ -1316,7 +1345,7 @@ export class PresBox extends ViewBoxBaseComponent() { Effects
Play Audio Annotation
- (activeItem.presPinAudioPlay = !BoolCast(activeItem.presPinAudioPlay))} checked={BoolCast(activeItem.presPinAudioPlay)} /> + (activeItem.followLinkAudio = !BoolCast(activeItem.followLinkAudio))} checked={BoolCast(activeItem.followLinkAudio)} />
() { const presData = Cast(this.rootDoc.data, listSpec(Doc)); if (data && presData) { data.push(doc); - TabDocView.PinDoc(doc); + TabDocView.PinDoc(doc, {}); this.gotoDocument(this.childDocs.length, this.activeItem); } else { this.props.addDocTab(doc, 'add:right'); diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx index 9af0949eb..133b882f6 100644 --- a/src/client/views/pdf/Annotation.tsx +++ b/src/client/views/pdf/Annotation.tsx @@ -52,7 +52,7 @@ class RegionAnnotation extends React.Component { }; @undoBatch - pinToPres = () => this.props.pinToPres(this.annoTextRegion); + pinToPres = () => this.props.pinToPres(this.annoTextRegion, {}); @undoBatch makePushpin = () => (this.annoTextRegion.isPushpin = !this.annoTextRegion.isPushpin); diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 6ff87ef9f..5a5c63c3d 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -166,12 +166,12 @@ export class PDFViewer extends React.Component { // scrolls to focus on a nested annotation document. if this is part a link preview then it will jump to the scroll location, // otherwise it will scroll smoothly. - scrollFocus = (doc: Doc, smooth: boolean) => { + scrollFocus = (doc: Doc, scrollTop: number, smooth: boolean) => { const mainCont = this._mainCont.current; let focusSpeed: Opt; if (doc !== this.props.rootDoc && mainCont) { const windowHeight = this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1); - const scrollTo = doc.unrendered ? NumCast(doc.y) : Utils.scrollIntoView(NumCast(doc.y), doc[HeightSym](), NumCast(this.props.layoutDoc._scrollTop), windowHeight, 0.1 * windowHeight, NumCast(this.props.Document.scrollHeight)); + const scrollTo = doc.unrendered ? scrollTop : Utils.scrollIntoView(scrollTop, doc[HeightSym](), NumCast(this.props.layoutDoc._scrollTop), windowHeight, 0.1 * windowHeight, NumCast(this.props.Document.scrollHeight)); if (scrollTo !== undefined && scrollTo !== this.props.layoutDoc._scrollTop) { if (!this._pdfViewer) this._initialScroll = scrollTo; else if (smooth) smoothScroll((focusSpeed = NumCast(doc.focusSpeed, 500)), mainCont, scrollTo); @@ -203,6 +203,11 @@ export class PDFViewer extends React.Component { } document.removeEventListener('pagesinit', this.pagesinit); var quickScroll: string | undefined = this._initialScroll ? this._initialScroll.toString() : ''; + this._disposers.scale = reaction( + () => NumCast(this.props.layoutDoc._viewScale, 1), + scale => (this._pdfViewer.currentScaleValue = scale), + { fireImmediately: true } + ); this._disposers.scroll = reaction( () => Math.abs(NumCast(this.props.Document._scrollTop)), pos => { @@ -290,7 +295,7 @@ export class PDFViewer extends React.Component { @action scrollToAnnotation = (scrollToAnnotation: Doc) => { if (scrollToAnnotation) { - this.scrollFocus(scrollToAnnotation, true); + this.scrollFocus(scrollToAnnotation, NumCast(scrollToAnnotation.y), true); Doc.linkFollowHighlight(scrollToAnnotation); } }; -- cgit v1.2.3-70-g09d2 From 66184a172006de4d4bf72d9da33858e04d298181 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 1 Dec 2022 10:13:03 -0500 Subject: refactored process of following links / jumping to docs and added following options for zoomTime, etc instead of setting temporary fields on docs. --- src/client/documents/Documents.ts | 8 +- src/client/util/DocumentManager.ts | 98 +++--- .../util/Import & Export/DirectoryImportBox.tsx | 334 +++++++++++---------- src/client/util/LinkFollower.ts | 84 +++--- src/client/util/SharingManager.tsx | 2 +- src/client/views/DocComponent.tsx | 2 +- src/client/views/InkingStroke.tsx | 6 +- src/client/views/MainView.tsx | 2 +- src/client/views/MarqueeAnnotator.tsx | 2 +- src/client/views/PropertiesView.tsx | 28 +- .../views/collections/CollectionNoteTakingView.tsx | 5 +- .../views/collections/CollectionStackingView.tsx | 5 +- src/client/views/collections/TabDocView.tsx | 4 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 8 +- .../collections/collectionFreeForm/MarqueeView.tsx | 2 +- .../collectionLinear/CollectionLinearView.tsx | 2 +- .../collectionSchema/CollectionSchemaCells.tsx | 2 +- src/client/views/nodes/DocumentView.tsx | 26 +- src/client/views/nodes/ImageBox.tsx | 8 +- src/client/views/nodes/PDFBox.tsx | 13 +- src/client/views/nodes/VideoBox.tsx | 6 +- src/client/views/nodes/WebBox.tsx | 10 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 12 +- src/client/views/nodes/trails/PresBox.tsx | 24 +- src/client/views/pdf/Annotation.tsx | 4 +- src/client/views/pdf/PDFViewer.tsx | 8 +- src/client/views/search/SearchBox.tsx | 2 +- 27 files changed, 354 insertions(+), 353 deletions(-) (limited to 'src/client/views/pdf/PDFViewer.tsx') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index eed839520..d13d96dd3 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -157,7 +157,7 @@ export class DocumentOptions { _contentBounds?: List; // the (forced) bounds of the document to display. format is: [left, top, right, bottom] _lockedPosition?: boolean; // lock the x,y coordinates of the document so that it can't be dragged _lockedTransform?: boolean; // lock the panx,pany and scale parameters of the document so that it be panned/zoomed - _isPushpin?: boolean; // whether document, when clicked, toggles display of its link target + _followLinkToggle?: boolean; // whether document, when clicked, toggles display of its link target _showTitle?: string; // field name to display in header (:hover is an optional suffix) _showCaption?: string; // which field to display in the caption area. leave empty to have no caption _scrollTop?: number; // scroll location for pdfs @@ -272,7 +272,7 @@ export class DocumentOptions { clipWidth?: number; // percent transition from before to after in comparisonBox dockingConfig?: string; annotationOn?: Doc; - isPushpin?: boolean; + followLinkToggle?: boolean; isGroup?: boolean; // whether a collection should use a grouping UI behavior _removeDropProperties?: List; // list of properties that should be removed from a document when it is dropped. e.g., a creator button may be forceActive to allow it be dragged, but the forceActive property can be removed from the dropped document noteType?: string; @@ -1703,7 +1703,7 @@ export namespace DocUtils { } export function LeavePushpin(doc: Doc, annotationField: string) { - if (doc.isPushpin) return undefined; + if (doc.followLinkToggle) return undefined; const context = Cast(doc.context, Doc, null) ?? Cast(doc.annotationOn, Doc, null); 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)) { @@ -1711,7 +1711,7 @@ export namespace DocUtils { title: 'pushpin', label: '', annotationOn: Cast(doc.annotationOn, Doc, null), - isPushpin: true, + followLinkToggle: true, icon: 'map-pin', x: Cast(doc.x, 'number', null), y: Cast(doc.y, 'number', null), diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 4f02a8202..1b63b615b 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -5,7 +5,7 @@ import { Cast, DocCast } from '../../fields/Types'; import { returnFalse } from '../../Utils'; import { DocumentType } from '../documents/DocumentTypes'; import { LightboxView } from '../views/LightboxView'; -import { DocumentView, OpenWhereMod, ViewAdjustment } from '../views/nodes/DocumentView'; +import { DocFocusOptions, DocumentView, OpenWhereMod, ViewAdjustment } from '../views/nodes/DocumentView'; import { LinkAnchorBox } from '../views/nodes/LinkAnchorBox'; import { CollectionDockingView } from '../views/collections/CollectionDockingView'; import { CollectionFreeFormView } from '../views/collections/collectionFreeForm'; @@ -171,19 +171,13 @@ export class DocumentManager { }; public jumpToDocument = ( targetDoc: Doc, // document to display - willZoom: boolean, // whether to zoom doc to take up most of screen + options: DocFocusOptions, // options for how to navigate to target createViewFunc = DocumentManager.addView, // how to create a view of the doc if it doesn't exist docContext: Doc[], // context to load that should contain the target - linkDoc?: Doc, // link that's being followed - closeContextIfNotFound: boolean = false, // after opening a context where the document should be, this determines whether the context should be closed if the Doc isn't actually there - originatingDoc: Opt = undefined, // doc that initiated the display of the target odoc - finished?: () => void, - originalTarget?: Doc, - noSelect?: boolean, - presZoomScale?: number + finished?: () => void ): void => { - originalTarget = originalTarget ?? targetDoc; - const docView = this.getFirstDocumentView(targetDoc, originatingDoc); + const originalTarget = options.originalTarget ?? targetDoc; + const docView = this.getFirstDocumentView(targetDoc, options.originatingDoc); const annotatedDoc = Cast(targetDoc.annotationOn, Doc, null); const resolvedTarget = targetDoc.type === DocumentType.MARKER ? annotatedDoc ?? docView?.rootDoc ?? targetDoc : docView?.rootDoc ?? targetDoc; // if target is a marker, then focus toggling should apply to the document it's on since the marker itself doesn't have a hidden field var wasHidden = resolvedTarget.hidden; @@ -195,14 +189,14 @@ export class DocumentManager { } const focusAndFinish = (didFocus: boolean) => { const finalTargetDoc = resolvedTarget; - if (originatingDoc?.isPushpin) { + if (options.toggleTarget) { if (!didFocus && !wasHidden) { // don't toggle the hidden state if the doc was already un-hidden as part of this document traversal finalTargetDoc.hidden = !finalTargetDoc.hidden; } } else { finalTargetDoc.hidden && (finalTargetDoc.hidden = undefined); - !noSelect && docView?.select(false); + !options.noSelect && docView?.select(false); } finished?.(); }; @@ -216,9 +210,8 @@ export class DocumentManager { if (annoContainerView.props.Document.layoutKey === 'layout_icon') { annoContainerView.iconify(() => annoContainerView.focus(targetDoc, { + ...options, originalTarget, - willZoom, - scale: presZoomScale, afterFocus: (didFocus: boolean) => new Promise(res => { focusAndFinish(true); @@ -232,13 +225,12 @@ export class DocumentManager { } } if (focusView) { - !noSelect && Doc.linkFollowHighlight(focusView.rootDoc, undefined, targetDoc); //TODO:glr make this a setting in PresBox - if (originatingDoc?.followLinkAudio) DocumentManager.playAudioAnno(focusView.rootDoc); + !options.noSelect && Doc.linkFollowHighlight(focusView.rootDoc, undefined, targetDoc); //TODO:glr make this a setting in PresBox + if (options.playAudio) DocumentManager.playAudioAnno(focusView.rootDoc); const doFocus = (forceDidFocus: boolean) => - focusView.focus(originalTarget ?? targetDoc, { + focusView.focus(originalTarget, { + ...options, originalTarget, - willZoom, - scale: presZoomScale, afterFocus: (didFocus: boolean) => new Promise(res => { focusAndFinish(forceDidFocus || didFocus); @@ -262,13 +254,12 @@ export class DocumentManager { targetDocContextView.rootDoc.hidden = false; // make sure context isn't hidden targetDocContext._viewTransition = 'transform 500ms'; targetDocContextView.props.focus(targetDocContextView.rootDoc, { - willZoom, + ...options, + // originalTarget, // needed? afterFocus: async () => { targetDocContext._viewTransition = undefined; if (targetDocContext.layoutKey === 'layout_icon') { - targetDocContextView.iconify(() => - this.jumpToDocument(resolvedTarget ?? targetDoc, willZoom, createViewFunc, docContext, linkDoc, closeContextIfNotFound, originatingDoc, finished, originalTarget, noSelect, presZoomScale) - ); + targetDocContextView.iconify(() => this.jumpToDocument(resolvedTarget ?? targetDoc, { ...options /* originalTarget - needed?*/ }, createViewFunc, docContext, finished)); } return ViewAdjustment.doNothing; }, @@ -281,56 +272,35 @@ export class DocumentManager { finished?.(); } else { // no timecode means we need to find the context view and focus on our target - const findView = (delay: number) => { - const retryDocView = this.getFirstDocumentView(resolvedTarget); // test again for the target view snce we presumably created the context above by focusing on it - if (retryDocView) { - // we found the target in the context. - Doc.linkFollowHighlight(retryDocView.rootDoc); - retryDocView.focus(targetDoc, { - willZoom, - afterFocus: (didFocus: boolean) => - new Promise(res => { - !noSelect && focusAndFinish(true); - res(ViewAdjustment.doNothing); - }), - }); // focus on the target in the context - } else if (delay > 1000) { - // we didn't find the target, so it must have moved out of the context. Go back to just creating it. - if (closeContextIfNotFound) targetDocContextView.props.removeDocument?.(targetDocContextView.rootDoc); - if (targetDoc.layout) { - // there will no layout for a TEXTANCHOR type document - createViewFunc(Doc.BrushDoc(targetDoc), finished); // create a new view of the target - } - } else { - setTimeout(() => findView(delay + 200), 200); - } - }; - setTimeout(() => findView(0), 0); + const retryDocView = this.getFirstDocumentView(resolvedTarget); // test again for the target view snce we presumably created the context above by focusing on it + if (retryDocView) { + // we found the target in the context. + Doc.linkFollowHighlight(retryDocView.rootDoc); + retryDocView.focus(targetDoc, { + ...options, + // originalTarget -- needed? + afterFocus: (didFocus: boolean) => + new Promise(res => { + !options.noSelect && focusAndFinish(true); + res(ViewAdjustment.doNothing); + }), + }); // focus on the target in the context + } else if (targetDoc.layout) { + // there will no layout for a TEXTANCHOR type document + createViewFunc(Doc.BrushDoc(targetDoc), finished); // create a new view of the target + } } } else { if (docContext.length && docContext[0]?.layoutKey === 'layout_icon') { const docContextView = this.getFirstDocumentView(docContext[0]); if (docContextView) { - return docContextView.iconify(() => - this.jumpToDocument(targetDoc, willZoom, createViewFunc, docContext.slice(1, docContext.length), linkDoc, closeContextIfNotFound, originatingDoc, finished, originalTarget, noSelect, presZoomScale) - ); + return docContextView.iconify(() => this.jumpToDocument(targetDoc, { ...options, originalTarget }, createViewFunc, docContext.slice(1, docContext.length), finished)); } } // there's no context view so we need to create one first and try again when that finishes createViewFunc( targetDocContext, // after creating the context, this calls the finish function that will retry looking for the target - () => - this.jumpToDocument( - targetDoc, - willZoom, - (doc: Doc, finished?: () => void) => doc !== targetDocContext && createViewFunc(doc, finished), - docContext, - linkDoc, - true /* if target not found, get rid of context just created */, - originatingDoc, - finished, - originalTarget - ) + () => this.jumpToDocument(targetDoc, { ...options, originalTarget }, (doc: Doc, finished?: () => void) => doc !== targetDocContext && createViewFunc(doc, finished), docContext, finished) ); } } diff --git a/src/client/util/Import & Export/DirectoryImportBox.tsx b/src/client/util/Import & Export/DirectoryImportBox.tsx index 37571ae01..916eee4b7 100644 --- a/src/client/util/Import & Export/DirectoryImportBox.tsx +++ b/src/client/util/Import & Export/DirectoryImportBox.tsx @@ -1,27 +1,27 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { BatchedArray } from "array-batcher"; -import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx"; -import { observer } from "mobx-react"; -import { extname } from "path"; -import Measure, { ContentRect } from "react-measure"; -import { Doc, DocListCast, DocListCastAsync, Opt } from "../../../fields/Doc"; -import { Id } from "../../../fields/FieldSymbols"; -import { List } from "../../../fields/List"; -import { listSpec } from "../../../fields/Schema"; -import { SchemaHeaderField } from "../../../fields/SchemaHeaderField"; -import { BoolCast, Cast, NumCast } from "../../../fields/Types"; -import { AcceptableMedia, Upload } from "../../../server/SharedMediaTypes"; -import { Utils } from "../../../Utils"; -import { GooglePhotos } from "../../apis/google_docs/GooglePhotosClientUtils"; -import { Docs, DocumentOptions, DocUtils } from "../../documents/Documents"; -import { Networking } from "../../Network"; -import { FieldView, FieldViewProps } from "../../views/nodes/FieldView"; -import { DocumentManager } from "../DocumentManager"; -import "./DirectoryImportBox.scss"; -import ImportMetadataEntry, { keyPlaceholder, valuePlaceholder } from "./ImportMetadataEntry"; -import React = require("react"); +import { BatchedArray } from 'array-batcher'; +import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx'; +import { observer } from 'mobx-react'; +import { extname } from 'path'; +import Measure, { ContentRect } from 'react-measure'; +import { Doc, DocListCast, DocListCastAsync, Opt } from '../../../fields/Doc'; +import { Id } from '../../../fields/FieldSymbols'; +import { List } from '../../../fields/List'; +import { listSpec } from '../../../fields/Schema'; +import { SchemaHeaderField } from '../../../fields/SchemaHeaderField'; +import { BoolCast, Cast, NumCast } from '../../../fields/Types'; +import { AcceptableMedia, Upload } from '../../../server/SharedMediaTypes'; +import { Utils } from '../../../Utils'; +import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils'; +import { Docs, DocumentOptions, DocUtils } from '../../documents/Documents'; +import { Networking } from '../../Network'; +import { FieldView, FieldViewProps } from '../../views/nodes/FieldView'; +import { DocumentManager } from '../DocumentManager'; +import './DirectoryImportBox.scss'; +import ImportMetadataEntry, { keyPlaceholder, valuePlaceholder } from './ImportMetadataEntry'; +import React = require('react'); -const unsupported = ["text/html", "text/plain"]; +const unsupported = ['text/html', 'text/plain']; @observer export class DirectoryImportBox extends React.Component { @@ -29,7 +29,7 @@ export class DirectoryImportBox extends React.Component { @observable private top = 0; @observable private left = 0; private dimensions = 50; - @observable private phase = ""; + @observable private phase = ''; private disposer: Opt; @observable private entries: ImportMetadataEntry[] = []; @@ -40,7 +40,9 @@ export class DirectoryImportBox extends React.Component { @observable private uploading = false; @observable private removeHover = false; - public static LayoutString(fieldKey: string) { return FieldView.LayoutString(DirectoryImportBox, fieldKey); } + public static LayoutString(fieldKey: string) { + return FieldView.LayoutString(DirectoryImportBox, fieldKey); + } constructor(props: FieldViewProps) { super(props); @@ -71,7 +73,7 @@ export class DirectoryImportBox extends React.Component { handleSelection = async (e: React.ChangeEvent) => { runInAction(() => { this.uploading = true; - this.phase = "Initializing download..."; + this.phase = 'Initializing download...'; }); const docs: Doc[] = []; @@ -79,7 +81,7 @@ export class DirectoryImportBox extends React.Component { const files = e.target.files; if (!files || files.length === 0) return; - const directory = (files.item(0) as any).webkitRelativePath.split("/", 1)[0]; + const directory = (files.item(0) as any).webkitRelativePath.split('/', 1)[0]; const validated: File[] = []; for (let i = 0; i < files.length; i++) { @@ -100,7 +102,7 @@ export class DirectoryImportBox extends React.Component { const sizes: number[] = []; const modifiedDates: number[] = []; - runInAction(() => this.phase = `Internal: uploading ${this.quota - this.completed} files to Dash...`); + runInAction(() => (this.phase = `Internal: uploading ${this.quota - this.completed} files to Dash...`)); const batched = BatchedArray.from(validated, { batchSize: 15 }); const uploads = await batched.batchedMapAsync>(async (batch, collector) => { @@ -109,23 +111,28 @@ export class DirectoryImportBox extends React.Component { modifiedDates.push(file.lastModified); }); collector.push(...(await Networking.UploadFilesToServer(batch))); - runInAction(() => this.completed += batch.length); + runInAction(() => (this.completed += batch.length)); }); - await Promise.all(uploads.map(async response => { - const { source: { type }, result } = response; - if (result instanceof Error) { - return; - } - const { accessPaths, exifData } = result; - const path = Utils.prepend(accessPaths.agnostic.client); - const document = type && await DocUtils.DocumentFromType(type, path, { _width: 300 }); - const { data, error } = exifData; - if (document) { - Doc.GetProto(document).exif = error || Doc.Get.FromJson({ data }); - docs.push(document); - } - })); + await Promise.all( + uploads.map(async response => { + const { + source: { type }, + result, + } = response; + if (result instanceof Error) { + return; + } + const { accessPaths, exifData } = result; + const path = Utils.prepend(accessPaths.agnostic.client); + const document = type && (await DocUtils.DocumentFromType(type, path, { _width: 300 })); + const { data, error } = exifData; + if (document) { + Doc.GetProto(document).exif = error || Doc.Get.FromJson({ data }); + docs.push(document); + } + }) + ); for (let i = 0; i < docs.length; i++) { const doc = docs[i]; @@ -146,7 +153,7 @@ export class DirectoryImportBox extends React.Component { _height: 500, _chromeHidden: true, x: NumCast(doc.x), - y: NumCast(doc.y) + offset + y: NumCast(doc.y) + offset, }; const parent = this.props.ContainingCollectionView; if (parent) { @@ -154,14 +161,14 @@ export class DirectoryImportBox extends React.Component { if (docs.length < 50) { importContainer = Docs.Create.MasonryDocument(docs, options); } else { - const headers = [new SchemaHeaderField("title"), new SchemaHeaderField("size")]; + const headers = [new SchemaHeaderField('title'), new SchemaHeaderField('size')]; importContainer = Docs.Create.SchemaDocument(headers, docs, options); } - runInAction(() => this.phase = 'External: uploading files to Google Photos...'); + runInAction(() => (this.phase = 'External: uploading files to Google Photos...')); await GooglePhotos.Export.CollectionToAlbum({ collection: importContainer }); - Doc.AddDocToList(Doc.GetProto(parent.props.Document), "data", importContainer); + Doc.AddDocToList(Doc.GetProto(parent.props.Document), 'data', importContainer); !this.persistent && this.props.removeDocument && this.props.removeDocument(doc); - DocumentManager.Instance.jumpToDocument(importContainer, true, undefined, []); + DocumentManager.Instance.jumpToDocument(importContainer, { willZoom: true }, undefined, []); } runInAction(() => { @@ -169,14 +176,14 @@ export class DirectoryImportBox extends React.Component { this.quota = 1; this.completed = 0; }); - } + }; componentDidMount() { - this.selector.current!.setAttribute("directory", ""); - this.selector.current!.setAttribute("webkitdirectory", ""); + this.selector.current!.setAttribute('directory', ''); + this.selector.current!.setAttribute('webkitdirectory', ''); this.disposer = reaction( () => this.completed, - completed => runInAction(() => this.phase = `Internal: uploading ${this.quota - completed} files to Dash...`) + completed => runInAction(() => (this.phase = `Internal: uploading ${this.quota - completed} files to Dash...`)) ); } @@ -193,7 +200,7 @@ export class DirectoryImportBox extends React.Component { const offset = this.dimensions / 2; this.left = bounds.width / 2 - offset; this.top = bounds.height / 2 - offset; - } + }; @action addMetadataEntry = async () => { @@ -201,8 +208,8 @@ export class DirectoryImportBox extends React.Component { entryDoc.checked = false; entryDoc.key = keyPlaceholder; entryDoc.value = valuePlaceholder; - Doc.AddDocToList(this.props.Document, "data", entryDoc); - } + Doc.AddDocToList(this.props.Document, 'data', entryDoc); + }; @action remove = async (entry: ImportMetadataEntry) => { @@ -217,7 +224,7 @@ export class DirectoryImportBox extends React.Component { } } } - } + }; render() { const dimensions = 50; @@ -228,193 +235,204 @@ export class DirectoryImportBox extends React.Component { const uploading = this.uploading; const showRemoveLabel = this.removeHover; const persistent = this.persistent; - let percent = `${completed / quota * 100}`; - percent = percent.split(".")[0]; - percent = percent.startsWith("100") ? "99" : percent; + let percent = `${(completed / quota) * 100}`; + percent = percent.split('.')[0]; + percent = percent.startsWith('100') ? '99' : percent; const marginOffset = (percent.length === 1 ? 5 : 0) - 1.6; - const message = {this.phase}; - const centerPiece = this.phase.includes("Google Photos") ? - - :
{this.phase}; + const centerPiece = this.phase.includes('Google Photos') ? ( + + ) : ( +
{percent}%
; + color: 'white', + marginLeft: this.left + marginOffset, + }}> + {percent}% +
+ ); return ( - {({ measureRef }) => -
+ {({ measureRef }) => ( +
{message} + position: 'absolute', + display: 'none', + }} + />
}> + , diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx index 7069ff399..d8e44ae9d 100644 --- a/src/client/views/pdf/Annotation.tsx +++ b/src/client/views/pdf/Annotation.tsx @@ -55,9 +55,9 @@ class RegionAnnotation extends React.Component { pinToPres = () => this.props.pinToPres(this.annoTextRegion, {}); @undoBatch - makePushpin = () => (this.annoTextRegion.followLinkToggle = !this.annoTextRegion.followLinkToggle); + makeTargretToggle = () => (this.annoTextRegion.followLinkToggle = !this.annoTextRegion.followLinkToggle); - isPushpin = () => BoolCast(this.annoTextRegion.followLinkToggle); + isTargetToggler = () => BoolCast(this.annoTextRegion.followLinkToggle); @action onPointerDown = (e: React.PointerEvent) => { @@ -67,8 +67,8 @@ class RegionAnnotation extends React.Component { AnchorMenu.Instance.Pinned = false; AnchorMenu.Instance.AddTag = this.addTag.bind(this); AnchorMenu.Instance.PinToPres = this.pinToPres; - AnchorMenu.Instance.MakePushpin = this.makePushpin; - AnchorMenu.Instance.IsPushpin = this.isPushpin; + AnchorMenu.Instance.MakeTargetToggle = this.makeTargretToggle; + AnchorMenu.Instance.IsTargetToggler = this.isTargetToggler; AnchorMenu.Instance.jumpTo(e.clientX, e.clientY, true); e.stopPropagation(); } else if (e.button === 0) { diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index f95d5ac2e..b0b7816b8 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -135,7 +135,7 @@ export class PDFViewer extends React.Component { copy = (e: ClipboardEvent) => { if (this.props.isContentActive() && e.clipboardData) { e.clipboardData.setData('text/plain', this._selectionText); - const anchor = this._getAnchor(); + const anchor = this._getAnchor(undefined, false); if (anchor) { anchor.textCopied = true; e.clipboardData.setData('dash/pdfAnchor', anchor[Id]); @@ -317,7 +317,7 @@ export class PDFViewer extends React.Component { this._ignoreScroll = false; if (this._scrollTimer) clearTimeout(this._scrollTimer); // wait until a scrolling pause, then create an anchor to audio this._scrollTimer = setTimeout(() => { - DocUtils.MakeLinkToActiveAudio(() => this.props.DocumentView?.().ComponentView?.getAnchor!()!, false); + DocUtils.MakeLinkToActiveAudio(() => this.props.DocumentView?.().ComponentView?.getAnchor!(true)!, false); this._scrollTimer = undefined; }, 200); } -- cgit v1.2.3-70-g09d2 From e17b1bdb09bfcadc717e687b09d2c18596341a10 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 9 Feb 2023 21:15:58 -0500 Subject: fixed childLayoutString to work. made images capable of fitWidth. fixed animating data field pres changes. fixed lightbox to ignore annotations on collections. fixed double-click on icon to open in lightbox. added options for turning off ink labels, and opening ink in lightbox. fixed closing ink strokes by dragging. fixed drawing ink to use coord sys of starting point and to render ink the correct width and to honor GestureOverlay mode properly. . --- src/client/documents/Documents.ts | 4 +- src/client/util/CurrentUserUtils.ts | 6 +- src/client/util/SettingsManager.tsx | 4 ++ src/client/views/DocumentDecorations.tsx | 13 ++-- src/client/views/GestureOverlay.tsx | 22 ++++--- src/client/views/GlobalKeyHandler.ts | 29 ++++++++- src/client/views/InkTranscription.tsx | 2 +- src/client/views/InkingStroke.tsx | 12 ++-- src/client/views/LightboxView.tsx | 64 +++++++++---------- src/client/views/PropertiesButtons.tsx | 10 +++ src/client/views/PropertiesView.tsx | 2 +- src/client/views/StyleProvider.tsx | 4 +- src/client/views/collections/CollectionView.tsx | 2 +- src/client/views/collections/TabDocView.tsx | 14 ++--- src/client/views/collections/TreeView.tsx | 4 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 66 +++++++++++-------- .../collections/collectionFreeForm/MarqueeView.tsx | 4 +- src/client/views/nodes/DocumentView.tsx | 21 ++++--- src/client/views/nodes/ImageBox.tsx | 73 +++++++++++++++++----- src/client/views/nodes/LinkAnchorBox.tsx | 1 + src/client/views/nodes/MapBox/MapBox.tsx | 4 +- src/client/views/nodes/WebBox.tsx | 11 ++-- .../views/nodes/formattedText/FormattedTextBox.tsx | 1 - src/client/views/nodes/trails/PresBox.tsx | 4 ++ src/client/views/pdf/AnchorMenu.tsx | 1 + src/client/views/pdf/PDFViewer.tsx | 17 ++--- src/fields/util.ts | 2 +- 27 files changed, 257 insertions(+), 140 deletions(-) (limited to 'src/client/views/pdf/PDFViewer.tsx') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 692d09629..3faa6e11d 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -149,6 +149,8 @@ export class DocumentOptions { _height?: NUMt = new NumInfo('displayed height of document'); _nativeWidth?: NUMt = new NumInfo('native width of document contents (e.g., the pixel width of an image)'); _nativeHeight?: NUMt = new NumInfo('native height of document contents (e.g., the pixel height of an image)'); + _nativeDimModifiable?: BOOLt = new BoolInfo('native dimensions can be modified using document decoration reizers'); + _nativeHeightUnfrozen?: BOOLt = new BoolInfo('native height can be changed independent of width by dragging decoration resizers'); _dimMagnitude?: NUMt = new NumInfo("magnitude of collectionMulti{row,col} element's width or height"); _dimUnit?: DIMt = new DimInfo("units of collectionMulti{row,col} element's width or height - 'px' or '*' for pixels or relative units"); _fitWidth?: BOOLt = new BoolInfo('whether document should scale its contents to fit its rendered width or not (e.g., for PDFviews)'); @@ -848,7 +850,7 @@ export namespace Docs { export function ImageDocument(url: string | ImageField, options: DocumentOptions = {}, overwriteDoc?: Doc) { const imgField = url instanceof ImageField ? url : new ImageField(url); - return InstanceFromProto(Prototypes.get(DocumentType.IMG), imgField, { title: basename(imgField.url.href), ...options }, undefined, undefined, undefined, overwriteDoc); + return InstanceFromProto(Prototypes.get(DocumentType.IMG), imgField, { _nativeDimModifiable: false, _nativeHeightUnfrozen: false, title: basename(imgField.url.href), ...options }, undefined, undefined, undefined, overwriteDoc); } export function PresDocument(options: DocumentOptions = {}) { diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 5f183cf91..f678c8936 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -183,7 +183,7 @@ export class CurrentUserUtils { const allopts = {system: true, ...opts}; return DocUtils.AssignScripts( (curIcon?.iconTemplate === opts.iconTemplate ? DocUtils.AssignOpts(curIcon, allopts):undefined) ?? ((templateIconsDoc[iconFieldName] = MakeTemplate(creator(allopts), true, iconFieldName, templateField))), - {onClick:"deiconifyView(documentView)"}); + {onClick:"deiconifyView(documentView)", onDoubleClick: "deiconifyViewToLightbox(documentView"}); }; const labelBox = (opts: DocumentOptions, data?:string) => Docs.Create.LabelDocument({ textTransform: "unset", letterSpacing: "unset", _singleLine: false, _minFontSize: 14, _maxFontSize: 24, borderRounding: "5px", _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, ...opts @@ -803,7 +803,9 @@ export class CurrentUserUtils { doc._showLabel ?? (doc._showLabel = true); doc.textAlign ?? (doc.textAlign = "left"); doc.activeTool = InkTool.None; - doc.activeInkColor ?? (doc.activeInkColor = "rgb(0, 0, 0)");; + doc.openInkInLightbox ?? (doc.openInkInLightbox = false); + doc.activeInkHideTextLabels ?? (doc.activeInkHideTextLabels = false); + doc.activeInkColor ?? (doc.activeInkColor = "rgb(0, 0, 0)"); doc.activeInkWidth ?? (doc.activeInkWidth = 1); doc.activeInkBezier ?? (doc.activeInkBezier = "0"); doc.activeFillColor ?? (doc.activeFillColor = ""); diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx index 179a1ac39..6c823e80a 100644 --- a/src/client/util/SettingsManager.tsx +++ b/src/client/util/SettingsManager.tsx @@ -199,6 +199,10 @@ export class SettingsManager extends React.Component<{}> { (Doc.UserDoc().activeInkHideTextLabels = !Doc.UserDoc().activeInkHideTextLabels)} checked={BoolCast(Doc.UserDoc().activeInkHideTextLabels)} />
Hide Labels In Ink Shapes
+
+ (Doc.UserDoc().openInkInLightbox = !Doc.UserDoc().openInkInLightbox)} checked={BoolCast(Doc.UserDoc().openInkInLightbox)} /> +
Open Ink Docs in Lightbox
+
); } diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index d1f0bf2ac..41f4a17fb 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -581,7 +581,8 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P } let actualdW = Math.max(width + dW * scale, 20); let actualdH = Math.max(height + dH * scale, 20); - const fixedAspect = nwidth && nheight && (!doc._fitWidth || e.ctrlKey || doc.nativeHeightUnfrozen || doc.nativeDimModifiable); + const preserveNativeDim = doc._nativeHeightUnfrozen === false && doc._nativeDimModifiable === false; + const fixedAspect = nwidth && nheight && (!doc._fitWidth || preserveNativeDim || e.ctrlKey || doc.nativeHeightUnfrozen || doc.nativeDimModifiable); if (fixedAspect) { if ((Math.abs(dW) > Math.abs(dH) && ((!dragBottom && !dragTop) || !modifyNativeDim)) || dragRight) { if (dragRight && modifyNativeDim) { @@ -589,7 +590,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P doc._nativeWidth = (actualdW / (doc._width || 1)) * Doc.NativeWidth(doc); } } else { - if (!doc._fitWidth) { + if (!doc._fitWidth || preserveNativeDim) { actualdH = (nheight / nwidth) * actualdW; doc._height = actualdH; } else if (!modifyNativeDim || dragBotRight) doc._height = actualdH; @@ -597,11 +598,13 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P doc._width = actualdW; } else { if ((dragBottom || dragTop) && (modifyNativeDim || (docView.layoutDoc.nativeHeightUnfrozen && docView.layoutDoc._fitWidth))) { - // frozen web pages, PDFs, and some RTFS have frozen nativewidth/height. But they are marked to allow their nativeHeight to be explicitly modified with fitWidth and vertical resizing. (ie, with fitWidth they can't grow horizontally to match a vertical resize so it makes more sense to change their nativeheight even if the ctrl key isn't used) + // frozen web pages, PDFs, and some RTFS have frozen nativewidth/height. But they are marked to allow their nativeHeight + // to be explicitly modified with fitWidth and vertical resizing. (ie, with fitWidth they can't grow horizontally to match + // a vertical resize so it makes more sense to change their nativeheight even if the ctrl key isn't used) doc._nativeHeight = (actualdH / (doc._height || 1)) * Doc.NativeHeight(doc); doc._autoHeight = false; } else { - if (!doc._fitWidth) { + if (!doc._fitWidth || preserveNativeDim) { actualdW = (nwidth / nheight) * actualdH; doc._width = actualdW; } else if (!modifyNativeDim || dragBotRight) doc._width = actualdW; @@ -615,7 +618,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P const rotCtr = [NumCast(doc._width) / 2, NumCast(doc._height) / 2]; const tlRotated = Utils.rotPt(-rotCtr[0], -rotCtr[1], (NumCast(doc._rotation) / 180) * Math.PI); - const maxHeight = doc.nativHeightUnfrozen || !nheight ? 0 : Math.max(nheight, NumCast(doc.scrollHeight, NumCast(doc[docView.LayoutFieldKey + '-scrollHeight']))) * docView.NativeDimScaling(); + const maxHeight = doc.nativeHeightUnfrozen || !nheight ? 0 : Math.max(nheight, NumCast(doc.scrollHeight, NumCast(doc[docView.LayoutFieldKey + '-scrollHeight']))) * docView.NativeDimScaling(); dH && (doc._height = actualdH > maxHeight && maxHeight ? maxHeight : actualdH); dW && (doc._width = actualdW); dH && (doc._autoHeight = false); diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index e3328fb4c..6058eaaf9 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -1,9 +1,9 @@ import React = require('react'); import * as fitCurve from 'fit-curve'; -import { action, computed, observable, runInAction, trace } from 'mobx'; +import { action, computed, observable, runInAction } from 'mobx'; +import { observer } from 'mobx-react'; import { Doc, Opt } from '../../fields/Doc'; import { InkData, InkTool } from '../../fields/InkField'; -import { List } from '../../fields/List'; import { ScriptField } from '../../fields/ScriptField'; import { Cast, FieldValue, NumCast } from '../../fields/Types'; import MobileInkOverlay from '../../mobile/MobileInkOverlay'; @@ -14,7 +14,6 @@ import { CognitiveServices } from '../cognitive_services/CognitiveServices'; import { Docs, DocUtils } from '../documents/Documents'; import { InteractionUtils } from '../util/InteractionUtils'; import { ScriptingGlobals } from '../util/ScriptingGlobals'; -import { SelectionManager } from '../util/SelectionManager'; import { Transform } from '../util/Transform'; import './GestureOverlay.scss'; import { @@ -39,7 +38,6 @@ import { RadialMenu } from './nodes/RadialMenu'; import HorizontalPalette from './Palette'; import { Touchable } from './Touchable'; import TouchScrollableMenu, { TouchScrollableMenuItem } from './TouchScrollableMenu'; -import { observer } from 'mobx-react'; interface GestureOverlayProps { isActive: boolean; @@ -47,6 +45,7 @@ interface GestureOverlayProps { @observer export class GestureOverlay extends Touchable { static Instance: GestureOverlay; + static Instances: GestureOverlay[] = []; @observable public InkShape: Opt; @observable public SavedColor?: string; @@ -66,6 +65,8 @@ export class GestureOverlay extends Touchable { @observable private _clipboardDoc?: JSX.Element; @observable private _possibilities: JSX.Element[] = []; + public static DownDocView: DocumentView | undefined; + @computed private get height(): number { return 2 * Math.max(this._pointerY && this._thumbY ? this._thumbY - this._pointerY : 100, 100); } @@ -89,7 +90,7 @@ export class GestureOverlay extends Touchable { constructor(props: any) { super(props); - GestureOverlay.Instance = this; + GestureOverlay.Instances.push(this); } static setupThumbButtons(doc: Doc) { @@ -154,7 +155,13 @@ export class GestureOverlay extends Touchable { } return Cast(userDoc.thumbDoc, Doc); } + + componentWillUnmount() { + GestureOverlay.Instances.splice(GestureOverlay.Instances.indexOf(this), 1); + GestureOverlay.Instance = GestureOverlay.Instances.lastElement(); + } componentDidMount = () => { + GestureOverlay.Instance = this; this._thumbDoc = FieldValue(Cast(GestureOverlay.setupThumbDoc(Doc.UserDoc()), Doc)); this._inkToTextDoc = FieldValue(Cast(this._thumbDoc?.inkToTextDoc, Doc)); }; @@ -627,6 +634,7 @@ export class GestureOverlay extends Touchable { } @action onPointerUp = (e: PointerEvent) => { + GestureOverlay.DownDocView = undefined; if (this._points.length > 1) { const B = this.svgBounds; const points = this._points.map(p => ({ X: p.X - B.left, Y: p.Y - B.top })); @@ -906,8 +914,8 @@ export class GestureOverlay extends Touchable { } @computed get elements() { - const selView = SelectionManager.Views().lastElement(); - const width = (Number(ActiveInkWidth()) * NumCast(selView?.rootDoc._viewScale, 1)) / (selView?.props.ScreenToLocalTransform().Scale || 1); + const selView = GestureOverlay.DownDocView; + const width = Number(ActiveInkWidth()) * NumCast(selView?.rootDoc._viewScale, 1); // * (selView?.props.ScreenToLocalTransform().Scale || 1); const rect = this._overlayRef.current?.getBoundingClientRect(); const B = { left: -20000, right: 20000, top: -20000, bottom: 20000, width: 40000, height: 40000 }; //this.getBounds(this._points, true); B.left = B.left - width / 2; diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts index 5e700e281..6c8a078ec 100644 --- a/src/client/views/GlobalKeyHandler.ts +++ b/src/client/views/GlobalKeyHandler.ts @@ -1,12 +1,12 @@ import { random } from 'lodash'; -import { action, observable, runInAction } from 'mobx'; +import { action, runInAction } from 'mobx'; import { DateField } from '../../fields/DateField'; import { Doc, DocListCast } from '../../fields/Doc'; import { Id } from '../../fields/FieldSymbols'; import { InkTool } from '../../fields/InkField'; import { List } from '../../fields/List'; import { ScriptField } from '../../fields/ScriptField'; -import { Cast, DocCast, PromiseValue } from '../../fields/Types'; +import { Cast, PromiseValue } from '../../fields/Types'; import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager'; import { DocServer } from '../DocServer'; import { DocumentType } from '../documents/DocumentTypes'; @@ -18,6 +18,7 @@ import { SharingManager } from '../util/SharingManager'; import { SnappingManager } from '../util/SnappingManager'; import { undoBatch, UndoManager } from '../util/UndoManager'; import { CollectionDockingView } from './collections/CollectionDockingView'; +import { CollectionFreeFormView } from './collections/collectionFreeForm'; import { CollectionFreeFormViewChrome } from './collections/CollectionMenu'; import { CollectionStackedTimeline } from './collections/CollectionStackedTimeline'; import { ContextMenu } from './ContextMenu'; @@ -103,6 +104,20 @@ export class KeyManager { const groupings = SelectionManager.Views().slice(); const randomGroup = random(0, 1000); + const collectionView = groupings.reduce( + (col, g) => (col === null || g.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView === col ? g.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView : undefined), + null as null | undefined | CollectionFreeFormView + ); + if (collectionView) { + UndoManager.RunInBatch(() => { + collectionView._marqueeViewRef.current?.collection( + e, + true, + groupings.map(g => g.rootDoc) + ); + }, 'grouping'); + break; + } UndoManager.RunInBatch(() => groupings.map(dv => (dv.layoutDoc.group = randomGroup)), 'group'); SelectionManager.DeselectAll(); break; @@ -192,6 +207,16 @@ export class KeyManager { case 'arrowdown': UndoManager.RunInBatch(() => SelectionManager.Views().map(dv => dv.props.CollectionFreeFormDocumentView?.().nudge?.(0, 10)), 'nudge down'); break; + case 'g': + if (document.activeElement?.tagName === 'INPUT' || document.activeElement?.tagName === 'TEXTAREA') { + return { stopPropagation: false, preventDefault: false }; + } + + const groupings = SelectionManager.Views().slice(); + const randomGroup = random(0, 1000); + UndoManager.RunInBatch(() => groupings.map(dv => (dv.layoutDoc.group = randomGroup)), 'group'); + SelectionManager.DeselectAll(); + break; } return { diff --git a/src/client/views/InkTranscription.tsx b/src/client/views/InkTranscription.tsx index bf0e8081d..246b887a6 100644 --- a/src/client/views/InkTranscription.tsx +++ b/src/client/views/InkTranscription.tsx @@ -67,7 +67,7 @@ export class InkTranscription extends React.Component { : null; } - r.addEventListener('exported', (e: any) => this.exportInk(e, this._mathRef)); + r?.addEventListener('exported', (e: any) => this.exportInk(e, this._mathRef)); return (this._mathRef = r); }; diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index 7a5151634..d7e8b1c05 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -54,7 +54,7 @@ export class InkingStroke extends ViewBoxBaseComponent() { return FieldView.LayoutString(InkingStroke, fieldStr); } public static IsClosed(inkData: InkData) { - return inkData && inkData.lastElement().X === inkData[0].X && inkData.lastElement().Y === inkData[0].Y; + return inkData?.length && inkData.lastElement().X === inkData[0].X && inkData.lastElement().Y === inkData[0].Y; } private _handledClick = false; // flag denoting whether ink stroke has handled a psuedo-click onPointerUp so that the real onClick event can be stopPropagated private _disposers: { [key: string]: IReactionDisposer } = {}; @@ -264,9 +264,13 @@ export class InkingStroke extends ViewBoxBaseComponent() { .map(p => ({ X: p[0], Y: p[1] })); const { distance, nearestT, nearestSeg, nearestPt } = InkStrokeProperties.nearestPtToStroke(screenPts, { X: e.clientX, Y: e.clientY }); - this._nearestT = nearestT; - this._nearestSeg = nearestSeg; - this._nearestScrPt = nearestPt; + if (distance < 40) { + this._nearestT = nearestT; + this._nearestSeg = nearestSeg; + this._nearestScrPt = nearestPt; + } else { + this._nearestT = this._nearestSeg = this._nearestScrPt = undefined; + } }; /** diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx index 3627aa783..e531bf71c 100644 --- a/src/client/views/LightboxView.tsx +++ b/src/client/views/LightboxView.tsx @@ -27,6 +27,13 @@ interface LightboxViewProps { maxBorder: number[]; } +type LightboxSavedState = { + panX: Opt; + panY: Opt; + scale: Opt; + scrollTop: Opt; + layoutKey: Opt; +}; @observer export class LightboxView extends React.Component { @computed public static get LightboxDoc() { @@ -34,21 +41,22 @@ export class LightboxView extends React.Component { } private static LightboxDocTemplate = () => LightboxView._layoutTemplate; @observable private static _layoutTemplate: Opt; + @observable private static _layoutTemplateString: Opt; @observable private static _doc: Opt; @observable private static _docTarget: Opt; @observable private static _docFilters: string[] = []; // filters - private static _savedState: Opt<{ panX: Opt; panY: Opt; scale: Opt; scrollTop: Opt }>; + private static _savedState: Opt; private static _history: Opt<{ doc: Doc; target?: Doc }[]> = []; @observable private static _future: Opt = []; @observable private static _docView: Opt; - private static openInTabFunc: any; - static path: { doc: Opt; target: Opt; history: Opt<{ doc: Doc; target?: Doc }[]>; future: Opt; saved: Opt<{ panX: Opt; panY: Opt; scale: Opt; scrollTop: Opt }> }[] = []; - @action public static SetLightboxDoc(doc: Opt, target?: Doc, future?: Doc[], layoutTemplate?: Doc) { + static path: { doc: Opt; target: Opt; history: Opt<{ doc: Doc; target?: Doc }[]>; future: Opt; saved: Opt }[] = []; + @action public static SetLightboxDoc(doc: Opt, target?: Doc, future?: Doc[], layoutTemplate?: Doc | string) { if (this.LightboxDoc && this.LightboxDoc !== doc && this._savedState) { - this.LightboxDoc._panX = this._savedState.panX; - this.LightboxDoc._panY = this._savedState.panY; - this.LightboxDoc._scrollTop = this._savedState.scrollTop; - this.LightboxDoc._viewScale = this._savedState.scale; + if (this._savedState.panX !== undefined) this.LightboxDoc._panX = this._savedState.panX; + if (this._savedState.panY !== undefined) this.LightboxDoc._panY = this._savedState.panY; + if (this._savedState.scrollTop !== undefined) this.LightboxDoc._scrollTop = this._savedState.scrollTop; + if (this._savedState.scale !== undefined) this.LightboxDoc._viewScale = this._savedState.scale; + this.LightboxDoc.layoutKey = this._savedState.layoutKey; } if (!doc) { this._docFilters && (this._docFilters.length = 0); @@ -69,10 +77,11 @@ export class LightboxView extends React.Component { this._history ? this._history.push({ doc, target }) : (this._history = [{ doc, target }]); if (doc !== LightboxView.LightboxDoc) { this._savedState = { - panX: Cast(doc._panX, 'number', null), - panY: Cast(doc._panY, 'number', null), - scale: Cast(doc._viewScale, 'number', null), - scrollTop: Cast(doc._scrollTop, 'number', null), + layoutKey: StrCast(doc.layoutKey), + panX: Cast(doc.panX, 'number', null), + panY: Cast(doc.panY, 'number', null), + scale: Cast(doc.viewScale, 'number', null), + scrollTop: Cast(doc.scrollTop, 'number', null), }; } } @@ -87,7 +96,10 @@ export class LightboxView extends React.Component { ]; } this._doc = doc; - this._layoutTemplate = layoutTemplate; + this._layoutTemplate = layoutTemplate instanceof Doc ? layoutTemplate : undefined; + if (doc && (typeof layoutTemplate === 'string' ? layoutTemplate : undefined)) { + doc.layoutKey = layoutTemplate; + } this._docTarget = target || doc; return true; @@ -132,7 +144,7 @@ export class LightboxView extends React.Component { this._docFilters = (f => (this._docFilters ? [this._docFilters.push(f) as any, this._docFilters][1] : [f]))(`cookies:${cookie}:provide`); } } - public static AddDocTab = (doc: Doc, location: OpenWhere, layoutTemplate?: Doc, openInTabFunc?: any) => { + public static AddDocTab = (doc: Doc, location: OpenWhere, layoutTemplate?: Doc | string) => { if (location !== OpenWhere.lightbox) { const inPlaceView = DocCast(doc.context) ? DocumentManager.Instance.getFirstDocumentView(DocCast(doc.context)) : undefined; if (inPlaceView) { @@ -140,12 +152,13 @@ export class LightboxView extends React.Component { return true; } } - LightboxView.openInTabFunc = openInTabFunc; SelectionManager.DeselectAll(); return LightboxView.SetLightboxDoc( doc, undefined, - [...DocListCast(doc[Doc.LayoutFieldKey(doc)]), ...DocListCast(doc[Doc.LayoutFieldKey(doc) + '-annotations']), ...(LightboxView._future ?? [])].sort((a: Doc, b: Doc) => NumCast(b._timecodeToShow) - NumCast(a._timecodeToShow)), + [...DocListCast(doc[Doc.LayoutFieldKey(doc)]), ...DocListCast(doc[Doc.LayoutFieldKey(doc) + '-annotations']).filter(anno => anno.annotationOn !== doc), ...(LightboxView._future ?? [])].sort( + (a: Doc, b: Doc) => NumCast(b._timecodeToShow) - NumCast(a._timecodeToShow) + ), layoutTemplate ); }; @@ -193,8 +206,7 @@ export class LightboxView extends React.Component { const docView = DocumentManager.Instance.getLightboxDocumentView(target || doc); if (docView) { LightboxView._docTarget = target; - if (!target) docView.ComponentView?.shrinkWrap?.(); - else docView.focus(target, { willPanZoom: true, zoomScale: 0.9 }); + target && docView.focus(target, { willPanZoom: true, zoomScale: 0.9 }); } else { LightboxView.SetLightboxDoc(doc, target); } @@ -218,7 +230,6 @@ export class LightboxView extends React.Component { .filter(doc => doc) .map(doc => doc!); LightboxView.SetLightboxDoc(coll, undefined, contents.length ? contents : links); - TabDocView.PinDoc(coll, { hidePresBox: true }); } }; @@ -251,19 +262,7 @@ export class LightboxView extends React.Component { { - LightboxView._docView = r !== null ? r : undefined; - r && - setTimeout( - action(() => { - const target = LightboxView._docTarget; - const doc = LightboxView._doc; - //const targetView = target && DocumentManager.Instance.getLightboxDocumentView(target); - //if (doc === r.props.Document && (!target || target === doc)) r.ComponentView?.shrinkWrap?.(); - //else target?.focus(target, { willZoom: true, scale: 0.9, instant: true }); // bcz: why was this here? it breaks smooth navigation in lightbox using 'next' button - }) - ); - })} + ref={action((r: DocumentView | null) => (LightboxView._docView = r !== null ? r : undefined))} Document={LightboxView.LightboxDoc} DataDoc={undefined} PanelWidth={this.lightboxWidth} @@ -332,7 +331,6 @@ export class LightboxView extends React.Component { onClick={e => { e.stopPropagation(); CollectionDockingView.AddSplit(LightboxView._docTarget || LightboxView._doc!, OpenWhereMod.none); - //LightboxView.openInTabFunc(LightboxView._docTarget || LightboxView._doc!, "inPlace"); SelectionManager.DeselectAll(); LightboxView.SetLightboxDoc(undefined); }}> diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx index 66c3ed439..65a950711 100644 --- a/src/client/views/PropertiesButtons.tsx +++ b/src/client/views/PropertiesButtons.tsx @@ -77,6 +77,14 @@ export class PropertiesButtons extends React.Component<{}, {}> { (dv, doc) => InkingStroke.toggleMask(dv?.layoutDoc || doc) ); } + @computed get hideImageButton() { + return this.propertyToggleBtn( + 'Background', + '_hideImage', + on => (on ? 'Show Image' : 'Show Background'), + on => 'portrait' + ); + } @computed get clustersButton() { return this.propertyToggleBtn( 'Clusters', @@ -383,6 +391,7 @@ export class PropertiesButtons extends React.Component<{}, {}> { const layoutField = this.selectedDoc?.[Doc.LayoutFieldKey(this.selectedDoc)]; const isText = layoutField instanceof RichTextField; const isInk = layoutField instanceof InkField; + const isImage = layoutField instanceof ImageField; const isMap = this.selectedDoc?.type === DocumentType.MAP; const isCollection = this.selectedDoc?.type === DocumentType.COL; //TODO: will likely need to create separate note-taking view type here @@ -410,6 +419,7 @@ export class PropertiesButtons extends React.Component<{}, {}> { {toggle(this.inPlaceContainerButton, { display: !isFreeForm && !isMap ? 'none' : '' })} {toggle(this.autoHeightButton, { display: !isText && !isStacking && !isTree ? 'none' : '' })} {toggle(this.maskButton, { display: !isInk ? 'none' : '' })} + {toggle(this.hideImageButton, { display: !isImage ? 'none' : '' })} {toggle(this.chromeButton, { display: !isCollection || isNovice ? 'none' : '' })} {toggle(this.gridButton, { display: !isCollection ? 'none' : '' })} {toggle(this.groupButton, { display: isTabView || !isCollection ? 'none' : '' })} diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index 411f51d84..a2bc37095 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -143,7 +143,7 @@ export class PropertiesView extends React.Component { : layoutDoc._fitWidth ? !Doc.NativeHeight(this.dataDoc) ? NumCast(this.props.height) - : Math.min((this.docWidth() * NumCast(layoutDoc.scrollHeight, Doc.NativeHeight(layoutDoc))) / Doc.NativeWidth(layoutDoc) || NumCast(this.props.height)) + : Math.min((this.docWidth() * Doc.NativeHeight(layoutDoc)) / Doc.NativeWidth(layoutDoc) || NumCast(this.props.height)) : NumCast(layoutDoc._height) || 50 ) ); diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index ece224c68..3cb920ba0 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -269,9 +269,9 @@ export function DefaultStyleProvider(doc: Opt, props: Opt this.props.childHideDecorationTitle?.() ?? BoolCast(this.Document.childHideDecorationTitle); childLayoutTemplate = () => this.props.childLayoutTemplate?.() || Cast(this.rootDoc.childLayoutTemplate, Doc, null); @computed get childLayoutString() { - return StrCast(this.rootDoc.childLayoutString); + return StrCast(this.rootDoc.childLayoutString, this.props.childLayoutString); } isContentActive = (outsideReaction?: boolean) => this.props.isContentActive(); diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index 25fccd89c..bf8d449ea 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -274,7 +274,7 @@ export class TabDocView extends React.Component { pinDoc.presStartTime = NumCast(doc.clipStart); pinDoc.presEndTime = NumCast(doc.clipEnd, duration); } - PresBox.pinDocView(pinDoc, pinProps.pinDocContent ? { ...pinProps, pinData: PresBox.pinDataTypes(doc) } : pinProps, pinDoc); + PresBox.pinDocView(pinDoc, pinProps.pinDocContent ? { ...pinProps, pinData: PresBox.pinDataTypes(doc) } : pinProps, DocCast(pinDoc.proto)); pinDoc.onClick = ScriptField.MakeFunction('navigateToDoc(self.presentationTargetDoc, self)'); Doc.AddDocToList(curPres, 'data', pinDoc, presSelected); //save position @@ -359,7 +359,7 @@ export class TabDocView extends React.Component { // prettier-ignore switch (whereFields[0]) { case OpenWhere.inPlace: // fall through to lightbox - case OpenWhere.lightbox: return LightboxView.AddDocTab(doc, location, undefined, this.addDocTab); + case OpenWhere.lightbox: return LightboxView.AddDocTab(doc, location); case OpenWhere.dashboard: return DashboardView.openDashboard(doc); case OpenWhere.fullScreen: return CollectionDockingView.OpenFullScreen(doc); case OpenWhere.close: return CollectionDockingView.CloseSplit(doc, whereMods); @@ -382,14 +382,8 @@ export class TabDocView extends React.Component { }; @action focusFunc = (doc: Doc, options: DocFocusOptions) => { - const shrinkwrap = options?.originalTarget === this._document && this.view?.ComponentView?.shrinkWrap; - if (options?.willPanZoom !== false && shrinkwrap && this._document) { - const focusSpeed = options.zoomTime ?? 500; - shrinkwrap(); - this._view?.setViewTransition('transform', focusSpeed, () => options?.afterFocus?.(false)); - } else { - options?.afterFocus?.(false); - } + options?.afterFocus?.(false); + if (!this.tab.header.parent._activeContentItem || this.tab.header.parent._activeContentItem !== this.tab.contentItem) { this.tab.header.parent.setActiveContentItem(this.tab.contentItem); // glr: Panning does not work when this is set - (this line is for trying to make a tab that is not topmost become topmost) } diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index bd326f917..2398d8f58 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -410,7 +410,7 @@ export class TreeView extends React.Component { if (aspect) return this.embeddedPanelWidth() / (aspect || 1); return layoutDoc._fitWidth ? !Doc.NativeHeight(layoutDoc) - ? NumCast(layoutDoc._height) //this.props.containerCollection._height) + ? NumCast(layoutDoc._height) : Math.min((this.embeddedPanelWidth() * NumCast(layoutDoc.scrollHeight, Doc.NativeHeight(layoutDoc))) / (Doc.NativeWidth(layoutDoc) || NumCast(this.props.containerCollection._height))) : (this.embeddedPanelWidth() * layoutDoc[HeightSym]()) / layoutDoc[WidthSym](); })() @@ -957,6 +957,7 @@ export class TreeView extends React.Component { ); }; + fitWidthFilter = (doc: Doc) => (doc.type === DocumentType.IMG ? false : undefined); renderEmbeddedDocument = (asText: boolean, isActive: () => boolean | undefined) => { return (
@@ -965,6 +966,7 @@ export class TreeView extends React.Component { ref={action((r: DocumentView | null) => (this._dref = r))} Document={this.doc} DataDoc={undefined} + fitWidth={this.fitWidthFilter} PanelWidth={this.embeddedPanelWidth} PanelHeight={this.embeddedPanelHeight} LayoutTemplateString={asText ? FormattedTextBox.LayoutString('text') : undefined} diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 51672578e..d6e95f97f 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -16,7 +16,7 @@ import { BoolCast, Cast, DocCast, FieldValue, NumCast, ScriptCast, StrCast } fro import { ImageField } from '../../../../fields/URLField'; import { TraceMobx } from '../../../../fields/util'; import { GestureUtils } from '../../../../pen-gestures/GestureUtils'; -import { aggregateBounds, emptyFunction, intersectRect, returnFalse, setupMoveUpEvents, Utils } from '../../../../Utils'; +import { aggregateBounds, emptyFunction, intersectRect, returnFalse, returnTransparent, setupMoveUpEvents, Utils } from '../../../../Utils'; import { CognitiveServices } from '../../../cognitive_services/CognitiveServices'; import { Docs, DocUtils } from '../../../documents/Documents'; import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes'; @@ -61,6 +61,7 @@ export type collectionFreeformViewProps = { scaleField?: string; noOverlay?: boolean; // used to suppress docs in the overlay (z) layer (ie, for minimap since overlay doesn't scale) engineProps?: any; + getScrollHeight?: () => number | undefined; dontScaleFilter?: (doc: Doc) => boolean; // whether this collection should scale documents to fit their panel vs just scrolling them dontRenderDocuments?: boolean; // used for annotation overlays which need to distribute documents into different freeformviews with different mixBlendModes depending on whether they are transparent or not. // However, this screws up interactions since only the top layer gets events. so we render the freeformview a 3rd time with all documents in order to get interaction events (eg., marquee) but we don't actually want to display the documents. @@ -363,10 +364,10 @@ export class CollectionFreeFormView extends CollectionSubView 20) { deltaScale = 20 / invTransform.Scale; } + if (deltaScale < 1 && invTransform.Scale <= NumCast(this.rootDoc._viewScaleMin, 1) && this.isAnnotationOverlay) { + return; + } if (deltaScale * invTransform.Scale < NumCast(this.rootDoc._viewScaleMin, 1) && this.isAnnotationOverlay) { deltaScale = NumCast(this.rootDoc._viewScaleMin, 1) / invTransform.Scale; } @@ -1012,29 +1017,26 @@ export class CollectionFreeFormView extends CollectionSubView= 0.05 || localTransform.Scale > this.zoomScaling()) { const safeScale = Math.min(Math.max(0.05, localTransform.Scale), 20); this.props.Document[this.scaleFieldKey] = Math.abs(safeScale); - this.setPan(-localTransform.TranslateX / safeScale, -localTransform.TranslateY / safeScale); + this.setPan(-localTransform.TranslateX / safeScale, NumCast(this.props.Document.scrollTop) * safeScale || -localTransform.TranslateY / safeScale); } }; @action onPointerWheel = (e: React.WheelEvent): void => { + if (this.Document._isGroup) return; // group style collections neither pan nor zoom PresBox.Instance?.pauseAutoPres(); if (this.layoutDoc._Transform || DocListCast(Doc.MyOverlayDocs?.data).includes(this.props.Document) || this.props.Document.treeViewOutlineMode === TreeViewType.outline) return; e.stopPropagation(); e.preventDefault(); switch (!e.ctrlKey ? Doc.UserDoc().freeformScrollMode : freeformScrollMode.Pan) { case freeformScrollMode.Pan: - // if shift is selected then zoom + // if ctrl is selected then zoom if (e.ctrlKey) { - if (!e.ctrlKey && this.props.Document.scrollHeight !== undefined) { - // things that can scroll vertically should do that instead of zooming - } else if (this.props.isContentActive(true) && !this.Document._isGroup) { + if (this.props.isContentActive(true)) { !this.props.isAnnotationOverlayScrollable && this.zoom(e.clientX, e.clientY, e.deltaY); // if (!this.props.isAnnotationOverlay) // bcz: do we want to zoom in on images/videos/etc? } - // otherwise pan - } else if (!e.ctrlKey && this.props.Document.scrollHeight !== undefined) { - // things that can scroll vertically should do that instead of zooming - } else if (this.props.isContentActive(true) && !this.Document._isGroup) { + } // otherwise pan + else if (this.props.isContentActive(true)) { const dx = -e.deltaX; const dy = -e.deltaY; if (e.shiftKey) { @@ -1046,9 +1048,7 @@ export class CollectionFreeFormView extends CollectionSubView { + this.rootDoc.scrollTop = relTop * maxScrollTop; + }, 10); + newPanY = minPanY; + } !this.Document._verticalScroll && (this.Document._panX = this.isAnnotationOverlay ? newPanX : panX); !this.Document._horizontalScroll && (this.Document._panY = this.isAnnotationOverlay ? newPanY : panY); } @@ -1169,7 +1188,7 @@ export class CollectionFreeFormView extends CollectionSubView {this._firstRender ? this.placeholder : this.marqueeView} {this.props.noOverlay ? null : } diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 0b7854926..bc3b17cd9 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -431,8 +431,8 @@ export class MarqueeView extends React.Component { - const selected = this.marqueeSelect(false); + collection = (e: KeyboardEvent | React.PointerEvent | undefined, group?: boolean, selection?: Doc[]) => { + const selected = selection ?? this.marqueeSelect(false); const activeFrame = selected.reduce((v, d) => v ?? Cast(d._activeFrame, 'number', null), undefined as number | undefined); if (e instanceof KeyboardEvent ? 'cg'.includes(e.key) : true) { this.props.removeDocument?.(selected); diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index b94db2c6b..36c0240f1 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -56,6 +56,7 @@ import { ScriptingBox } from './ScriptingBox'; import { PresEffect, PresEffectDirection } from './trails'; import { PinProps } from './trails/PresBox'; import React = require('react'); +import { GestureOverlay } from '../GestureOverlay'; const { Howl } = require('howler'); interface Window { @@ -139,7 +140,6 @@ export interface DocComponentView { fieldKey?: string; annotationKey?: string; getTitle?: () => string; - getScrollHeight?: () => number; getCenter?: (xf: Transform) => { X: number; Y: number }; ptToScreen?: (pt: { X: number; Y: number }) => { X: number; Y: number }; ptFromScreen?: (pt: { X: number; Y: number }) => { X: number; Y: number }; @@ -170,7 +170,7 @@ export interface DocumentViewSharedProps { dataTransition?: string; // specifies animation transition - used by collectionPile and potentially other layout engines when changing the size of documents so that the change won't be abrupt styleProvider: Opt; focus: DocFocusFunc; - fitWidth?: (doc: Doc) => boolean; + fitWidth?: (doc: Doc) => boolean | undefined; docFilters: () => string[]; docRangeFilters: () => string[]; searchFilterDocs: () => Doc[]; @@ -637,8 +637,8 @@ export class DocumentViewInternal extends DocComponent (func().result?.select === true ? this.props.select(false) : ''), 'on double click'); - } else if (!Doc.IsSystem(this.rootDoc) && !this.rootDoc.isLinkButton) { - UndoManager.RunInBatch(() => LightboxView.AddDocTab(this.rootDoc, OpenWhere.lightbox, this.props.LayoutTemplate?.(), this.props.addDocTab), 'double tap'); + } else if (!Doc.IsSystem(this.rootDoc) && (![DocumentType.INK].includes(this.rootDoc.type as any) || Doc.UserDoc().openInkInLightbox) && !this.rootDoc.isLinkButton) { + UndoManager.RunInBatch(() => LightboxView.AddDocTab(this.rootDoc, OpenWhere.lightbox, this.props.LayoutTemplate?.()), 'double tap'); SelectionManager.DeselectAll(); Doc.UnBrushDoc(this.props.Document); } @@ -669,7 +669,7 @@ export class DocumentViewInternal extends DocComponent { this._timeout = undefined; clickFunc(); - }, 350); + }, 150); } else clickFunc(); } else if (!this._longPress && this.allLinks.length && this.Document.type !== DocumentType.LINK && !isScriptBox() && this.Document.isLinkButton && !e.shiftKey && !e.ctrlKey) { SelectionManager.DeselectAll(); @@ -698,6 +698,7 @@ export class DocumentViewInternal extends DocComponent { + if (!(e.nativeEvent as any).DownDocView) (e.nativeEvent as any).DownDocView = GestureOverlay.DownDocView = this.props.DocumentView(); if (this.rootDoc.type === DocumentType.INK && Doc.ActiveTool === InkTool.Eraser) return; // continue if the event hasn't been canceled AND we are using a mouse or this has an onClick or onDragStart function (meaning it is a button document) if (!(InteractionUtils.IsType(e, InteractionUtils.MOUSETYPE) || [InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool))) { @@ -1640,7 +1641,7 @@ export class DocumentView extends React.Component { return this.docView?.LayoutFieldKey || 'layout'; } get fitWidth() { - return this.props.fitWidth?.(this.rootDoc) || this.layoutDoc.fitWidth; + return this.props.fitWidth?.(this.rootDoc) ?? this.layoutDoc?.fitWidth; } @computed get hideLinkButton() { @@ -1688,8 +1689,7 @@ export class DocumentView extends React.Component { } @computed get panelHeight() { if (this.effectiveNativeHeight && !this.layoutDoc.nativeHeightUnfrozen) { - const scrollHeight = this.fitWidth ? Math.max(this.ComponentView?.getScrollHeight?.() ?? NumCast(this.layoutDoc.scrollHeight)) : 0; - return Math.min(this.props.PanelHeight(), Math.max(scrollHeight, this.effectiveNativeHeight) * this.nativeScaling); + return Math.min(this.props.PanelHeight(), this.effectiveNativeHeight * this.nativeScaling); } return this.props.PanelHeight(); } @@ -1902,6 +1902,11 @@ ScriptingGlobals.add(function deiconifyView(documentView: DocumentView) { documentView.select(false); }); +ScriptingGlobals.add(function deiconifyViewToLightbox(documentView: DocumentView) { + //documentView.iconify(() => + LightboxView.AddDocTab(documentView.rootDoc, OpenWhere.lightbox, 'layout'); //, 0); +}); + ScriptingGlobals.add(function toggleDetail(dv: DocumentView, detailLayoutKeySuffix: string) { if (dv.Document.layoutKey === 'layout_' + detailLayoutKeySuffix) dv.switchViews(false, 'layout'); else dv.switchViews(true, detailLayoutKeySuffix, undefined, true); diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index bdd99528b..540958941 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -11,7 +11,7 @@ import { ComputedField } from '../../../fields/ScriptField'; import { BoolCast, Cast, NumCast, StrCast } from '../../../fields/Types'; import { ImageField } from '../../../fields/URLField'; import { TraceMobx } from '../../../fields/util'; -import { emptyFunction, OmitKeys, returnEmptyString, returnFalse, returnOne, setupMoveUpEvents, Utils } from '../../../Utils'; +import { DashColor, emptyFunction, OmitKeys, returnEmptyString, returnFalse, returnOne, setupMoveUpEvents, Utils } from '../../../Utils'; import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils'; import { CognitiveServices, Confidence, Service, Tag } from '../../cognitive_services/CognitiveServices'; import { Docs, DocUtils } from '../../documents/Documents'; @@ -26,13 +26,15 @@ import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComp import { MarqueeAnnotator } from '../MarqueeAnnotator'; import { AnchorMenu } from '../pdf/AnchorMenu'; import { StyleProp } from '../StyleProvider'; -import { DocFocusOptions, DocumentViewProps } from './DocumentView'; +import { DocFocusOptions, OpenWhere } from './DocumentView'; import { FaceRectangles } from './FaceRectangles'; import { FieldView, FieldViewProps } from './FieldView'; import './ImageBox.scss'; import { PresBox } from './trails'; import React = require('react'); import Color = require('color'); +import { LinkDocPreview } from './LinkDocPreview'; +import { DocumentManager } from '../../util/DocumentManager'; export const pageSchema = createSchema({ googlePhotosUrl: 'string', @@ -51,6 +53,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent>, addAsAnnotation: boolean) => Opt = () => undefined; @@ -119,6 +123,16 @@ export class ImageBox extends ViewBoxAnnotatableComponent this.layoutDoc._scrollTop, + s_top => { + this._forcedScroll = true; + !this._ignoreScroll && this._mainCont.current && (this._mainCont.current.scrollTop = NumCast(s_top)); + this._mainCont.current?.scrollTo({ top: NumCast(s_top) }); + this._forcedScroll = false; + }, + { fireImmediately: true } + ); } componentWillUnmount() { @@ -155,6 +169,21 @@ export class ImageBox extends ViewBoxAnnotatableComponent (this.layoutDoc[this.fieldKey + '-useAlt'] = !this.layoutDoc[this.fieldKey + '-useAlt']); + @undoBatch + setNativeSize = action(() => { + const scaling = (this.props.DocumentView?.().props.ScreenToLocalTransform().Scale || 1) / NumCast(this.rootDoc._viewScale, 1); + const nscale = NumCast(this.props.PanelWidth()) / scaling; + const nh = nscale / NumCast(this.dataDoc[this.fieldKey + '-nativeHeight']); + const nw = nscale / NumCast(this.dataDoc[this.fieldKey + '-nativeWidth']); + this.dataDoc[this.fieldKey + '-nativeHeight'] = NumCast(this.dataDoc[this.fieldKey + '-nativeHeight']) * nh; + this.dataDoc[this.fieldKey + '-nativeWidth'] = NumCast(this.dataDoc[this.fieldKey + '-nativeWidth']) * nw; + this.rootDoc._panX = nh * NumCast(this.rootDoc._panX); + this.rootDoc._panY = nw * NumCast(this.rootDoc._panY); + this.dataDoc._panXMax = this.dataDoc._panXMax ? nh * NumCast(this.dataDoc._panXMax) : undefined; + this.dataDoc._panXMin = this.dataDoc._panXMin ? nh * NumCast(this.dataDoc._panXMin) : undefined; + this.dataDoc._panYMax = this.dataDoc._panYMax ? nw * NumCast(this.dataDoc._panYMax) : undefined; + this.dataDoc._panYMin = this.dataDoc._panYMin ? nw * NumCast(this.dataDoc._panYMin) : undefined; + }); @undoBatch rotate = action(() => { const nw = NumCast(this.dataDoc[this.fieldKey + '-nativeWidth']); @@ -189,6 +218,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent setTimeout(() => (dv.ComponentView as ImageBox).setNativeSize(), 200)); this.props.bringToFront(cropping); return cropping; }; @@ -216,6 +250,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent Utils.CopyText(this.choosePath(field.url)), icon: 'expand-arrows-alt' }); if (!Doc.noviceMode) { @@ -282,6 +317,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent; }; + getScrollHeight = () => (this.props.fitWidth?.(this.rootDoc) !== false && NumCast(this.rootDoc._viewScale, 1) === NumCast(this.rootDoc._viewScaleMin, 1) ? this.nativeSize.nativeHeight : undefined); + @computed private get considerDownloadIcon() { const data = this.dataDoc[this.fieldKey]; @@ -346,9 +383,9 @@ export class ImageBox extends ViewBoxAnnotatableComponent -
+
{fadepath === srcpath ? null : (
[this.content]; private _mainCont: React.RefObject = React.createRef(); @@ -394,6 +430,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent; } + screenToLocalTransform = () => this.props.ScreenToLocalTransform().translate(0, NumCast(this.layoutDoc._scrollTop) * this.props.ScreenToLocalTransform().Scale); marqueeDown = (e: React.PointerEvent) => { if (!e.altKey && e.button === 0 && NumCast(this.rootDoc._viewScale, 1) <= NumCast(this.rootDoc.viewScaleMin, 1) && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) { setupMoveUpEvents( @@ -410,7 +447,6 @@ export class ImageBox extends ViewBoxAnnotatableComponent this.nativeSize.nativeHeight; @action finishMarquee = () => { this._getAnchor = AnchorMenu.Instance?.GetAnchor; @@ -418,11 +454,6 @@ export class ImageBox extends ViewBoxAnnotatableComponent this._savedAnnotations; - styleProvider = (doc: Opt, props: Opt, property: string): any => { - if (property === StyleProp.BoxShadow) return undefined; - return this.props.styleProvider?.(doc, props, property); - }; - render() { TraceMobx(); const borderRad = this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BorderRounding); @@ -432,25 +463,35 @@ export class ImageBox extends ViewBoxAnnotatableComponent { + if (!this._forcedScroll) { + if (this.layoutDoc._scrollTop || this._mainCont.current?.scrollTop) { + this._ignoreScroll = true; + this.layoutDoc._scrollTop = this._mainCont.current?.scrollTop; + this._ignoreScroll = false; + } + } + })} style={{ width: this.props.PanelWidth() ? undefined : `100%`, height: this.props.PanelWidth() ? undefined : `100%`, pointerEvents: this.layoutDoc._lockedPosition ? 'none' : undefined, borderRadius, - overflow: 'auto', + overflow: this.layoutDoc.fitWidth || this.props.fitWidth?.(this.rootDoc) ? 'auto' : undefined, }}> () { } else { this.rootDoc[this.fieldKey + '_x'] = ((pt[0] - bounds.left) / bounds.width) * 100; this.rootDoc[this.fieldKey + '_y'] = ((pt[1] - bounds.top) / bounds.height) * 100; + this.rootDoc.linkAutoMove = false; } } return false; diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx index 95cb49037..5940fc075 100644 --- a/src/client/views/nodes/MapBox/MapBox.tsx +++ b/src/client/views/nodes/MapBox/MapBox.tsx @@ -542,8 +542,8 @@ export class MapBox extends ViewBoxAnnotatableComponent this.props.PanelWidth() / (this.props.NativeDimScaling?.() || 1) - this.sidebarWidth(); // (this.Document.scrollHeight || Doc.NativeHeight(this.Document) || 0); - panelHeight = () => this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1); // () => this._pageSizes.length && this._pageSizes[0] ? this._pageSizes[0].width : Doc.NativeWidth(this.Document); + panelWidth = () => this.props.PanelWidth() / (this.props.NativeDimScaling?.() || 1) - this.sidebarWidth(); + panelHeight = () => this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1); scrollXf = () => this.props.ScreenToLocalTransform().translate(0, NumCast(this.layoutDoc._scrollTop)); transparentFilter = () => [...this.props.docFilters(), Utils.IsTransparentFilter()]; opaqueFilter = () => [...this.props.docFilters(), Utils.IsOpaqueFilter()]; diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index d0d638e98..c4cca9679 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -295,12 +295,12 @@ export class WebBox extends ViewBoxAnnotatableComponent this._scrollHeight; - isFirefox = () => { return 'InstallTrigger' in window; // navigator.userAgent.indexOf("Chrome") !== -1; }; @@ -969,8 +966,8 @@ export class WebBox extends ViewBoxAnnotatableComponent) => (this._searchString = e.currentTarget.value); showInfo = action((anno: Opt) => (this._overlayAnnoInfo = anno)); setPreviewCursor = (func?: (x: number, y: number, drag: boolean, hide: boolean) => void) => (this._setPreviewCursor = func); - panelWidth = () => this.props.PanelWidth() / (this.props.NativeDimScaling?.() || 1) - this.sidebarWidth() + WebBox.sidebarResizerWidth; // (this.Document.scrollHeight || Doc.NativeHeight(this.Document) || 0); - panelHeight = () => this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1); // () => this._pageSizes.length && this._pageSizes[0] ? this._pageSizes[0].width : Doc.NativeWidth(this.Document); + panelWidth = () => this.props.PanelWidth() / (this.props.NativeDimScaling?.() || 1) - this.sidebarWidth() + WebBox.sidebarResizerWidth; + panelHeight = () => this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1); scrollXf = () => this.props.ScreenToLocalTransform().translate(0, NumCast(this.layoutDoc._scrollTop)); anchorMenuClick = () => this._sidebarRef.current?.anchorMenuClick; transparentFilter = () => [...this.props.docFilters(), Utils.IsTransparentFilter()]; diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 619c59f0e..80b18b8b9 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -994,7 +994,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent this.scrollHeight; // 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) => { diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index 427911bd3..0a96297b7 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -35,6 +35,7 @@ import { ScriptingBox } from '../ScriptingBox'; import './PresBox.scss'; import { PresEffect, PresEffectDirection, PresMovement, PresStatus } from './PresEnums'; import { CollectionStackedTimeline } from '../../collections/CollectionStackedTimeline'; +import { PresElementBox } from './PresElementBox'; const { Howl } = require('howler'); export interface PinProps { @@ -383,9 +384,11 @@ export class PresBox extends ViewBoxBaseComponent() { Doc.GetProto(bestTarget)[fkey + '-annotations'] = new List([...DocListCast(bestTarget[fkey + '-annotations']).filter(doc => doc.unrendered), ...DocListCast(activeItem.presAnnotations)]); } if (pinDataTypes.dataview && activeItem.presData !== undefined) { + bestTarget._dataTransition = `all ${transTime}ms`; const fkey = Doc.LayoutFieldKey(bestTarget); Doc.GetProto(bestTarget)[fkey] = activeItem.presData instanceof ObjectField ? activeItem.presData[Copy]() : activeItem.presData; bestTarget[fkey + '-useAlt'] = activeItem.presUseAlt; + setTimeout(() => (bestTarget._dataTransition = undefined), transTime + 10); } if (pinDataTypes.textview && activeItem.presData !== undefined) Doc.GetProto(bestTarget)[Doc.LayoutFieldKey(bestTarget)] = activeItem.presData instanceof ObjectField ? activeItem.presData[Copy]() : activeItem.presData; if (pinDataTypes.poslayoutview) { @@ -2244,6 +2247,7 @@ export class PresBox extends ViewBoxBaseComponent() { ignoreUnrendered={true} //childFitWidth={returnTrue} childOpacity={returnOne} + //childLayoutString={PresElementBox.LayoutString('data')} childLayoutTemplate={this.childLayoutTemplate} childXPadding={Doc.IsComicStyle(this.rootDoc) ? 20 : undefined} filterAddDocument={this.addDocumentFilter} diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx index c53cc608c..4a7f5d038 100644 --- a/src/client/views/pdf/AnchorMenu.tsx +++ b/src/client/views/pdf/AnchorMenu.tsx @@ -11,6 +11,7 @@ import { AntimodeMenu, AntimodeMenuProps } from '../AntimodeMenu'; import { LinkPopup } from '../linking/LinkPopup'; import { ButtonDropdown } from '../nodes/formattedText/RichTextMenu'; import './AnchorMenu.scss'; +import { LightboxView } from '../LightboxView'; @observer export class AnchorMenu extends AntimodeMenu { diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index b0b7816b8..9610a71ac 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -144,6 +144,8 @@ export class PDFViewer extends React.Component { } }; + @observable _scrollHeight = 0; + @action initialLoad = async () => { if (this._pageSizes.length === 0) { @@ -164,8 +166,8 @@ export class PDFViewer extends React.Component { ) ) ); - this.props.Document.scrollHeight = (this._pageSizes.reduce((size, page) => size + page.height, 0) * 96) / 72; } + runInAction(() => (this._scrollHeight = (this._pageSizes.reduce((size, page) => size + page.height, 0) * 96) / 72)); }; _scrollStopper: undefined | (() => void); @@ -177,7 +179,7 @@ export class PDFViewer extends React.Component { let focusSpeed: Opt; if (doc !== this.props.rootDoc && mainCont) { const windowHeight = this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1); - const scrollTo = doc.unrendered ? scrollTop : Utils.scrollIntoView(scrollTop, doc[HeightSym](), NumCast(this.props.layoutDoc._scrollTop), windowHeight, 0.1 * windowHeight, NumCast(this.props.Document.scrollHeight)); + const scrollTo = doc.unrendered ? scrollTop : Utils.scrollIntoView(scrollTop, doc[HeightSym](), NumCast(this.props.layoutDoc._scrollTop), windowHeight, 0.1 * windowHeight, this._scrollHeight); if (scrollTo !== undefined && scrollTo !== this.props.layoutDoc._scrollTop) { if (!this._pdfViewer) this._initialScroll = { loc: scrollTo, easeFunc: options.easeFunc }; else if (!options.instant) this._scrollStopper = smoothScroll((focusSpeed = options.zoomTime ?? 500), mainCont, scrollTo, options.easeFunc, this._scrollStopper); @@ -453,10 +455,6 @@ export class PDFViewer extends React.Component { } }; - scrollXf = () => { - return this._mainCont.current ? this.props.ScreenToLocalTransform().translate(0, NumCast(this.props.layoutDoc._scrollTop)) : this.props.ScreenToLocalTransform(); - }; - onClick = (e: React.MouseEvent) => { this._scrollStopper?.(); if (this._setPreviewCursor && e.button === 0 && Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD) { @@ -504,10 +502,12 @@ export class PDFViewer extends React.Component { ); } + getScrollHeight = () => this._scrollHeight; showInfo = action((anno: Opt) => (this._overlayAnnoInfo = anno)); + scrollXf = () => (this._mainCont.current ? this.props.ScreenToLocalTransform().translate(0, NumCast(this.props.layoutDoc._scrollTop)) : this.props.ScreenToLocalTransform()); overlayTransform = () => this.scrollXf().scale(1 / NumCast(this.props.layoutDoc._viewScale, 1)); - panelWidth = () => this.props.PanelWidth() / (this.props.NativeDimScaling?.() || 1); // (this.Document.scrollHeight || Doc.NativeHeight(this.Document) || 0); - panelHeight = () => this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1); // () => this._pageSizes.length && this._pageSizes[0] ? this._pageSizes[0].width : Doc.NativeWidth(this.Document); + panelWidth = () => this.props.PanelWidth() / (this.props.NativeDimScaling?.() || 1); + panelHeight = () => this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1); transparentFilter = () => [...this.props.docFilters(), Utils.IsTransparentFilter()]; opaqueFilter = () => [...this.props.docFilters(), Utils.noDragsDocFilter, ...(DragManager.docsBeingDragged.length ? [] : [Utils.IsOpaqueFilter()])]; childStyleProvider = (doc: Doc | undefined, props: Opt, property: string): any => { @@ -533,6 +533,7 @@ export class PDFViewer extends React.Component { isAnnotationOverlay={true} fieldKey={this.props.fieldKey + '-annotations'} CollectionView={undefined} + getScrollHeight={this.getScrollHeight} setPreviewCursor={this.setPreviewCursor} setBrushViewer={this.setBrushViewer} PanelHeight={this.panelHeight} diff --git a/src/fields/util.ts b/src/fields/util.ts index dc0b41276..3a7484cfd 100644 --- a/src/fields/util.ts +++ b/src/fields/util.ts @@ -350,7 +350,7 @@ export function getter(target: any, prop: string | symbol, proxy: any): any { return target[prop]; case AclSym : return target[AclSym]; case $mobx: return target.__fields[prop]; - case LayoutSym: return target.__Layout__; + case LayoutSym: return target.__LAYOUT__; case HeightSym: case WidthSym: if (GetEffectiveAcl(target) === AclPrivate) return returnZero; default : if (typeof prop === 'symbol') return target[prop]; -- cgit v1.2.3-70-g09d2 From 4475adee0f13d9ec407aff6a47094c7ce808af0c Mon Sep 17 00:00:00 2001 From: Sophie Zhang Date: Wed, 22 Feb 2023 17:35:26 -0500 Subject: added GPT summarization functionality --- package-lock.json | 73 ++++++++++++------------- package.json | 1 + src/client/apis/gpt/Summarization.ts | 35 ++++++++++++ src/client/views/MainView.tsx | 1 + src/client/views/MarqueeAnnotator.tsx | 19 +++++-- src/client/views/collections/CollectionView.tsx | 2 + src/client/views/nodes/WebBox.tsx | 19 +++++++ src/client/views/pdf/AnchorMenu.tsx | 43 +++++++++++++++ src/client/views/pdf/GPTPopup.scss | 9 +++ src/client/views/pdf/GPTPopup.tsx | 19 +++++++ src/client/views/pdf/PDFViewer.tsx | 22 +++++++- 11 files changed, 199 insertions(+), 44 deletions(-) create mode 100644 src/client/apis/gpt/Summarization.ts create mode 100644 src/client/views/pdf/GPTPopup.scss create mode 100644 src/client/views/pdf/GPTPopup.tsx (limited to 'src/client/views/pdf/PDFViewer.tsx') diff --git a/package-lock.json b/package-lock.json index 547a6e1d7..6ea3936cd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5303,16 +5303,6 @@ "integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=", "dev": true }, - "d": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", - "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", - "dev": true, - "requires": { - "es5-ext": "^0.10.50", - "type": "^1.0.1" - } - }, "d3-array": { "version": "2.12.1", "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", @@ -6480,28 +6470,6 @@ "is-symbol": "^1.0.2" } }, - "es5-ext": { - "version": "0.10.62", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.62.tgz", - "integrity": "sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==", - "dev": true, - "requires": { - "es6-iterator": "^2.0.3", - "es6-symbol": "^3.1.3", - "next-tick": "^1.1.0" - } - }, - "es6-iterator": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", - "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", - "dev": true, - "requires": { - "d": "1", - "es5-ext": "^0.10.35", - "es6-symbol": "^3.1.1" - } - }, "es6-promise": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.2.1.tgz", @@ -6513,7 +6481,6 @@ "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", "dev": true, "requires": { - "d": "^1.0.1", "ext": "^1.1.2" } }, @@ -16724,6 +16691,40 @@ "mimic-fn": "^2.1.0" } }, + "openai": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-3.1.0.tgz", + "integrity": "sha512-v5kKFH5o+8ld+t0arudj833Mgm3GcgBnbyN9946bj6u7bvel4Yg6YFz2A4HLIYDzmMjIo0s6vSG9x73kOwvdCg==", + "requires": { + "axios": "^0.26.0", + "form-data": "^4.0.0" + }, + "dependencies": { + "axios": { + "version": "0.26.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz", + "integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==", + "requires": { + "follow-redirects": "^1.14.8" + } + }, + "follow-redirects": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", + "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==" + }, + "form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + } + } + }, "opentype.js": { "version": "1.3.4", "resolved": "https://registry.npmjs.org/opentype.js/-/opentype.js-1.3.4.tgz", @@ -21392,12 +21393,6 @@ "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" }, - "type": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", - "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==", - "dev": true - }, "type-check": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", diff --git a/package.json b/package.json index 712354355..480657d5f 100644 --- a/package.json +++ b/package.json @@ -238,6 +238,7 @@ "nodemon": "^1.19.4", "normalize.css": "^8.0.1", "npm": "^6.14.18", + "openai": "^3.1.0", "p-limit": "^2.2.0", "passport": "^0.4.0", "passport-google-oauth20": "^2.0.0", diff --git a/src/client/apis/gpt/Summarization.ts b/src/client/apis/gpt/Summarization.ts new file mode 100644 index 000000000..3706c7a5b --- /dev/null +++ b/src/client/apis/gpt/Summarization.ts @@ -0,0 +1,35 @@ +import { Configuration, OpenAIApi } from 'openai'; + +const gptSummarize = async (text: string) => { + try { + const configuration = new Configuration({ + apiKey: process.env.OPENAI_KEY, + }); + const openai = new OpenAIApi(configuration); + const response = await openai.createCompletion({ + model: 'text-davinci-003', + max_tokens: 256, + temperature: 0.7, + prompt: `Summarize this text in one sentence: ${text}`, + }); + return response.data.choices[0].text; + } catch (err) { + console.log(err); + return ''; + } +}; + +// Summarizing with the MeaningCloud API +const fetchSummary = async (text: string, numSentences?: number) => { + const key = '0b41c071f838e573847f477e8f69e9d9'; + const queryURL = ''; + const sentences = numSentences ? numSentences : 3; + const URL = `https://api.meaningcloud.com/summarization-1.0?key=${key}&txt=${text}&sentences=${sentences}`; + + const res = await fetch(URL); + const data = await res.json(); + console.log(data.summary); + return data.summary; +}; + +export { fetchSummary, gptSummarize }; diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 895ed9bda..68f1bd72b 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -321,6 +321,7 @@ export class MainView extends React.Component { fa.faClone, fa.faCloudUploadAlt, fa.faCommentAlt, + fa.faCommentDots, fa.faCompressArrowsAlt, fa.faCut, fa.faEllipsisV, diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx index 5ab91dd70..9e9f24393 100644 --- a/src/client/views/MarqueeAnnotator.tsx +++ b/src/client/views/MarqueeAnnotator.tsx @@ -28,6 +28,8 @@ export interface MarqueeAnnotatorProps { docView: DocumentView; savedAnnotations: () => ObservableMap; selectionText: () => string; + summaryText: () => string; + setSummaryText: () => Promise; annotationLayer: HTMLDivElement; addDocument: (doc: Doc) => boolean; getPageFromScroll?: (top: number) => number; @@ -50,6 +52,10 @@ export class MarqueeAnnotator extends React.Component { AnchorMenu.Instance.OnCrop = (e: PointerEvent) => this.props.anchorMenuCrop?.(this.highlight('', true), true); AnchorMenu.Instance.OnClick = (e: PointerEvent) => this.props.anchorMenuClick?.()?.(this.highlight(this.props.highlightDragSrcColor ?? 'rgba(173, 216, 230, 0.75)', true)); + AnchorMenu.Instance.OnSummary = async (e: PointerEvent) => { + await this.props.setSummaryText(); + this.props.anchorMenuClick?.()?.(this.highlight('', true, undefined, undefined, true)); + }; AnchorMenu.Instance.OnAudio = unimplementedFunction; AnchorMenu.Instance.Highlight = this.highlight; AnchorMenu.Instance.GetAnchor = (savedAnnotations?: ObservableMap, addAsAnnotation?: boolean) => this.highlight('rgba(173, 216, 230, 0.75)', true, savedAnnotations, true); @@ -141,7 +147,7 @@ export class MarqueeAnnotator extends React.Component { @undoBatch @action - makeAnnotationDocument = (color: string, isLinkButton?: boolean, savedAnnotations?: ObservableMap): Opt => { + makeAnnotationDocument = (color: string, isLinkButton?: boolean, savedAnnotations?: ObservableMap, summarize?: boolean): Opt => { const savedAnnoMap = savedAnnotations?.values() && Array.from(savedAnnotations?.values()).length ? savedAnnotations : this.props.savedAnnotations(); if (savedAnnoMap.size === 0) return undefined; const savedAnnos = Array.from(savedAnnoMap.values())[0]; @@ -159,7 +165,12 @@ export class MarqueeAnnotator extends React.Component { return marqueeAnno; } - const textRegionAnno = Docs.Create.HTMLAnchorDocument([], { annotationOn: this.props.rootDoc, text: this.props.selectionText(), backgroundColor: 'transparent', title: 'Selection on ' + this.props.rootDoc.title }); + const textRegionAnno = Docs.Create.HTMLAnchorDocument([], { + annotationOn: this.props.rootDoc, + text: summarize ? this.props.summaryText() : this.props.selectionText(), + backgroundColor: 'transparent', + title: 'Selection on ' + this.props.rootDoc.title, + }); let minX = Number.MAX_VALUE; let maxX = -Number.MAX_VALUE; let minY = Number.MAX_VALUE; @@ -194,10 +205,10 @@ export class MarqueeAnnotator extends React.Component { return textRegionAnno; }; @action - highlight = (color: string, isLinkButton: boolean, savedAnnotations?: ObservableMap, addAsAnnotation?: boolean) => { + highlight = (color: string, isLinkButton: boolean, savedAnnotations?: ObservableMap, addAsAnnotation?: boolean, summarize?: boolean) => { // creates annotation documents for current highlights const effectiveAcl = GetEffectiveAcl(this.props.rootDoc[DataSym]); - const annotationDoc = [AclAugment, AclSelfEdit, AclEdit, AclAdmin].includes(effectiveAcl) && this.makeAnnotationDocument(color, isLinkButton, savedAnnotations); + const annotationDoc = [AclAugment, AclSelfEdit, AclEdit, AclAdmin].includes(effectiveAcl) && this.makeAnnotationDocument(color, isLinkButton, savedAnnotations, summarize); addAsAnnotation && !savedAnnotations && annotationDoc && this.props.addDocument(annotationDoc); return (annotationDoc as Doc) ?? undefined; }; diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index a28b1ca19..55f46906e 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -67,6 +67,8 @@ interface CollectionViewProps_ extends FieldViewProps { AddToMap?: (treeViewDoc: Doc, index: number[]) => Doc[]; RemFromMap?: (treeViewDoc: Doc, index: number[]) => Doc[]; hierarchyIndex?: number[]; // hierarchical index of a document up to the rendering root (primarily used for tree views) + // for animation trail in-between selection + isTrailBox?: boolean; } export interface CollectionViewProps extends React.PropsWithChildren {} @observer diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index d0d638e98..996028ec8 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -36,6 +36,7 @@ import { VideoBox } from './VideoBox'; import './WebBox.scss'; import React = require('react'); import { DragManager } from '../../util/DragManager'; +import { gptSummarize } from '../../apis/gpt/Summarization'; const { CreateImage } = require('./WebBoxRenderer'); const _global = (window /* browser */ || global) /* node */ as any; const htmlToText = require('html-to-text'); @@ -58,6 +59,21 @@ export class WebBox extends ViewBoxAnnotatableComponent(); private _searchString = ''; + // GPT Additions + private _summaryText: string = ''; + private _selectionText: string = ''; + setSummaryText = async () => { + try { + const summary = await gptSummarize(this.selectionText()); + this._summaryText = `Summary: ${summary}`; + } catch (err) { + console.log(err); + this._summaryText = 'Failed to fetch summary.'; + } + }; + summaryText = () => this._summaryText; + selectionText = () => this._selectionText; + private get _getAnchor() { return AnchorMenu.Instance?.GetAnchor; } @@ -341,6 +357,7 @@ export class WebBox extends ViewBoxAnnotatableComponent this.createTextAnnotation(sel, !sel.isCollapsed ? sel.getRangeAt(0) : undefined); AnchorMenu.Instance.jumpTo(e.clientX * scale + mainContBounds.translateX, e.clientY * scale + mainContBounds.translateY - NumCast(this.layoutDoc._scrollTop) * scale); } @@ -1017,6 +1034,8 @@ export class WebBox extends ViewBoxAnnotatableComponent diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx index c53cc608c..63c8f9145 100644 --- a/src/client/views/pdf/AnchorMenu.tsx +++ b/src/client/views/pdf/AnchorMenu.tsx @@ -10,6 +10,8 @@ import { SelectionManager } from '../../util/SelectionManager'; import { AntimodeMenu, AntimodeMenuProps } from '../AntimodeMenu'; import { LinkPopup } from '../linking/LinkPopup'; import { ButtonDropdown } from '../nodes/formattedText/RichTextMenu'; +import { gptSummarize } from '../../apis/gpt/Summarization'; +import { GPTPopup } from './GPTPopup'; import './AnchorMenu.scss'; @observer @@ -43,10 +45,30 @@ export class AnchorMenu extends AntimodeMenu { @observable public Highlighting: boolean = false; @observable public Status: 'marquee' | 'annotation' | '' = ''; + // GPT additions (flow 2) + @observable private summarizedText: string = ''; + @observable private showGPTPopup: boolean = false; + @action + setGPTPopupVis = (vis: boolean) => { + this.showGPTPopup = vis; + }; + @action + setSummarizedText = (txt: string) => { + this.summarizedText = txt; + }; + + private selectedText: string = ''; + setSelectedText = (txt: string) => { + this.selectedText = txt; + }; + public onMakeAnchor: () => Opt = () => undefined; // Method to get anchor from text search public OnCrop: (e: PointerEvent) => void = unimplementedFunction; public OnClick: (e: PointerEvent) => void = unimplementedFunction; + public OnSummary: (e: PointerEvent) => Promise = () => { + return new Promise(() => {}); + }; public OnAudio: (e: PointerEvent) => void = unimplementedFunction; public StartDrag: (e: PointerEvent, ele: HTMLElement) => void = unimplementedFunction; public StartCropDrag: (e: PointerEvent, ele: HTMLElement) => void = unimplementedFunction; @@ -83,11 +105,26 @@ export class AnchorMenu extends AntimodeMenu { () => SelectionManager.Views(), selected => { this._showLinkPopup = false; + this.setGPTPopupVis(false); AnchorMenu.Instance.fadeOut(true); } ); } + getGPTSummary = (e: React.PointerEvent) => { + setupMoveUpEvents(this, e, returnFalse, returnFalse, e => this.OnSummary?.(e)); + }; + + invokeGPT = async (e: React.PointerEvent) => { + this.setGPTPopupVis(true); + const res = await gptSummarize(this.selectedText); + if (res) { + this.setSummarizedText(res); + } else { + this.setSummarizedText('Something went wrong.'); + } + }; + pointerDown = (e: React.PointerEvent) => { setupMoveUpEvents( this, @@ -192,6 +229,12 @@ export class AnchorMenu extends AntimodeMenu { + Summarize with GPT-3
}> + + + {AnchorMenu.Instance.OnAudio === unimplementedFunction ? null : ( Click to Record Annotation
}>
) : null} @@ -292,7 +304,7 @@ export class DocumentLinksButton extends React.Component void; export type StyleProviderFunc = (doc: Opt, props: Opt, property: string) => any; export interface DocComponentView { updateIcon?: () => void; // updates the icon representation of the document - getAnchor?: (addAsAnnotation: boolean) => Doc; // returns an Anchor Doc that represents the current state of the doc's componentview (e.g., the current playhead location of a an audio/video box) - scrollFocus?: (doc: Doc, options: DocFocusOptions) => Opt; // returns the duration of the focus + getAnchor?: (addAsAnnotation: boolean, pinData?: PinProps) => Doc; // returns an Anchor Doc that represents the current state of the doc's componentview (e.g., the current playhead location of a an audio/video box) + scrollPreview?: (docView: DocumentView, doc: Doc, options: DocFocusOptions) => Opt; // returns the duration of the focus + scrollFocus?: (docView: DocumentView, doc: Doc, options: DocFocusOptions) => Opt; // returns the duration of the focus brushView?: (view: { width: number; height: number; panX: number; panY: number }) => void; reverseNativeScaling?: () => boolean; // DocumentView's setup screenToLocal based on the doc having a nativeWidth/Height. However, some content views (e.g., FreeFormView w/ fitContentsToBox set) may ignore the native dimensions so this flags the DocumentView to not do Nativre scaling. shrinkWrap?: () => void; // requests a document to display all of its contents with no white space. currently only implemented (needed?) for freeform views @@ -585,7 +586,13 @@ export class DocumentViewInternal extends DocComponent (focusSpeed => (PresBox.restoreTargetDocView(docView, anchor, focusSpeed) ? focusSpeed : undefined))(options.instant ? 0 : options.zoomTime ?? 500)); + const focusSpeed = targetMatch && scrollFocus?.(this.props.DocumentView(), presItem?.proto === anchor ? presItem : anchor, options); // FOCUS: navigate through the display hierarchy making sure the target is in view const endFocus = focusSpeed === undefined ? options?.afterFocus : async (moved: boolean) => options?.afterFocus?.(true) ?? ViewAdjustment.doNothing; diff --git a/src/client/views/nodes/FunctionPlotBox.tsx b/src/client/views/nodes/FunctionPlotBox.tsx index 5c0005dae..8fa01c97a 100644 --- a/src/client/views/nodes/FunctionPlotBox.tsx +++ b/src/client/views/nodes/FunctionPlotBox.tsx @@ -13,7 +13,7 @@ import { Docs } from '../../documents/Documents'; import { DragManager } from '../../util/DragManager'; import { undoBatch } from '../../util/UndoManager'; import { ViewBoxAnnotatableComponent } from '../DocComponent'; -import { DocFocusOptions } from './DocumentView'; +import { DocFocusOptions, DocumentView } from './DocumentView'; import { FieldView, FieldViewProps } from './FieldView'; const EquationSchema = createSchema({}); @@ -42,14 +42,14 @@ export class FunctionPlotBox extends ViewBoxAnnotatableComponent ); } getAnchor = (addAsAnnotation: boolean) => { - const anchor = Docs.Create.TextanchorDocument({ annotationOn: this.rootDoc }); + const anchor = Docs.Create.TextanchorDocument({ annotationOn: this.rootDoc, unrendered: true }); anchor.xRange = new List(Array.from(this._plot.options.xAxis.domain)); anchor.yRange = new List(Array.from(this._plot.options.yAxis.domain)); if (addAsAnnotation) this.addDocument(anchor); return anchor; }; @action - scrollFocus = (doc: Doc, smooth: DocFocusOptions) => { + scrollFocus = (docView: DocumentView, doc: Doc, options: DocFocusOptions) => { this.dataDoc.xRange = new List(Array.from(Cast(doc.xRange, listSpec('number'), Cast(this.dataDoc.xRange, listSpec('number'), [-10, 10])))); this.dataDoc.yRange = new List(Array.from(Cast(doc.yRange, listSpec('number'), Cast(this.dataDoc.xRange, listSpec('number'), [-1, 9])))); return 0; diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 0ba576e55..363cd1d94 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -8,7 +8,7 @@ import { List } from '../../../fields/List'; import { ObjectField } from '../../../fields/ObjectField'; import { createSchema } from '../../../fields/Schema'; import { ComputedField } from '../../../fields/ScriptField'; -import { BoolCast, Cast, NumCast, StrCast } from '../../../fields/Types'; +import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../fields/Types'; import { ImageField } from '../../../fields/URLField'; import { TraceMobx } from '../../../fields/util'; import { DashColor, emptyFunction, OmitKeys, returnEmptyString, returnFalse, returnOne, setupMoveUpEvents, Utils } from '../../../Utils'; @@ -26,11 +26,11 @@ import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComp import { MarqueeAnnotator } from '../MarqueeAnnotator'; import { AnchorMenu } from '../pdf/AnchorMenu'; import { StyleProp } from '../StyleProvider'; -import { DocFocusOptions, OpenWhere } from './DocumentView'; +import { DocFocusOptions, DocumentView, OpenWhere } from './DocumentView'; import { FaceRectangles } from './FaceRectangles'; import { FieldView, FieldViewProps } from './FieldView'; import './ImageBox.scss'; -import { PresBox } from './trails'; +import { PinProps, PresBox } from './trails'; import React = require('react'); import Color = require('color'); import { LinkDocPreview } from './LinkDocPreview'; @@ -71,34 +71,14 @@ export class ImageBox extends ViewBoxAnnotatableComponent { - const focusSpeed = options.instant ? 0 : options.zoomTime ?? 500; - return PresBox.restoreTargetDocView( - this.props.DocumentView?.(), // - { pinDocLayout: BoolCast(anchor.presPinLayout) }, - anchor, - focusSpeed, - !anchor.presPinData - ? {} - : { - pannable: true, - dataannos: anchor.presAnnotations !== undefined, - dataview: true, - } - ) - ? focusSpeed - : undefined; - }; // sets viewing information for a componentview, typically when following a link. 'preview' tells the view to use the values without writing to the document - - getAnchor = (addAsAnnotation: boolean) => { + getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => { const anchor = this._getAnchor?.(this._savedAnnotations, false) ?? // use marquee anchor, otherwise, save zoom/pan as anchor Docs.Create.ImageanchorDocument({ title: 'ImgAnchor:' + this.rootDoc.title, presTransition: 1000, unrendered: true, annotationOn: this.rootDoc }); if (anchor) { if (!addAsAnnotation) anchor.backgroundColor = 'transparent'; /* addAsAnnotation &&*/ this.addDocument(anchor); - PresBox.pinDocView(anchor, { pinData: { pannable: true } }, this.rootDoc); + PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), pannable: true } }, this.rootDoc); return anchor; } return this.rootDoc; @@ -177,9 +157,9 @@ export class ImageBox extends ViewBoxAnnotatableComponent this.rootDoc; - getTitle() { return this.rootDoc['title-custom'] ? StrCast(this.rootDoc.title) : this.props.label ? this.props.label : typeof this.rootDoc[this.fieldKey] === 'string' ? StrCast(this.rootDoc[this.fieldKey]) : StrCast(this.rootDoc.title); } diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 88d134bba..8d7ab2f0d 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -5,7 +5,7 @@ import * as Pdfjs from 'pdfjs-dist'; import 'pdfjs-dist/web/pdf_viewer.css'; import { Doc, DocListCast, HeightSym, Opt, WidthSym } from '../../../fields/Doc'; import { Id } from '../../../fields/FieldSymbols'; -import { BoolCast, Cast, ImageCast, NumCast, StrCast } from '../../../fields/Types'; +import { Cast, ImageCast, NumCast, StrCast } from '../../../fields/Types'; import { ImageField, PdfField } from '../../../fields/URLField'; import { TraceMobx } from '../../../fields/util'; import { emptyFunction, setupMoveUpEvents, Utils } from '../../../Utils'; @@ -22,13 +22,13 @@ import { LightboxView } from '../LightboxView'; import { CreateImage } from '../nodes/WebBoxRenderer'; import { PDFViewer } from '../pdf/PDFViewer'; import { SidebarAnnos } from '../SidebarAnnos'; +import { DocFocusOptions, DocumentView } from './DocumentView'; import { FieldView, FieldViewProps } from './FieldView'; import { ImageBox } from './ImageBox'; import './PDFBox.scss'; +import { PinProps, PresBox } from './trails'; import { VideoBox } from './VideoBox'; import React = require('react'); -import { PresBox } from './trails'; -import { DocFocusOptions } from './DocumentView'; @observer export class PDFBox extends ViewBoxAnnotatableComponent() { @@ -200,18 +200,18 @@ export class PDFBox extends ViewBoxAnnotatableComponent this._pdfViewer?.brushView(view); - scrollFocus = (doc: Doc, options: DocFocusOptions) => { + scrollFocus = (docView: DocumentView, anchor: Doc, options: DocFocusOptions) => { let didToggle = false; - if (DocListCast(this.props.Document[this.fieldKey + '-sidebar']).includes(doc) && !this.SidebarShown) { + if (DocListCast(this.props.Document[this.fieldKey + '-sidebar']).includes(anchor) && !this.SidebarShown) { this.toggleSidebar(options.preview); didToggle = true; } - if (this._sidebarRef?.current?.makeDocUnfiltered(doc)) return 1; - this._initialScrollTarget = doc; - PresBox.restoreTargetDocView(this.props.DocumentView?.(), {}, doc, options.zoomTime ?? 500, { pannable: doc.presPinData ? true : false }); - return this._pdfViewer?.scrollFocus(doc, NumCast(doc.presPinViewScroll, NumCast(doc.y)), options) ?? (didToggle ? 1 : undefined); + if (this._sidebarRef?.current?.makeDocUnfiltered(anchor)) return 1; + this._initialScrollTarget = anchor; + PresBox.restoreTargetDocView(docView, anchor, options.zoomTime ?? 500); + return this._pdfViewer?.scrollFocus(anchor, NumCast(anchor.presPinViewScroll, NumCast(anchor.y)), options) ?? (didToggle ? 1 : undefined); }; - getAnchor = (addAsAnnotation: boolean) => { + getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => { let ele: Opt = undefined; if (this._pdfViewer?.selectionContent()) { ele = document.createElement('div'); @@ -223,11 +223,11 @@ export class PDFBox extends ViewBoxAnnotatableComponent { this._pdfViewer = pdfViewer; - if (this._initialScrollTarget) { - this.scrollFocus(this._initialScrollTarget, { instant: true }); + const docView = this.props.DocumentView?.(); + if (this._initialScrollTarget && docView) { + this.scrollFocus(docView, this._initialScrollTarget, { instant: true }); this._initialScrollTarget = undefined; } }; diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx index f94996c66..61e4894f0 100644 --- a/src/client/views/nodes/ScreenshotBox.tsx +++ b/src/client/views/nodes/ScreenshotBox.tsx @@ -130,7 +130,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent { const startTime = Cast(this.layoutDoc._currentTimecode, 'number', null) || (this._videoRec ? (Date.now() - (this.recordingStart || 0)) / 1000 : undefined); - return CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.annotationKey, '_timecodeToShow', '_timecodeToHide', startTime, startTime === undefined ? undefined : startTime + 3, undefined, addAsAnnotation) || this.rootDoc; + return CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.annotationKey, startTime, startTime === undefined ? undefined : startTime + 3, undefined, addAsAnnotation) || this.rootDoc; }; videoLoad = () => { diff --git a/src/client/views/nodes/ScriptingBox.tsx b/src/client/views/nodes/ScriptingBox.tsx index 281967a21..fa2021642 100644 --- a/src/client/views/nodes/ScriptingBox.tsx +++ b/src/client/views/nodes/ScriptingBox.tsx @@ -20,6 +20,7 @@ import { EditableView } from '../EditableView'; import { FieldView, FieldViewProps } from '../nodes/FieldView'; import { OverlayView } from '../OverlayView'; import { DocumentIconContainer } from './DocumentIcon'; +import { DocFocusOptions, DocumentView } from './DocumentView'; import './ScriptingBox.scss'; const _global = (window /* browser */ || global) /* node */ as any; @@ -481,10 +482,6 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent { - return undefined; - }; - getSuggestedParams(pos: number) { const firstScript = this.rawText.slice(0, pos); const indexP = firstScript.lastIndexOf('.'); diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index 1dfa55c64..c26562e7c 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -7,6 +7,7 @@ import * as rp from 'request-promise'; import { Doc, DocListCast, HeightSym, WidthSym } from '../../../fields/Doc'; import { InkTool } from '../../../fields/InkField'; import { List } from '../../../fields/List'; +import { ObjectField } from '../../../fields/ObjectField'; import { Cast, NumCast, StrCast } from '../../../fields/Types'; import { AudioField, ImageField, VideoField } from '../../../fields/URLField'; import { emptyFunction, formatTime, OmitKeys, returnEmptyString, returnFalse, returnOne, setupMoveUpEvents, Utils } from '../../../Utils'; @@ -27,11 +28,11 @@ import { DocumentDecorations } from '../DocumentDecorations'; import { MarqueeAnnotator } from '../MarqueeAnnotator'; import { AnchorMenu } from '../pdf/AnchorMenu'; import { StyleProp } from '../StyleProvider'; +import { OpenWhere } from './DocumentView'; import { FieldView, FieldViewProps } from './FieldView'; import { RecordingBox } from './RecordingBox'; +import { PinProps, PresBox } from './trails'; import './VideoBox.scss'; -import { ObjectField } from '../../../fields/ObjectField'; -import { DocFocusOptions, OpenWhere } from './DocumentView'; const path = require('path'); /** @@ -390,13 +391,13 @@ export class VideoBox extends ViewBoxAnnotatableComponent downX !== undefined && downY !== undefined && DocumentManager.Instance.getFirstDocumentView(imageSummary)?.startDragging(downX, downY, 'move', true)); }; - getAnchor = (addAsAnnotation: boolean) => { + getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => { const timecode = Cast(this.layoutDoc._currentTimecode, 'number', null); const marquee = AnchorMenu.Instance.GetAnchor?.(undefined, addAsAnnotation); - return ( - CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.annotationKey, '_timecodeToShow' /* videoStart */, '_timecodeToHide' /* videoEnd */, timecode ? timecode : undefined, undefined, marquee, addAsAnnotation) || - this.rootDoc - ); + if (!addAsAnnotation && marquee) marquee.backgroundColor = 'transparent'; + const anchor = CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.annotationKey, timecode ? timecode : undefined, undefined, marquee, addAsAnnotation) || this.rootDoc; + PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), temporal: true } }, this.rootDoc); + return anchor; }; // sets video info on load @@ -953,14 +954,6 @@ export class VideoBox extends ViewBoxAnnotatableComponent { - if (doc !== this.rootDoc) { - const showTime = Cast(doc._timecodeToShow, 'number', null); - showTime !== undefined && setTimeout(() => this.Seek(showTime), 100); - return 0.1; - } - }; - // renders CollectionStackedTimeline @computed get renderTimeline() { return ( diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index 4c9bead9d..caab18e0d 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -1,5 +1,5 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction, trace } from 'mobx'; +import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as WebRequest from 'web-request'; import { Doc, DocListCast, HeightSym, Opt, WidthSym } from '../../../fields/Doc'; @@ -11,8 +11,9 @@ import { listSpec } from '../../../fields/Schema'; import { Cast, ImageCast, NumCast, StrCast } from '../../../fields/Types'; import { ImageField, WebField } from '../../../fields/URLField'; import { TraceMobx } from '../../../fields/util'; -import { emptyFunction, getWordAtPoint, OmitKeys, returnEmptyString, returnFalse, returnOne, setupMoveUpEvents, smoothScroll, StopEvent, Utils } from '../../../Utils'; +import { emptyFunction, getWordAtPoint, OmitKeys, returnFalse, returnOne, setupMoveUpEvents, smoothScroll, StopEvent, Utils } from '../../../Utils'; import { Docs, DocUtils } from '../../documents/Documents'; +import { DragManager } from '../../util/DragManager'; import { ScriptingGlobals } from '../../util/ScriptingGlobals'; import { SnappingManager } from '../../util/SnappingManager'; import { undoBatch, UndoManager } from '../../util/UndoManager'; @@ -21,7 +22,6 @@ import { CollectionFreeFormView } from '../collections/collectionFreeForm/Collec import { ContextMenu } from '../ContextMenu'; import { ContextMenuProps } from '../ContextMenuItem'; import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent'; -import { DocumentDecorations } from '../DocumentDecorations'; import { Colors } from '../global/globalEnums'; import { LightboxView } from '../LightboxView'; import { MarqueeAnnotator } from '../MarqueeAnnotator'; @@ -29,13 +29,13 @@ import { AnchorMenu } from '../pdf/AnchorMenu'; import { Annotation } from '../pdf/Annotation'; import { SidebarAnnos } from '../SidebarAnnos'; import { StyleProp } from '../StyleProvider'; -import { DocFocusOptions, DocumentView, DocumentViewInternal, DocumentViewProps } from './DocumentView'; +import { DocFocusOptions, DocumentView, DocumentViewProps } from './DocumentView'; import { FieldView, FieldViewProps } from './FieldView'; import { LinkDocPreview } from './LinkDocPreview'; import { VideoBox } from './VideoBox'; import './WebBox.scss'; import React = require('react'); -import { DragManager } from '../../util/DragManager'; +import { PinProps, PresBox } from './trails'; const { CreateImage } = require('./WebBoxRenderer'); const _global = (window /* browser */ || global) /* node */ as any; const htmlToText = require('html-to-text'); @@ -189,6 +189,7 @@ export class WebBox extends ViewBoxAnnotatableComponent void) => (this._setBrushViewer = func); brushView = (view: { width: number; height: number; panX: number; panY: number }) => this._setBrushViewer?.(view); - scrollFocus = (doc: Doc, options: DocFocusOptions) => { - if (this._url && StrCast(doc.webUrl) !== this._url) this.submitURL(StrCast(doc.webUrl), options.preview); - if (DocListCast(this.props.Document[this.fieldKey + '-sidebar']).includes(doc) && !this.SidebarShown) { + focus = (doc: Doc, options: DocFocusOptions) => { + !doc.unrendered && this.props.DocumentView?.() && this.scrollFocus(this.props.DocumentView?.(), doc, {}); + this.props.focus(doc, options); + }; + scrollFocus = (docView: DocumentView, anchor: Doc, options: DocFocusOptions) => { + if (this._url && StrCast(anchor.webUrl) !== this._url) this.submitURL(StrCast(anchor.webUrl), options.preview); + if (DocListCast(this.props.Document[this.fieldKey + '-sidebar']).includes(anchor) && !this.SidebarShown) { this.toggleSidebar(options.preview); } - if (this._sidebarRef?.current?.makeDocUnfiltered(doc)) return 1; - if (doc !== this.rootDoc && this._outerRef.current) { + if (this._sidebarRef?.current?.makeDocUnfiltered(anchor)) return 1; + if (anchor !== this.rootDoc && this._outerRef.current) { const windowHeight = this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1); - const scrollTo = Utils.scrollIntoView(NumCast(doc.y), doc[HeightSym](), NumCast(this.layoutDoc._scrollTop), windowHeight, windowHeight * 0.1, Math.max(NumCast(doc.y) + doc[HeightSym](), this._scrollHeight)); + const scrollTo = !anchor[HeightSym]() + ? NumCast(anchor.y) + : Utils.scrollIntoView(NumCast(anchor.y), anchor[HeightSym](), NumCast(this.layoutDoc._scrollTop), windowHeight, windowHeight * 0.1, Math.max(NumCast(anchor.y) + anchor[HeightSym](), this._scrollHeight)); + const focusSpeed = options.instant ? 0 : options.zoomTime ?? 500; if (scrollTo !== undefined && this._initialScroll === undefined) { - const focusSpeed = options.instant ? 0 : options.zoomTime ?? 500; this.goTo(scrollTo, focusSpeed, options.easeFunc); + PresBox.restoreTargetDocView(docView, anchor, focusSpeed); return focusSpeed; } else if (!this._webPageHasBeenRendered || !this._scrollHeight || this._initialScroll !== undefined) { this._initialScroll = scrollTo; + return PresBox.restoreTargetDocView(docView, anchor, focusSpeed) ? focusSpeed : undefined; } } return undefined; }; - getAnchor = (addAsAnnotation: boolean) => { + getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => { let ele: Opt = undefined; try { const contents = this._iframe?.contentWindow?.getSelection()?.getRangeAt(0).cloneContents(); @@ -330,9 +339,11 @@ export class WebBox extends ViewBoxAnnotatableComponent { + const layoutFrameNumber = Cast(dv.props.ContainingCollectionDoc?._currentFrame, 'number'); // frame number that container is at which determines layout frame values + const contentFrameNumber = Cast(dv.rootDoc?._currentFrame, 'number', layoutFrameNumber ?? null); // frame number that content is at which determines what content is displayed + if (contentFrameNumber !== undefined) { + CollectionFreeFormDocumentView.setStringValues(contentFrameNumber, dv.rootDoc, { backgroundColor: color }); + } else { + dv.rootDoc._backgroundColor = color; + } + }); + } else { + const selected = SelectionManager.Docs().length ? SelectionManager.Docs() : LinkManager.currentLink ? [LinkManager.currentLink] : []; + if (checkResult) { + return selected.lastElement()?._backgroundColor ?? 'transparent'; + } + selected.forEach(doc => (doc._backgroundColor = color)); } - if (selected) selected._backgroundColor = color; }); // toggle: Set overlay status of selected document diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 788de7af9..269438a7c 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -45,7 +45,7 @@ import { LightboxView } from '../../LightboxView'; import { AnchorMenu } from '../../pdf/AnchorMenu'; import { SidebarAnnos } from '../../SidebarAnnos'; import { StyleProp } from '../../StyleProvider'; -import { DocFocusOptions, DocumentViewInternal, OpenWhere } from '../DocumentView'; +import { DocFocusOptions, DocumentView, DocumentViewInternal, OpenWhere } from '../DocumentView'; import { FieldView, FieldViewProps } from '../FieldView'; import { LinkDocPreview } from '../LinkDocPreview'; import { DashDocCommentView } from './DashDocCommentView'; @@ -932,7 +932,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { + scrollFocus = (docView: DocumentView, textAnchor: Doc, options: DocFocusOptions) => { let didToggle = false; if (DocListCast(this.Document[this.fieldKey + '-sidebar']).includes(textAnchor) && !this.SidebarShown) { this.toggleSidebar(options.preview); diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index 13cbd87eb..45d386436 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -4,9 +4,9 @@ import { Tooltip } from '@material-ui/core'; import { action, computed, IReactionDisposer, observable, ObservableSet, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import { ColorState, SketchPicker } from 'react-color'; -import { AnimationSym, Doc, DocListCast, FieldResult, Opt, StrListCast } from '../../../../fields/Doc'; +import { AnimationSym, Doc, DocListCast, Field, FieldResult, Opt, StrListCast } from '../../../../fields/Doc'; import { Copy, Id } from '../../../../fields/FieldSymbols'; -import { InkTool } from '../../../../fields/InkField'; +import { InkField, InkTool } from '../../../../fields/InkField'; import { List } from '../../../../fields/List'; import { ObjectField } from '../../../../fields/ObjectField'; import { listSpec } from '../../../../fields/Schema'; @@ -34,8 +34,23 @@ import { FieldView, FieldViewProps } from '../FieldView'; import { ScriptingBox } from '../ScriptingBox'; import './PresBox.scss'; import { PresEffect, PresEffectDirection, PresMovement, PresStatus } from './PresEnums'; +import { SerializationHelper } from '../../../util/SerializationHelper'; const { Howl } = require('howler'); +export interface pinDataTypes { + scrollable?: boolean; + pannable?: boolean; + viewType?: boolean; + inkable?: boolean; + filters?: boolean; + pivot?: boolean; + temporal?: boolean; + clippable?: boolean; + dataview?: boolean; + textview?: boolean; + poslayoutview?: boolean; + dataannos?: boolean; +} export interface PinProps { audioRange?: boolean; activeFrame?: number; @@ -45,18 +60,7 @@ export interface PinProps { pinDocLayout?: boolean; // pin layout info (width/height/x/y) pinDocContent?: boolean; // pin data info (scroll/pan/zoom/text) pinAudioPlay?: boolean; // pin audio annotation - pinData?: { - scrollable?: boolean | undefined; - pannable?: boolean | undefined; - viewType?: boolean | undefined; - filters?: boolean | undefined; - temporal?: boolean | undefined; - clippable?: boolean | undefined; - dataview?: boolean | undefined; - textview?: boolean | undefined; - poslayoutview?: boolean | undefined; - dataannos?: boolean | undefined; - }; + pinData?: pinDataTypes; } @observer @@ -318,19 +322,9 @@ export class PresBox extends ViewBoxBaseComponent() { this.onHideDocument(); //Handles hide after/before } }); - static pinDataTypes(target?: Doc): { - scrollable?: boolean; - pannable?: boolean; - viewType?: boolean; - filters?: boolean; - temporal?: boolean; - clippable?: boolean; - dataview?: boolean; - textview?: boolean; - poslayoutview?: boolean; - dataannos?: boolean; - } { + static pinDataTypes(target?: Doc): pinDataTypes { const targetType = target?.type as any; + const inkable = [DocumentType.INK].includes(targetType); const scrollable = [DocumentType.PDF, DocumentType.RTF, DocumentType.WEB].includes(targetType) || target?._viewType === CollectionViewType.Stacking; const pannable = [DocumentType.IMG, DocumentType.PDF].includes(targetType) || (targetType === DocumentType.COL && target?._viewType === CollectionViewType.Freeform); const temporal = [DocumentType.AUDIO, DocumentType.VID].includes(targetType); @@ -340,18 +334,19 @@ export class PresBox extends ViewBoxBaseComponent() { const textview = [DocumentType.RTF].includes(targetType) && target?.activeFrame === undefined; const viewType = targetType === DocumentType.COL; const filters = true; + const pivot = true; const dataannos = false; - return { scrollable, pannable, viewType, filters, temporal, clippable, dataview, textview, poslayoutview, dataannos }; + return { scrollable, pannable, inkable, viewType, pivot, filters, temporal, clippable, dataview, textview, poslayoutview, dataannos }; } @action playAnnotation = (anno: AudioField) => {}; @action - static restoreTargetDocView(bestTargetView: Opt, pinProps: PinProps | undefined, activeItem: Doc, transTime: number, pinDataTypes = this.pinDataTypes(bestTargetView?.rootDoc)) { - if (!bestTargetView) return; - const bestTarget = bestTargetView.rootDoc; + static restoreTargetDocView(bestTargetView: Opt, activeItem: Doc, transTime: number, pinDocLayout: boolean = BoolCast(activeItem.presPinLayout), pinDataTypes?: pinDataTypes, targetDoc?: Doc) { + const bestTarget = bestTargetView?.rootDoc ?? (targetDoc?.unrendered ? DocCast(targetDoc?.annotationOn) : targetDoc); + if (!bestTarget) return; let changed = false; - if (pinProps?.pinDocLayout) { + if (pinDocLayout) { if ( bestTarget.x !== NumCast(activeItem.presX, NumCast(bestTarget.x)) || bestTarget.y !== NumCast(activeItem.presY, NumCast(bestTarget.y)) || @@ -369,33 +364,51 @@ export class PresBox extends ViewBoxBaseComponent() { changed = true; } } - if (pinDataTypes.clippable) { + if (pinDataTypes?.clippable || (!pinDataTypes && activeItem.presPinClipWidth !== undefined)) { if (bestTarget._clipWidth !== activeItem.presPinClipWidth) { bestTarget._clipWidth = activeItem.presPinClipWidth; changed = true; } } - if (pinDataTypes.temporal) { + if (pinDataTypes?.temporal || (!pinDataTypes && activeItem.presStartTime !== undefined)) { if (bestTarget._currentTimecode !== activeItem.presStartTime) { bestTarget._currentTimecode = activeItem.presStartTime; changed = true; } } - if (pinDataTypes.viewType && activeItem.presPinViewType !== undefined) { + if (pinDataTypes?.inkable || (!pinDataTypes && (activeItem.presFillColor !== undefined || activeItem.color !== undefined))) { + if (bestTarget.fillColor !== activeItem.presFillColor) { + Doc.GetProto(bestTarget).fillColor = activeItem.presFillColor; + changed = true; + } + if (bestTarget.color !== activeItem.color) { + Doc.GetProto(bestTarget).color = activeItem.color; + changed = true; + } + } + if ((pinDataTypes?.viewType && activeItem.presPinViewType !== undefined) || (!pinDataTypes && activeItem.presPinViewType !== undefined)) { if (bestTarget._viewType !== activeItem.presPinViewType) { bestTarget._viewType = activeItem.presPinViewType; changed = true; } } - if (pinDataTypes.filters && activeItem.presPinDocFilters !== undefined) { + if ((pinDataTypes?.filters && activeItem.presPinDocFilters !== undefined) || (!pinDataTypes && activeItem.presPinDocFilters !== undefined)) { if (bestTarget.docFilters !== activeItem.presPinDocFilters) { bestTarget.docFilters = ObjectField.MakeCopy(activeItem.presPinDocFilters as ObjectField) || new List([]); changed = true; } } - if (pinDataTypes.scrollable) { + if ((pinDataTypes?.pivot && activeItem.presPinPvitoField !== undefined) || (!pinDataTypes && activeItem.presPinPivotField !== undefined)) { + if (bestTarget.pivotField !== activeItem.presPinPivotField) { + bestTarget.pivotField = activeItem.presPinPivotField; + bestTarget._prevFilterIndex = 1; // need to revisit this...see CollectionTimeView + changed = true; + } + } + + if (pinDataTypes?.scrollable || (!pinDataTypes && activeItem.presPinViewScroll !== undefined)) { if (bestTarget._scrollTop !== activeItem.presPinViewScroll) { bestTarget._scrollTop = activeItem.presPinViewScroll; changed = true; @@ -406,39 +419,59 @@ export class PresBox extends ViewBoxBaseComponent() { } } } - if (pinDataTypes.dataannos) { + if (pinDataTypes?.dataannos || (!pinDataTypes && activeItem.presAnnotations !== undefined)) { const fkey = Doc.LayoutFieldKey(bestTarget); - Doc.GetProto(bestTarget)[fkey + '-annotations'] = new List([...DocListCast(bestTarget[fkey + '-annotations']).filter(doc => doc.unrendered), ...DocListCast(activeItem.presAnnotations)]); + const oldItems = DocListCast(bestTarget[fkey + '-annotations']).filter(doc => doc.unrendered); + const newItems = DocListCast(activeItem.presAnnotations).map(doc => { + doc.hidden = false; + return doc; + }); + const hiddenItems = DocListCast(bestTarget[fkey + '-annotations']) + .filter(doc => !doc.unrendered && !newItems.includes(doc)) + .map(doc => { + doc.hidden = true; + return doc; + }); + const newList = new List([...oldItems, ...hiddenItems, ...newItems]); + Doc.GetProto(bestTarget)[fkey + '-annotations'] = newList; } - if (pinDataTypes.dataview && activeItem.presData !== undefined) { + if ((pinDataTypes?.dataview && activeItem.presData !== undefined) || (!pinDataTypes && activeItem.presData !== undefined)) { bestTarget._dataTransition = `all ${transTime}ms`; const fkey = Doc.LayoutFieldKey(bestTarget); Doc.GetProto(bestTarget)[fkey] = activeItem.presData instanceof ObjectField ? activeItem.presData[Copy]() : activeItem.presData; bestTarget[fkey + '-useAlt'] = activeItem.presUseAlt; setTimeout(() => (bestTarget._dataTransition = undefined), transTime + 10); } - if (pinDataTypes.textview && activeItem.presData !== undefined) Doc.GetProto(bestTarget)[Doc.LayoutFieldKey(bestTarget)] = activeItem.presData instanceof ObjectField ? activeItem.presData[Copy]() : activeItem.presData; - if (pinDataTypes.poslayoutview) { + if ((pinDataTypes?.textview && activeItem.presData !== undefined) || (!pinDataTypes && activeItem.presData !== undefined)) { + Doc.GetProto(bestTarget)[Doc.LayoutFieldKey(bestTarget)] = activeItem.presData instanceof ObjectField ? activeItem.presData[Copy]() : activeItem.presData; + } + if (pinDataTypes?.poslayoutview || (!pinDataTypes && activeItem.presPinLayoutData !== undefined)) { changed = true; + const layoutField = Doc.LayoutFieldKey(bestTarget); + const transitioned = new Set(); StrListCast(activeItem.presPinLayoutData) - .map(str => JSON.parse(str) as { id: string; x: number; y: number; w: number; h: number }) - .forEach(data => { - const doc = DocServer.GetCachedRefField(data.id) as Doc; - doc._dataTransition = `all ${transTime}ms`; - doc.x = data.x; - doc.y = data.y; - doc._width = data.w; - doc._height = data.h; + .map(str => JSON.parse(str) as { id: string; x: number; y: number; back: string; fill: string; w: number; h: number; data: string; text: string }) + .forEach(async data => { + const doc = DocCast(DocServer.GetCachedRefField(data.id)); + if (doc) { + transitioned.add(doc); + const field = !data.data ? undefined : await SerializationHelper.Deserialize(data.data); + const tfield = !data.text ? undefined : await SerializationHelper.Deserialize(data.text); + doc._dataTransition = `all ${transTime}ms`; + doc.x = data.x; + doc.y = data.y; + data.back && (doc._backgroundColor = data.back); + data.fill && (doc._fillColor = data.fill); + doc._width = data.w; + doc._height = data.h; + data.data && (Doc.GetProto(doc).data = field); + data.text && (Doc.GetProto(doc).text = tfield); + Doc.AddDocToList(Doc.GetProto(bestTarget), layoutField, doc); + } }); - setTimeout( - () => - StrListCast(activeItem.presPinLayoutData) - .map(str => JSON.parse(str) as { id: string; x: number; y: number; w: number; h: number }) - .forEach(action(data => ((DocServer.GetCachedRefField(data.id) as Doc)._dataTransition = undefined))), - transTime + 10 - ); + setTimeout(() => Array.from(transitioned).forEach(action(doc => (doc._dataTransition = undefined))), transTime + 10); } - if (pinDataTypes.pannable) { + if (pinDataTypes?.pannable || (!pinDataTypes && (activeItem.presPinViewBounds !== undefined || activeItem.presPinViewX !== undefined || activeItem.presPinViewScale !== undefined))) { const contentBounds = Cast(activeItem.presPinViewBounds, listSpec('number')); if (contentBounds) { const viewport = { panX: (contentBounds[0] + contentBounds[2]) / 2, panY: (contentBounds[1] + contentBounds[3]) / 2, width: contentBounds[2] - contentBounds[0], height: contentBounds[3] - contentBounds[1] }; @@ -460,7 +493,7 @@ export class PresBox extends ViewBoxBaseComponent() { } } if (changed) { - return bestTargetView.setViewTransition('all', transTime); + return bestTargetView?.setViewTransition('all', transTime); } } @@ -499,12 +532,31 @@ export class PresBox extends ViewBoxBaseComponent() { pinDoc.presAnnotations = new List(DocListCast(Doc.GetProto(targetDoc)[fkey + '-annotations']).filter(doc => !doc.unrendered)); } if (pinProps.pinData.textview) pinDoc.presData = targetDoc[Doc.LayoutFieldKey(targetDoc)] instanceof ObjectField ? (targetDoc[Doc.LayoutFieldKey(targetDoc)] as ObjectField)[Copy]() : targetDoc.text; + if (pinProps.pinData.inkable) { + pinDoc.presFillColor = targetDoc.fillColor; + pinDoc.presColor = targetDoc.color; + } if (pinProps.pinData.scrollable) pinDoc.presPinViewScroll = targetDoc._scrollTop; if (pinProps.pinData.clippable) pinDoc.presPinClipWidth = targetDoc._clipWidth; if (pinProps.pinData.poslayoutview) - pinDoc.presPinLayoutData = new List(DocListCast(targetDoc[fkey] as ObjectField).map(d => JSON.stringify({ id: d[Id], x: NumCast(d.x), y: NumCast(d.y), w: NumCast(d._width), h: NumCast(d._height) }))); + pinDoc.presPinLayoutData = new List( + DocListCast(targetDoc[fkey] as ObjectField).map(d => + JSON.stringify({ + id: d[Id], + x: NumCast(d.x), + y: NumCast(d.y), + w: NumCast(d._width), + h: NumCast(d._height), + fill: StrCast(d._fillColor), + back: StrCast(d._backgroundColor), + data: SerializationHelper.Serialize(d.data instanceof ObjectField ? d.data[Copy]() : ''), + text: SerializationHelper.Serialize(d.text instanceof ObjectField ? d.text[Copy]() : ''), + }) + ) + ); if (pinProps.pinData.viewType) pinDoc.presPinViewType = targetDoc._viewType; if (pinProps.pinData.filters) pinDoc.presPinDocFilters = ObjectField.MakeCopy(targetDoc.docFilters as ObjectField); + if (pinProps.pinData.pivot) pinDoc.presPinPivotField = targetDoc._pivotField; if (pinProps.pinData.pannable) { pinDoc.presPinViewX = NumCast(targetDoc._panX); pinDoc.presPinViewY = NumCast(targetDoc._panY); @@ -586,7 +638,7 @@ export class PresBox extends ViewBoxBaseComponent() { const pinDocLayout = (BoolCast(activeItem.presPinLayout) || BoolCast(activeItem.presPinView)) && DocCast(targetDoc.context)?._currentFrame === undefined; if (activeItem.presPinData || activeItem.presPinView || pinDocLayout) { // targetDoc may or may not be displayed. so get the first available document (or alias) view that matches targetDoc and use it - PresBox.restoreTargetDocView(DocumentManager.Instance.getFirstDocumentView(targetDoc), { pinDocLayout }, activeItem, NumCast(activeItem.presTransition, 500)); + // PresBox.restoreTargetDocView(DocumentManager.Instance.getFirstDocumentView(targetDoc), { pinDocLayout }, activeItem, NumCast(activeItem.presTransition, 500), undefined, targetDoc); } }; const finishAndRestoreLayout = () => { @@ -660,16 +712,43 @@ export class PresBox extends ViewBoxBaseComponent() { _exitTrail: Opt<() => void>; PlayTrail = (docs: Doc[]) => { - const savedStates = docs.map(doc => (doc._viewType !== CollectionViewType.Freeform ? undefined : { c: doc, x: NumCast(doc.panX), y: NumCast(doc.panY), s: NumCast(doc.viewScale) })); + const savedStates = docs.map(doc => { + switch (doc.type) { + case DocumentType.COL: + if (doc._viewType === CollectionViewType.Freeform) return { type: CollectionViewType.Freeform, doc, x: NumCast(doc.panX), y: NumCast(doc.panY), s: NumCast(doc.viewScale) }; + break; + case DocumentType.INK: + if (doc.data instanceof InkField) { + return { type: doc.type, doc, data: doc.data?.[Copy](), fillColor: doc.fillColor, color: doc.color, x: NumCast(doc.x), y: NumCast(doc.y) }; + } + } + return undefined; + }); this.startPresentation(0); this._exitTrail = () => { savedStates .filter(savedState => savedState) .map(savedState => { - const { x, y, s, c } = savedState!; - c._panX = x; - c._panY = y; - c._viewScale = s; + switch (savedState?.type) { + case CollectionViewType.Freeform: + { + const { x, y, s, doc } = savedState!; + doc._panX = x; + doc._panY = y; + doc._viewScale = s; + } + break; + case DocumentType.INK: + { + const { data, fillColor, color, x, y, doc } = savedState!; + doc.x = x; + doc.y = y; + doc.data = data; + doc.fillColor = fillColor; + doc.color = color; + } + break; + } }); LightboxView.SetLightboxDoc(undefined); Doc.RemoveDocFromList(Doc.MyOverlayDocs, undefined, this.rootDoc); @@ -1869,7 +1948,7 @@ export class PresBox extends ViewBoxBaseComponent() { ); } - scrollFocus = () => { + scrollFocus = (docView: DocumentView, doc: Doc, options: DocFocusOptions) => { // this.gotoDocument(0); // this.startOrPause(false); return undefined; diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx index 4a7f5d038..fc8f1da49 100644 --- a/src/client/views/pdf/AnchorMenu.tsx +++ b/src/client/views/pdf/AnchorMenu.tsx @@ -41,7 +41,6 @@ export class AnchorMenu extends AntimodeMenu { @observable private highlightColor: string = 'rgba(245, 230, 95, 0.616)'; @observable private _showLinkPopup: boolean = false; - @observable public Highlighting: boolean = false; @observable public Status: 'marquee' | 'annotation' | '' = ''; public onMakeAnchor: () => Opt = () => undefined; // Method to get anchor from text search @@ -121,9 +120,7 @@ export class AnchorMenu extends AntimodeMenu { @action highlightClicked = (e: React.MouseEvent) => { - if (!this.Highlight(this.highlightColor, false, undefined, true) && this.Pinned) { - this.Highlighting = !this.Highlighting; - } + this.Highlight(this.highlightColor, false, undefined, true); AnchorMenu.Instance.fadeOut(true); }; @@ -138,7 +135,7 @@ export class AnchorMenu extends AntimodeMenu { const button = ( diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 2db44788f..9877690f8 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -30,7 +30,7 @@ const _global = (window /* browser */ || global) /* node */ as any; //pdfjsLib.GlobalWorkerOptions.workerSrc = `/assets/pdf.worker.js`; // The workerSrc property shall be specified. -pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://unpkg.com/pdfjs-dist@2.14.305/build/pdf.worker.js'; +pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://unpkg.com/pdfjs-dist@2.16.105/build/pdf.worker.js'; interface IViewerProps extends FieldViewProps { Document: Doc; @@ -179,7 +179,7 @@ export class PDFViewer extends React.Component { let focusSpeed: Opt; if (doc !== this.props.rootDoc && mainCont) { const windowHeight = this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1); - const scrollTo = doc.unrendered ? scrollTop : Utils.scrollIntoView(scrollTop, doc[HeightSym](), NumCast(this.props.layoutDoc._scrollTop), windowHeight, 0.1 * windowHeight, this._scrollHeight); + const scrollTo = doc.unrendered ? scrollTop : Utils.scrollIntoView(scrollTop, doc[HeightSym](), NumCast(this.props.layoutDoc._scrollTop), windowHeight, windowHeight * 0.1, this._scrollHeight); if (scrollTo !== undefined && scrollTo !== this.props.layoutDoc._scrollTop) { if (!this._pdfViewer) this._initialScroll = { loc: scrollTo, easeFunc: options.easeFunc }; else if (!options.instant && !options.preview) this._scrollStopper = smoothScroll((focusSpeed = options.zoomTime ?? 500), mainCont, scrollTo, options.easeFunc, this._scrollStopper); @@ -518,6 +518,10 @@ export class PDFViewer extends React.Component { return this.props.styleProvider?.(doc, props, property); }; + focus = (doc: Doc, options: DocFocusOptions) => { + !doc.unrendered && this.props.DocumentView?.() && this.scrollToAnnotation(doc); + this.props.focus(doc, options); + }; renderAnnotations = (docFilters?: () => string[], mixBlendMode?: any, display?: string) => (
{ PanelWidth={this.panelWidth} ScreenToLocalTransform={this.overlayTransform} dropAction={'alias'} + focus={this.focus} docFilters={docFilters} select={emptyFunction} bringToFront={emptyFunction} diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index df49c32f0..ed5eaa756 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -722,13 +722,22 @@ export namespace Doc { return bestAlias ?? Doc.MakeAlias(doc); } - export async function makeClone(doc: Doc, cloneMap: Map, linkMap: Map, rtfs: { copy: Doc; key: string; field: RichTextField }[], exclusions: string[], dontCreate: boolean, asBranch: boolean): Promise { + export async function makeClone( + doc: Doc, + cloneMap: Map, + linkMap: Map, + rtfs: { copy: Doc; key: string; field: RichTextField }[], + exclusions: string[], + topLevelExclusions: string[], + dontCreate: boolean, + asBranch: boolean + ): Promise { if (Doc.IsBaseProto(doc)) return doc; if (cloneMap.get(doc[Id])) return cloneMap.get(doc[Id])!; const copy = dontCreate ? (asBranch ? Cast(doc.branchMaster, Doc, null) || doc : doc) : new Doc(undefined, true); cloneMap.set(doc[Id], copy); const fieldExclusions = doc.type === DocumentType.MARKER ? exclusions.filter(ex => ex !== 'annotationOn') : exclusions; - const filter = [...fieldExclusions, ...Cast(doc.cloneFieldFilter, listSpec('string'), [])]; + const filter = [...fieldExclusions, ...topLevelExclusions, ...Cast(doc.cloneFieldFilter, listSpec('string'), [])]; await Promise.all( Object.keys(doc).map(async key => { if (filter.includes(key)) return; @@ -739,10 +748,10 @@ export namespace Doc { const list = await Cast(doc[key], listSpec(Doc)); const docs = list && (await DocListCastAsync(list))?.filter(d => d instanceof Doc); if (docs !== undefined && docs.length) { - const clones = await Promise.all(docs.map(async d => Doc.makeClone(d, cloneMap, linkMap, rtfs, exclusions, dontCreate, asBranch))); + const clones = await Promise.all(docs.map(async d => Doc.makeClone(d, cloneMap, linkMap, rtfs, exclusions, [], dontCreate, asBranch))); !dontCreate && assignKey(new List(clones)); } else if (doc[key] instanceof Doc) { - assignKey(key.includes('layout[') ? undefined : key.startsWith('layout') ? (doc[key] as Doc) : await Doc.makeClone(doc[key] as Doc, cloneMap, linkMap, rtfs, exclusions, dontCreate, asBranch)); // reference documents except copy documents that are expanded template fields + assignKey(key.includes('layout[') ? undefined : key.startsWith('layout') ? (doc[key] as Doc) : await Doc.makeClone(doc[key] as Doc, cloneMap, linkMap, rtfs, exclusions, [], dontCreate, asBranch)); // reference documents except copy documents that are expanded template fields } else { !dontCreate && assignKey(ObjectField.MakeCopy(field)); if (field instanceof RichTextField) { @@ -754,17 +763,18 @@ export namespace Doc { }; if (key === 'proto') { if (doc[key] instanceof Doc) { - assignKey(await Doc.makeClone(doc[key] as Doc, cloneMap, linkMap, rtfs, exclusions, dontCreate, asBranch)); + assignKey(await Doc.makeClone(doc[key] as Doc, cloneMap, linkMap, rtfs, exclusions, [], dontCreate, asBranch)); } } else if (key === 'anchor1' || key === 'anchor2') { if (doc[key] instanceof Doc) { - assignKey(await Doc.makeClone(doc[key] as Doc, cloneMap, linkMap, rtfs, exclusions, true, asBranch)); + assignKey(await Doc.makeClone(doc[key] as Doc, cloneMap, linkMap, rtfs, exclusions, [], true, asBranch)); } } else { if (field instanceof RefField) { assignKey(field); } else if (cfield instanceof ComputedField) { - !dontCreate && assignKey(ComputedField.MakeFunction(cfield.script.originalScript)); + !dontCreate && assignKey(cfield[Copy]()); + // ComputedField.MakeFunction(cfield.script.originalScript)); } else if (field instanceof ObjectField) { await copyObjectField(field); } else if (field instanceof Promise) { @@ -776,7 +786,7 @@ export namespace Doc { }) ); for (const link of Array.from(doc[DirectLinksSym])) { - const linkClone = await Doc.makeClone(link, cloneMap, linkMap, rtfs, exclusions, dontCreate, asBranch); + const linkClone = await Doc.makeClone(link, cloneMap, linkMap, rtfs, exclusions, [], dontCreate, asBranch); linkMap.set(link, linkClone); } if (!dontCreate) { @@ -793,7 +803,7 @@ export namespace Doc { export async function MakeClone(doc: Doc, dontCreate: boolean = false, asBranch = false, cloneMap: Map = new Map()) { const linkMap = new Map(); const rtfMap: { copy: Doc; key: string; field: RichTextField }[] = []; - const copy = await Doc.makeClone(doc, cloneMap, linkMap, rtfMap, ['cloneOf', 'branches', 'branchOf', 'context'], dontCreate, asBranch); + const copy = await Doc.makeClone(doc, cloneMap, linkMap, rtfMap, ['cloneOf', 'branches', 'branchOf'], ['context'], dontCreate, asBranch); Array.from(linkMap.entries()).map((links: Doc[]) => LinkManager.Instance.addLink(links[1], true)); rtfMap.map(({ copy, key, field }) => { const replacer = (match: any, attr: string, id: string, offset: any, string: any) => { diff --git a/src/fields/ScriptField.ts b/src/fields/ScriptField.ts index b23732b45..5f02bd73d 100644 --- a/src/fields/ScriptField.ts +++ b/src/fields/ScriptField.ts @@ -185,9 +185,11 @@ export class ComputedField extends ScriptField { const compiled = ScriptField.CompileScript(script, params, false); return compiled.compiled ? new ComputedField(compiled) : undefined; } - public static MakeFunction(script: string, params: object = {}, capturedVariables?: { [name: string]: Doc | string | number | boolean }) { + public static MakeFunction(script: string, params: object = {}, capturedVariables?: { [name: string]: Doc | string | number | boolean }, setterscript?: string) { const compiled = ScriptField.CompileScript(script, params, true, capturedVariables); - return compiled.compiled ? new ComputedField(compiled) : undefined; + const compiledsetter = setterscript ? ScriptField.CompileScript(setterscript, { ...params, value: 'any' }, false, capturedVariables) : undefined; + const compiledsetscript = compiledsetter?.compiled ? compiledsetter : undefined; + return compiled.compiled ? new ComputedField(compiled, compiledsetscript) : undefined; } public static MakeInterpolatedNumber(fieldKey: string, interpolatorKey: string, doc: Doc, curTimecode: number, defaultVal: Opt) { if (!doc[`${fieldKey}-indexed`]) { diff --git a/src/server/DashUploadUtils.ts b/src/server/DashUploadUtils.ts index 33809824f..f461cf3fa 100644 --- a/src/server/DashUploadUtils.ts +++ b/src/server/DashUploadUtils.ts @@ -501,7 +501,7 @@ export namespace DashUploadUtils { const parseExifData = async (source: string) => { const image = await request.get(source, { encoding: null }); - const { data, error } = await new Promise(resolve => { + const { data, error } = await new Promise<{ data: any; error: any }>(resolve => { new ExifImage({ image }, (error, data) => { let reason: Opt = undefined; if (error) { diff --git a/src/server/websocket.ts b/src/server/websocket.ts index 9b91a35a6..68b003496 100644 --- a/src/server/websocket.ts +++ b/src/server/websocket.ts @@ -199,7 +199,7 @@ export namespace WebSocket { return Database.Instance.getDocument(id, callback); } function GetRefField([id, callback]: [string, (result?: Transferable) => void]) { - process.stdout.write(`.`); + process.stdout.write(`+`); GetRefFieldLocal([id, callback]); } -- cgit v1.2.3-70-g09d2 From ed94cc8fb516455dbeea223d0fab25e4fb1c1d78 Mon Sep 17 00:00:00 2001 From: Sophie Zhang Date: Mon, 27 Feb 2023 16:56:02 -0500 Subject: feat: added summarization functionality to WebBox and cleaned up code --- src/client/apis/gpt/Summarization.ts | 20 +++- src/client/views/MainView.tsx | 1 + src/client/views/nodes/PDFBox.tsx | 1 + src/client/views/nodes/WebBox.tsx | 6 ++ .../views/nodes/formattedText/FormattedTextBox.tsx | 1 - src/client/views/pdf/AnchorMenu.tsx | 39 ++++--- src/client/views/pdf/GPTPopup.scss | 91 ++++++++++++++-- src/client/views/pdf/GPTPopup.tsx | 115 +++++++++++++++++++-- src/client/views/pdf/PDFViewer.tsx | 19 ++-- 9 files changed, 243 insertions(+), 50 deletions(-) (limited to 'src/client/views/pdf/PDFViewer.tsx') diff --git a/src/client/apis/gpt/Summarization.ts b/src/client/apis/gpt/Summarization.ts index 931e0e48f..c0dcc0267 100644 --- a/src/client/apis/gpt/Summarization.ts +++ b/src/client/apis/gpt/Summarization.ts @@ -1,22 +1,32 @@ import { Configuration, OpenAIApi } from 'openai'; +/** + * Summarizes the inputted text with OpenAI. + * + * @param text Text to summarize + * @returns Summary of text + */ const gptSummarize = async (text: string) => { text += '.'; + const model = 'text-curie-001'; // Most advanced: text-davinci-003 + const maxTokens = 200; + const temp = 0.8; + try { const configuration = new Configuration({ apiKey: process.env.OPENAI_KEY, }); const openai = new OpenAIApi(configuration); const response = await openai.createCompletion({ - model: 'text-curie-001', - max_tokens: 256, - temperature: 0.7, - prompt: `Summarize this text in one sentence: ${text}`, + model: model, + max_tokens: maxTokens, + temperature: temp, + prompt: `Summarize this text: ${text}`, }); return response.data.choices[0].text; } catch (err) { console.log(err); - return ''; + return 'Error connecting with API.'; } }; diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index da64e7c12..d7603cf5a 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -245,6 +245,7 @@ export class MainView extends React.Component { library.add( ...[ + fa.faExclamationCircle, fa.faEdit, fa.faTrash, fa.faTrashAlt, diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index b88ac113e..40c04c08f 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -485,6 +485,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent this.createTextAnnotation(sel, !sel.isCollapsed ? sel.getRangeAt(0) : undefined); AnchorMenu.Instance.jumpTo(e.clientX * scale + mainContBounds.translateX, e.clientY * scale + mainContBounds.translateY - NumCast(this.layoutDoc._scrollTop) * scale); + // Changing which document to add the annotation to (the currently selected WebBox) + GPTPopup.Instance.setSidebarId(`${this.props.fieldKey}-${this._urlHash}-sidebar`); + GPTPopup.Instance.addDoc = this.sidebarAddDocument; } } }; @@ -780,6 +785,7 @@ export class WebBox extends ViewBoxAnnotatableComponent { + console.log(annotationKey); (doc instanceof Doc ? [doc] : doc).forEach(doc => (doc.webUrl = this._url)); return this.addDocument(doc, annotationKey); }; diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 683ccb9c4..b9327db0d 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -888,7 +888,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { if (this._editorView && this._recording) { this.stopDictation(true); diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx index 6bcfbe4c2..8f9261614 100644 --- a/src/client/views/pdf/AnchorMenu.tsx +++ b/src/client/views/pdf/AnchorMenu.tsx @@ -45,7 +45,7 @@ export class AnchorMenu extends AntimodeMenu { @observable public Highlighting: boolean = false; @observable public Status: 'marquee' | 'annotation' | '' = ''; - // GPT additions (flow 2) + // GPT additions @observable private summarizedText: string = ''; @observable private loadingSummary: boolean = false; @observable private showGPTPopup: boolean = false; @@ -63,7 +63,7 @@ export class AnchorMenu extends AntimodeMenu { }; private selectedText: string = ''; - setSelectedText = (txt: string) => { + public setSelectedText = (txt: string) => { this.selectedText = txt; }; @@ -71,9 +71,6 @@ export class AnchorMenu extends AntimodeMenu { public OnCrop: (e: PointerEvent) => void = unimplementedFunction; public OnClick: (e: PointerEvent) => void = unimplementedFunction; - public OnSummary: (e: PointerEvent) => Promise = () => { - return new Promise(() => {}); - }; public OnAudio: (e: PointerEvent) => void = unimplementedFunction; public StartDrag: (e: PointerEvent, ele: HTMLElement) => void = unimplementedFunction; public StartCropDrag: (e: PointerEvent, ele: HTMLElement) => void = unimplementedFunction; @@ -123,14 +120,27 @@ export class AnchorMenu extends AntimodeMenu { ); } - getGPTSummary = (e: React.PointerEvent) => { - setupMoveUpEvents(this, e, returnFalse, returnFalse, e => this.OnSummary?.(e)); + /** + * Returns a mock summary simulating an API call. + * @returns A Promise that resolves into a string + */ + mockSummarize = async (): Promise => { + return new Promise((resolve, reject) => { + setTimeout(() => { + resolve('Mock summary. This is a summary of the highlighted text.'); + }, 1000); + }); }; + /** + * Invokes the API with the selected text and stores it in the summarized text. + * @param e pointer down event + */ invokeGPT = async (e: React.PointerEvent) => { this.setGPTPopupVis(true); this.setLoading(true); const res = await gptSummarize(this.selectedText); + // const res = await this.mockSummarize(); if (res) { this.setSummarizedText(res); } else { @@ -243,12 +253,15 @@ export class AnchorMenu extends AntimodeMenu { - Summarize with AI
}> - - - + {/* GPT Summarize icon only shows up when text is highlighted, not on marquee selection*/} + {AnchorMenu.Instance.StartCropDrag === unimplementedFunction && ( + Summarize with AI
}> + + + )} + {AnchorMenu.Instance.OnAudio === unimplementedFunction ? null : ( Click to Record Annotation
}> + + + ) : ( +
+ + + +
+ )} +
+ )} + {this.summaryDone && ( +
+ + AI generated responses can contain inaccurate or misleading content.{' '} + + Learn More + +
)}
); diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 324f31f23..408ba07d1 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -25,6 +25,7 @@ import { Annotation } from './Annotation'; import './PDFViewer.scss'; import React = require('react'); import { gptSummarize } from '../../apis/gpt/Summarization'; +import { GPTPopup } from './GPTPopup'; const PDFJSViewer = require('pdfjs-dist/web/pdf_viewer'); const pdfjsLib = require('pdfjs-dist'); const _global = (window /* browser */ || global) /* node */ as any; @@ -41,6 +42,7 @@ interface IViewerProps extends FieldViewProps { fieldKey: string; pdf: Pdfjs.PDFDocumentProxy; url: string; + sidebarAddDoc: (doc: Doc | Doc[], sidebarKey?: string | undefined) => boolean; loaded?: (nw: number, nh: number, np: number) => void; setPdfViewer: (view: PDFViewer) => void; anchorMenuClick?: () => undefined | ((anchor: Doc, summarize?: boolean) => void); @@ -83,19 +85,6 @@ export class PDFViewer extends React.Component { return AnchorMenu.Instance?.GetAnchor; } - // Fields for using GPT to summarize selected text - private _summaryText: string = ''; - setSummaryText = async () => { - try { - const summary = await gptSummarize(this.selectionText()); - this._summaryText = `Summary: ${summary}`; - } catch (err) { - console.log(err); - this._summaryText = 'Failed to fetch summary.'; - } - }; - summaryText = () => this._summaryText; - selectionText = () => this._selectionText; selectionContent = () => this._selectionContent; @@ -435,6 +424,10 @@ export class PDFViewer extends React.Component { this.createTextAnnotation(sel, sel.getRangeAt(0)); AnchorMenu.Instance.jumpTo(e.clientX, e.clientY); } + + // Changing which document to add the annotation to (the currently selected PDF) + GPTPopup.Instance.setSidebarId('data-sidebar'); + GPTPopup.Instance.addDoc = this.props.sidebarAddDoc; }; @action -- cgit v1.2.3-70-g09d2 From 08e15b05cd014f99726826c9db407e738040cdbb Mon Sep 17 00:00:00 2001 From: Sophie Zhang Date: Mon, 27 Feb 2023 17:16:07 -0500 Subject: feat: deleted unnecessary props from MarqueeAnnotator --- src/client/apis/gpt/Summarization.ts | 2 +- src/client/views/MarqueeAnnotator.tsx | 6 ------ src/client/views/pdf/PDFViewer.tsx | 2 -- 3 files changed, 1 insertion(+), 9 deletions(-) (limited to 'src/client/views/pdf/PDFViewer.tsx') diff --git a/src/client/apis/gpt/Summarization.ts b/src/client/apis/gpt/Summarization.ts index c0dcc0267..ba98ad591 100644 --- a/src/client/apis/gpt/Summarization.ts +++ b/src/client/apis/gpt/Summarization.ts @@ -10,7 +10,7 @@ const gptSummarize = async (text: string) => { text += '.'; const model = 'text-curie-001'; // Most advanced: text-davinci-003 const maxTokens = 200; - const temp = 0.8; + const temp = 0.5; try { const configuration = new Configuration({ diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx index 9e9f24393..8757ac7bf 100644 --- a/src/client/views/MarqueeAnnotator.tsx +++ b/src/client/views/MarqueeAnnotator.tsx @@ -28,8 +28,6 @@ export interface MarqueeAnnotatorProps { docView: DocumentView; savedAnnotations: () => ObservableMap; selectionText: () => string; - summaryText: () => string; - setSummaryText: () => Promise; annotationLayer: HTMLDivElement; addDocument: (doc: Doc) => boolean; getPageFromScroll?: (top: number) => number; @@ -52,10 +50,6 @@ export class MarqueeAnnotator extends React.Component { AnchorMenu.Instance.OnCrop = (e: PointerEvent) => this.props.anchorMenuCrop?.(this.highlight('', true), true); AnchorMenu.Instance.OnClick = (e: PointerEvent) => this.props.anchorMenuClick?.()?.(this.highlight(this.props.highlightDragSrcColor ?? 'rgba(173, 216, 230, 0.75)', true)); - AnchorMenu.Instance.OnSummary = async (e: PointerEvent) => { - await this.props.setSummaryText(); - this.props.anchorMenuClick?.()?.(this.highlight('', true, undefined, undefined, true)); - }; AnchorMenu.Instance.OnAudio = unimplementedFunction; AnchorMenu.Instance.Highlight = this.highlight; AnchorMenu.Instance.GetAnchor = (savedAnnotations?: ObservableMap, addAsAnnotation?: boolean) => this.highlight('rgba(173, 216, 230, 0.75)', true, savedAnnotations, true); diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 408ba07d1..6e268c561 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -607,8 +607,6 @@ export class PDFViewer extends React.Component { finishMarquee={this.finishMarquee} savedAnnotations={this.savedAnnotations} selectionText={this.selectionText} - setSummaryText={this.setSummaryText} - summaryText={this.summaryText} annotationLayer={this._annotationLayer.current} mainCont={this._mainCont.current} anchorMenuCrop={this._textSelecting ? undefined : this.crop} -- cgit v1.2.3-70-g09d2 From 5e7989da274606638c96f649e97e9d1a979956f5 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 28 Feb 2023 20:57:01 -0500 Subject: cleaning up of following inPlace links to overlay current inplaceContainer contents instead of deleting them. --- src/client/util/LinkFollower.ts | 8 ++--- src/client/views/DocumentButtonBar.tsx | 2 +- src/client/views/LightboxView.tsx | 11 ++---- src/client/views/PropertiesButtons.tsx | 39 ++++++++++------------ src/client/views/PropertiesView.tsx | 6 ++-- src/client/views/collections/CollectionSubView.tsx | 1 + src/client/views/collections/TabDocView.tsx | 12 ++++++- .../collectionFreeForm/CollectionFreeFormView.tsx | 4 ++- src/client/views/nodes/DocumentView.tsx | 9 ++--- src/client/views/nodes/PDFBox.tsx | 10 +++++- src/client/views/nodes/WebBox.tsx | 9 ++++- .../views/nodes/formattedText/FormattedTextBox.tsx | 8 +++++ src/client/views/pdf/PDFViewer.tsx | 2 +- 13 files changed, 72 insertions(+), 49 deletions(-) (limited to 'src/client/views/pdf/PDFViewer.tsx') diff --git a/src/client/util/LinkFollower.ts b/src/client/util/LinkFollower.ts index bbfd516da..50bc89ed2 100644 --- a/src/client/util/LinkFollower.ts +++ b/src/client/util/LinkFollower.ts @@ -1,11 +1,11 @@ import { action, runInAction } from 'mobx'; import { Doc, DocListCast, Opt } from '../../fields/Doc'; +import { List } from '../../fields/List'; import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../fields/Types'; -import { CollectionViewType, DocumentType } from '../documents/DocumentTypes'; -import { CollectionDockingView } from '../views/collections/CollectionDockingView'; +import { DocumentType } from '../documents/DocumentTypes'; import { DocumentDecorations } from '../views/DocumentDecorations'; import { LightboxView } from '../views/LightboxView'; -import { DocFocusOptions, DocumentViewSharedProps, OpenWhere, OpenWhereMod, ViewAdjustment } from '../views/nodes/DocumentView'; +import { DocFocusOptions, DocumentViewSharedProps, OpenWhere, ViewAdjustment } from '../views/nodes/DocumentView'; import { PresBox } from '../views/nodes/trails'; import { DocumentManager } from './DocumentManager'; import { LinkManager } from './LinkManager'; @@ -35,7 +35,7 @@ export class LinkFollower { const createViewFunc = (doc: Doc, followLoc: string, finished?: Opt<() => void>) => { const createTabForTarget = (didFocus: boolean) => new Promise(res => { - const where = LightboxView.LightboxDoc ? OpenWhere.inPlace : (StrCast(sourceDoc.followLinkLocation, followLoc) as OpenWhere); + const where = LightboxView.LightboxDoc ? OpenWhere.lightbox : (StrCast(sourceDoc.followLinkLocation, followLoc) as OpenWhere); docViewProps.addDocTab(doc, where); setTimeout(() => { const targDocView = DocumentManager.Instance.getFirstDocumentView(doc); // get first document view available within the lightbox if that's open, or anywhere otherwise. diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index 20c35739e..af868ba9c 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -243,7 +243,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
this.props.views().map(view => view?.docView?.toggleFollowLink(undefined, undefined, false)))}> + onClick={undoBatch(e => this.props.views().map(view => view?.docView?.toggleFollowLink(undefined, false)))}>
{followBtn( true, diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx index e531bf71c..c9b1dcfd8 100644 --- a/src/client/views/LightboxView.tsx +++ b/src/client/views/LightboxView.tsx @@ -145,13 +145,6 @@ export class LightboxView extends React.Component { } } public static AddDocTab = (doc: Doc, location: OpenWhere, layoutTemplate?: Doc | string) => { - if (location !== OpenWhere.lightbox) { - const inPlaceView = DocCast(doc.context) ? DocumentManager.Instance.getFirstDocumentView(DocCast(doc.context)) : undefined; - if (inPlaceView) { - inPlaceView.dataDoc[Doc.LayoutFieldKey(inPlaceView.rootDoc)] = new List([doc]); - return true; - } - } SelectionManager.DeselectAll(); return LightboxView.SetLightboxDoc( doc, @@ -318,7 +311,7 @@ export class LightboxView extends React.Component {
{ e.stopPropagation(); LightboxView.LightboxDoc!._fitWidth = !LightboxView.LightboxDoc!._fitWidth; @@ -327,7 +320,7 @@ export class LightboxView extends React.Component {
{ e.stopPropagation(); CollectionDockingView.AddSplit(LightboxView._docTarget || LightboxView._doc!, OpenWhereMod.none); diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx index a152b4948..7e33ee495 100644 --- a/src/client/views/PropertiesButtons.tsx +++ b/src/client/views/PropertiesButtons.tsx @@ -6,6 +6,7 @@ import { Doc, DocListCast, Opt } from '../../fields/Doc'; import { Id } from '../../fields/FieldSymbols'; import { InkField } from '../../fields/InkField'; import { RichTextField } from '../../fields/RichTextField'; +import { ScriptField } from '../../fields/ScriptField'; import { BoolCast, StrCast } from '../../fields/Types'; import { ImageField } from '../../fields/URLField'; import { DocUtils } from '../documents/Documents'; @@ -129,27 +130,21 @@ export class PropertiesButtons extends React.Component<{}, {}> { onClick => { SelectionManager.Views().forEach(dv => { const containerDoc = dv.rootDoc; - containerDoc.followAllLinks = - containerDoc.noShadow = - containerDoc.noHighlighting = - containerDoc._isLinkButton = - containerDoc._fitContentsToBox = - containerDoc._forceActive = - containerDoc._isInPlaceContainer = - !containerDoc._isInPlaceContainer; - containerDoc.followLinkLocation = containerDoc._isInPlaceContainer ? OpenWhere.inPlace : undefined; + //containerDoc.followAllLinks = + // containerDoc.noShadow = + // containerDoc.noHighlighting = + // containerDoc._forceActive = + containerDoc._fitContentsToBox = containerDoc._isInPlaceContainer = !containerDoc._isInPlaceContainer; containerDoc._xPadding = containerDoc._yPadding = containerDoc._isInPlaceContainer ? 10 : undefined; - const menuDoc = DocListCast(dv.dataDoc[dv.props.fieldKey ?? Doc.LayoutFieldKey(containerDoc)]).lastElement(); - if (menuDoc) { - menuDoc.hideDecorations = menuDoc._forceActive = menuDoc._fitContentsToBox = menuDoc._isLinkButton = menuDoc._noShadow = menuDoc.noHighlighting = containerDoc._isInPlaceContainer; - if (!dv.allLinks.find(link => link.anchor1 === menuDoc || link.anchor2 === menuDoc)) { - DocUtils.MakeLink({ doc: dv.rootDoc }, { doc: menuDoc }, 'back link to container'); - } - DocListCast(menuDoc[Doc.LayoutFieldKey(menuDoc)]).forEach(menuItem => { - menuItem.followLinkAudio = menuItem.followAllLinks = menuItem._isLinkButton = true; - menuItem._followLinkLocation = OpenWhere.inPlace; + const containerContents = DocListCast(dv.dataDoc[dv.props.fieldKey ?? Doc.LayoutFieldKey(containerDoc)]); + dv.rootDoc.onClick = ScriptField.MakeScript('{self.data = undefined; documentView.select(false)}', { documentView: 'any' }); + containerContents.forEach(doc => { + DocListCast(doc.links).forEach(link => { + doc.isLinkButton = true; + //doc.followLinkLocation = OpenWhere.inPlace; + link.linkDisplay = false; }); - } + }); }); } ); @@ -309,10 +304,12 @@ export class PropertiesButtons extends React.Component<{}, {}> { docView.setToggleDetail(); break; case 'linkInPlace': - docView.toggleFollowLink('inPlace', false, false); + docView.toggleFollowLink(false, false); + docView.props.Document.followLinkLocation = docView.props.Document._isLinkButton ? OpenWhere.inPlace : undefined; break; case 'linkOnRight': - docView.toggleFollowLink('add:right', false, false); + docView.toggleFollowLink(false, false); + docView.props.Document.followLinkLocation = docView.props.Document._isLinkButton ? OpenWhere.addRight : undefined; break; } }); diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index f7708d801..e9d1433c3 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -1406,7 +1406,7 @@ export class PropertiesView extends React.Component { }); @undoBatch - changeFollowBehavior = action((follow: string) => this.sourceAnchor && (this.sourceAnchor.followLinkLocation = follow)); + changeFollowBehavior = action((follow: Opt) => this.sourceAnchor && (this.sourceAnchor.followLinkLocation = follow)); @undoBatch changeAnimationBehavior = action((behavior: string) => this.sourceAnchor && (this.sourceAnchor.followLinkAnimEffect = behavior)); @@ -1615,8 +1615,8 @@ export class PropertiesView extends React.Component {

Follow by

- this.changeFollowBehavior(e.currentTarget.value === 'Default' ? undefined : e.currentTarget.value)} value={Cast(this.sourceAnchor?.followLinkLocation, 'string', null)}> + diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 27ae3041f..c88ae314e 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -67,6 +67,7 @@ export function CollectionSubView(moreProps?: X) { // to its children which may be templates. // If 'annotationField' is specified, then all children exist on that field of the extension document, otherwise, they exist directly on the data document under 'fieldKey' @computed get dataField() { + if (this.layoutDoc[this.props.fieldKey]) return this.layoutDoc[this.props.fieldKey]; // sets the dataDoc's data field to an empty list if the data field is undefined - prevents issues with addonly // setTimeout changes it outside of the @computed section !this.dataDoc[this.props.fieldKey] && setTimeout(() => !this.dataDoc[this.props.fieldKey] && (this.dataDoc[this.props.fieldKey] = new List())); diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index b75f315ca..01872df84 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -358,7 +358,17 @@ export class TabDocView extends React.Component { if (doc.dockingConfig) return DashboardView.openDashboard(doc); // prettier-ignore switch (whereFields[0]) { - case OpenWhere.inPlace: // fall through to lightbox + case undefined: + case OpenWhere.inPlace: { + if (this.layoutDoc?.isInPlaceContainer) { + const inPlaceView = !doc.annotationOn && DocCast(doc.context) ? DocumentManager.Instance.getFirstDocumentView(DocCast(doc.context)) : undefined; + const data = inPlaceView?.dataDoc[Doc.LayoutFieldKey(inPlaceView.rootDoc)]; + if (inPlaceView && (!data || data instanceof List)) { + inPlaceView.layoutDoc[Doc.LayoutFieldKey(inPlaceView.rootDoc)] = new List([doc]); + return true; + } + } + } case OpenWhere.lightbox: return LightboxView.AddDocTab(doc, location); case OpenWhere.dashboard: return DashboardView.openDashboard(doc); case OpenWhere.fullScreen: return CollectionDockingView.OpenFullScreen(doc); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 0477c6a16..78804b070 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1395,6 +1395,7 @@ export class CollectionFreeFormView extends CollectionSubView { + if (this.props.isAnnotationOverlay) return this.props.addDocTab(doc, where); switch (where) { case OpenWhere.inParent: return this.props.addDocument?.(doc) || false; @@ -1412,9 +1413,10 @@ export class CollectionFreeFormView extends CollectionSubView(doc instanceof Doc ? [doc] : doc); + this.layoutDoc[this.props.fieldKey] = new List(doc instanceof Doc ? [doc] : doc); return true; } } diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 039a75a1b..beb0f8e3a 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -792,7 +792,7 @@ export class DocumentViewInternal extends DocComponent, zoom?: boolean, setTargetToggle?: boolean): void => { + toggleFollowLink = (zoom?: boolean, setTargetToggle?: boolean): void => { this.Document.ignoreClick = false; if (setTargetToggle) { this.Document.followLinkToggle = !this.Document.followLinkToggle; @@ -802,7 +802,6 @@ export class DocumentViewInternal extends DocComponent (this.Document.ignoreClick = !this.Document.ignoreClick), icon: this.Document.ignoreClick ? 'unlock' : 'lock' }); - onClicks.push({ description: this.Document.isLinkButton ? 'Remove Follow Behavior' : 'Follow Link in Place', event: () => this.toggleFollowLink('inPlace', false, false), icon: 'link' }); - !this.Document.isLinkButton && onClicks.push({ description: 'Follow Link on Right', event: () => this.toggleFollowLink('add:right', false, false), icon: 'link' }); - onClicks.push({ description: this.Document.isLinkButton || this.onClickHandler ? 'Remove Click Behavior' : 'Follow Link', event: () => this.toggleFollowLink(undefined, false, false), icon: 'link' }); - onClicks.push({ description: (this.Document.followLinkToggle ? 'Remove' : 'Make') + ' Target Visibility Toggle', event: () => this.toggleFollowLink(undefined, false, true), icon: 'map-pin' }); + onClicks.push({ description: this.Document.isLinkButton || this.onClickHandler ? 'Remove Click Behavior' : 'Follow Link', event: () => this.toggleFollowLink(false, false), icon: 'link' }); + onClicks.push({ description: (this.Document.followLinkToggle ? 'Remove' : 'Make') + ' Target Visibility Toggle', event: () => this.toggleFollowLink(false, true), icon: 'map-pin' }); onClicks.push({ description: 'Edit onClick Script', event: () => UndoManager.RunInBatch(() => DocUtils.makeCustomViewClicked(this.props.Document, undefined, 'onClick'), 'edit onClick'), icon: 'terminal' }); !existingOnClick && cm.addItem({ description: 'OnClick...', addDivider: true, noexpand: true, subitems: onClicks, icon: 'mouse-pointer' }); } else if (DocListCast(this.Document.links).length) { diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 8d7ab2f0d..6b2f2246a 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -22,7 +22,7 @@ import { LightboxView } from '../LightboxView'; import { CreateImage } from '../nodes/WebBoxRenderer'; import { PDFViewer } from '../pdf/PDFViewer'; import { SidebarAnnos } from '../SidebarAnnos'; -import { DocFocusOptions, DocumentView } from './DocumentView'; +import { DocFocusOptions, DocumentView, OpenWhere } from './DocumentView'; import { FieldView, FieldViewProps } from './FieldView'; import { ImageBox } from './ImageBox'; import './PDFBox.scss'; @@ -200,6 +200,13 @@ export class PDFBox extends ViewBoxAnnotatableComponent this._pdfViewer?.brushView(view); + sidebarAddDocTab = (doc: Doc, where: OpenWhere) => { + if (DocListCast(this.props.Document[this.props.fieldKey + '-sidebar']).includes(doc) && !this.SidebarShown) { + this.toggleSidebar(false); + return true; + } + return this.props.addDocTab(doc, where); + }; scrollFocus = (docView: DocumentView, anchor: Doc, options: DocFocusOptions) => { let didToggle = false; if (DocListCast(this.props.Document[this.fieldKey + '-sidebar']).includes(anchor) && !this.SidebarShown) { @@ -487,6 +494,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent { + if (DocListCast(this.props.Document[this.props.fieldKey + '-sidebar']).includes(doc) && !this.SidebarShown) { + this.toggleSidebar(false); + return true; + } + return this.props.addDocTab(doc, where); + }; getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => { let ele: Opt = undefined; try { diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 123499647..dc7a11e6e 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -230,6 +230,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { + if (DocListCast(this.props.Document[this.props.fieldKey + '-sidebar']).includes(doc) && !this.SidebarShown) { + this.toggleSidebar(false); + return true; + } + return this.props.addDocTab(doc, where); + }; + getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => { if (!pinProps) return this.rootDoc; const anchor = Docs.Create.TextanchorDocument({ annotationOn: this.rootDoc, unrendered: true }); diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 9877690f8..ffc49e4f3 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -16,7 +16,7 @@ import { SnappingManager } from '../../util/SnappingManager'; import { MarqueeOptionsMenu } from '../collections/collectionFreeForm'; import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView'; import { MarqueeAnnotator } from '../MarqueeAnnotator'; -import { DocFocusOptions, DocumentViewProps } from '../nodes/DocumentView'; +import { DocFocusOptions, DocumentViewProps, OpenWhere } from '../nodes/DocumentView'; import { FieldViewProps } from '../nodes/FieldView'; import { LinkDocPreview } from '../nodes/LinkDocPreview'; import { StyleProp } from '../StyleProvider'; -- cgit v1.2.3-70-g09d2 From f189ce6f25b91fcd402b7e81ba8ed378e39e6142 Mon Sep 17 00:00:00 2001 From: Sophie Zhang Date: Wed, 1 Mar 2023 23:33:01 -0500 Subject: Added text completion --- src/client/apis/gpt/Summarization.ts | 45 ++++--- .../nodes/formattedText/FormattedTextBox.scss | 4 + .../views/nodes/formattedText/FormattedTextBox.tsx | 129 ++++++++++++++++----- src/client/views/pdf/AnchorMenu.tsx | 26 ++++- src/client/views/pdf/PDFViewer.tsx | 1 - 5 files changed, 154 insertions(+), 51 deletions(-) (limited to 'src/client/views/pdf/PDFViewer.tsx') diff --git a/src/client/apis/gpt/Summarization.ts b/src/client/apis/gpt/Summarization.ts index ba98ad591..b65736237 100644 --- a/src/client/apis/gpt/Summarization.ts +++ b/src/client/apis/gpt/Summarization.ts @@ -1,27 +1,41 @@ import { Configuration, OpenAIApi } from 'openai'; +enum GPTCallType { + SUMMARY = 'summary', + COMPLETION = 'completion', +} + +type GPTCallOpts = { + model: string; + maxTokens: number; + temp: number; + prompt: string; +}; + +const callTypeMap: { [type: string]: GPTCallOpts } = { + summary: { model: 'text-davinci-003', maxTokens: 100, temp: 0.5, prompt: 'Summarize this text: ' }, + completion: { model: 'text-davinci-003', maxTokens: 100, temp: 0.5, prompt: '' }, +}; + /** - * Summarizes the inputted text with OpenAI. - * - * @param text Text to summarize - * @returns Summary of text + * Calls the OpenAI API. + * + * @param inputText Text to process + * @returns AI Output */ -const gptSummarize = async (text: string) => { - text += '.'; - const model = 'text-curie-001'; // Most advanced: text-davinci-003 - const maxTokens = 200; - const temp = 0.5; - +const gptAPICall = async (inputText: string, callType: GPTCallType) => { + if (callType === GPTCallType.SUMMARY) inputText += '.'; + const opts: GPTCallOpts = callTypeMap[callType]; try { const configuration = new Configuration({ apiKey: process.env.OPENAI_KEY, }); const openai = new OpenAIApi(configuration); const response = await openai.createCompletion({ - model: model, - max_tokens: maxTokens, - temperature: temp, - prompt: `Summarize this text: ${text}`, + model: opts.model, + max_tokens: opts.maxTokens, + temperature: opts.temp, + prompt: `${opts.prompt}${inputText}`, }); return response.data.choices[0].text; } catch (err) { @@ -30,4 +44,5 @@ const gptSummarize = async (text: string) => { } }; -export { gptSummarize }; + +export { gptAPICall, GPTCallType}; diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss index cbe0a465d..fd7fbb333 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.scss +++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss @@ -149,6 +149,10 @@ audiotag:hover { } } +.gpt-typing-wrapper { + padding: 10px; +} + // .menuicon { // display: inline-block; // border-right: 1px solid rgba(0, 0, 0, 0.2); diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index b9327db0d..35c845deb 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -64,12 +64,21 @@ import { SummaryView } from './SummaryView'; import applyDevTools = require('prosemirror-dev-tools'); import React = require('react'); import { Configuration, OpenAIApi } from 'openai'; +import { gptAPICall, GPTCallType } from '../../../apis/gpt/Summarization'; +import ReactLoading from 'react-loading'; +import Typist from 'react-typist'; const translateGoogleApi = require('translate-google-api'); export const GoogleRef = 'googleDocId'; type PullHandler = (exportState: Opt, dataDoc: Doc) => void; +enum GPTStatus { + LOADING, + TYPING, + NONE, +} + @observer export class FormattedTextBox extends ViewBoxAnnotatableComponent() { public static LayoutString(fieldStr: string) { @@ -172,6 +181,19 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { + this.gptStatus = status; + }; + public static PasteOnLoad: ClipboardEvent | undefined; public static SelectOnLoad = ''; public static DontSelectInitialText = false; // whether initial text should be selected or not @@ -840,14 +862,48 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent this.generateImage(), icon: 'star' }); + optionItems.push({ description: `Ask GPT-3`, event: () => this.askGPT(), icon: 'lightbulb' }); 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' }); !options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'eye' }); this._downX = this._downY = Number.NaN; }; + mockGPT = async (): Promise => { + return new Promise((resolve, reject) => { + setTimeout(() => { + resolve('Mock GPT Call.'); + }, 2000); + }); + }; + + askGPT = action(async () => { + try { + this.gptPrompt = (this.dataDoc.text as RichTextField)?.Text; + this.setGPTStatus(GPTStatus.LOADING); + let res = await gptAPICall((this.dataDoc.text as RichTextField)?.Text, GPTCallType.COMPLETION); + // let res = await this.mockGPT(); + if (res) { + this.gptRes = res; + this.setGPTStatus(GPTStatus.TYPING); + } else { + this.setGPTStatus(GPTStatus.NONE); + } + } catch (err) { + console.log(err); + this.dataDoc.text = (this.dataDoc.text as RichTextField)?.Text + 'Something went wrong'; + this.setGPTStatus(GPTStatus.NONE); + } + }); + + setGPTText = action(() => { + this.dataDoc.text = (this.dataDoc.text as RichTextField)?.Text + this.gptRes; + this.gptRes = ''; + this.setGPTStatus(GPTStatus.NONE); + }); + generateImage = async () => { - console.log("Generate image from text: ", (this.dataDoc.text as RichTextField)?.Text); + console.log('Generate image from text: ', (this.dataDoc.text as RichTextField)?.Text); try { const configuration = new Configuration({ apiKey: process.env.OPENAI_KEY, @@ -856,17 +912,17 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { if (this._editorView && this._recording) { @@ -1942,29 +1997,45 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent -
+ {this.gptStatus === GPTStatus.NONE || this.gptStatus === GPTStatus.LOADING ? (
-
+ onScroll={this.onScroll} + onDrop={this.ondrop}> +
+
+ ) : ( +
+
{this.gptPrompt}
+
+ { + this.setGPTText(); + }}> +
{this.gptRes}
+
+
+ )} {this.noSidebar || this.props.dontSelectOnLoad || !this.SidebarShown || this.sidebarWidthPercent === '0%' ? null : this.sidebarCollection} {this.noSidebar || this.Document._noSidebar || this.props.dontSelectOnLoad || this.Document._singleLine ? null : this.sidebarHandle} {this.audioHandle} diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx index 8f9261614..04904b3b1 100644 --- a/src/client/views/pdf/AnchorMenu.tsx +++ b/src/client/views/pdf/AnchorMenu.tsx @@ -10,7 +10,7 @@ import { SelectionManager } from '../../util/SelectionManager'; import { AntimodeMenu, AntimodeMenuProps } from '../AntimodeMenu'; import { LinkPopup } from '../linking/LinkPopup'; import { ButtonDropdown } from '../nodes/formattedText/RichTextMenu'; -import { gptSummarize } from '../../apis/gpt/Summarization'; +import { gptAPICall, GPTCallType } from '../../apis/gpt/Summarization'; import { GPTPopup } from './GPTPopup'; import './AnchorMenu.scss'; @@ -136,16 +136,17 @@ export class AnchorMenu extends AntimodeMenu { * Invokes the API with the selected text and stores it in the summarized text. * @param e pointer down event */ - invokeGPT = async (e: React.PointerEvent) => { + gptSummarize = async (e: React.PointerEvent) => { this.setGPTPopupVis(true); this.setLoading(true); - const res = await gptSummarize(this.selectedText); + const res = await gptAPICall(this.selectedText, GPTCallType.SUMMARY); // const res = await this.mockSummarize(); if (res) { this.setSummarizedText(res); } else { this.setSummarizedText('Something went wrong.'); } + this.setLoading(false); }; @@ -243,6 +244,19 @@ export class AnchorMenu extends AntimodeMenu { this.highlightColor = Utils.colorString(col); }; + /** + * Returns whether the selected text can be summarized. The goal is to have + * all selected text available to summarize but its only supported for pdf and web ATM. + * @returns Whether the GPT icon for summarization should appear + */ + canSummarize = (): boolean => { + const docs = SelectionManager.Docs(); + if (docs.length > 0) { + return docs[0].type === 'pdf' || docs[0].type === 'web'; + } + return false; + }; + render() { const buttons = this.Status === 'marquee' ? ( @@ -254,14 +268,14 @@ export class AnchorMenu extends AntimodeMenu { {/* GPT Summarize icon only shows up when text is highlighted, not on marquee selection*/} - {AnchorMenu.Instance.StartCropDrag === unimplementedFunction && ( + {AnchorMenu.Instance.StartCropDrag === unimplementedFunction && this.canSummarize() && ( Summarize with AI
}> - )} - + {AnchorMenu.Instance.OnAudio === unimplementedFunction ? null : ( Click to Record Annotation
}> )} - + {AnchorMenu.Instance.OnAudio === unimplementedFunction ? null : ( Click to Record Annotation
}> )} + {this.canEdit() && ( + Edit text with AI
}> + + + )} Find document to link to selected text
}> - - - ) : ( -
- - - -
- )} -
- )} - {this.summaryDone && ( -
- - AI generated responses can contain inaccurate or misleading content.{' '} - - Learn More - -
- )} -
- ); - } -} diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.scss b/src/client/views/pdf/GPTPopup/GPTPopup.scss new file mode 100644 index 000000000..50fbe5211 --- /dev/null +++ b/src/client/views/pdf/GPTPopup/GPTPopup.scss @@ -0,0 +1,132 @@ +$textgrey: #707070; +$lighttextgrey: #a3a3a3; +$greyborder: #d3d3d3; +$lightgrey: #ececec; +$button: #5b97ff; +$highlightedText: #82e0ff; + +.summary-box { + display: flex; + flex-direction: column; + justify-content: space-between; + background-color: #ffffff; + box-shadow: 0 2px 5px #7474748d; + color: $textgrey; + position: fixed; + bottom: 5px; + right: 5px; + width: 250px; + min-height: 200px; + border-radius: 15px; + padding: 15px; + padding-bottom: 0; + z-index: 999; + + .summary-heading { + display: flex; + align-items: center; + border-bottom: 1px solid $greyborder; + padding-bottom: 5px; + + .summary-text { + font-size: 12px; + font-weight: 500; + } + } + + label { + color: $textgrey; + font-size: 12px; + font-weight: 400; + letter-spacing: 1px; + margin: 0; + padding-right: 5px; + } + + a { + cursor: pointer; + } + + .content-wrapper { + padding-top: 10px; + min-height: 50px; + max-height: 150px; + overflow-y: auto; + } + + .btns-wrapper { + height: 50px; + display: flex; + justify-content: space-between; + align-items: center; + + .summarizing { + display: flex; + align-items: center; + } + } + + button { + font-size: 9px; + padding: 10px; + color: #ffffff; + background-color: $button; + border-radius: 5px; + } + + .text-btn { + &:hover { + background-color: $button; + } + } + + .btn-secondary { + font-size: 8px; + padding: 10px 5px; + background-color: $lightgrey; + color: $textgrey; + &:hover { + background-color: $lightgrey; + } + } + + .icon-btn { + background-color: #ffffff; + padding: 10px; + border-radius: 50%; + color: $button; + border: 1px solid $button; + } + + .ai-warning { + padding: 10px 0; + font-size: 10px; + color: $lighttextgrey; + border-top: 1px solid $greyborder; + } + + .highlighted-text { + background-color: $highlightedText; + } +} + +// Typist CSS +.Typist .Cursor { + display: inline-block; +} +.Typist .Cursor--blinking { + opacity: 1; + animation: blink 1s linear infinite; +} + +@keyframes blink { + 0% { + opacity: 1; + } + 50% { + opacity: 0; + } + 100% { + opacity: 1; + } +} diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx new file mode 100644 index 000000000..91bc0a7ff --- /dev/null +++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx @@ -0,0 +1,186 @@ +import React = require('react'); +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { action, observable } from 'mobx'; +import { observer } from 'mobx-react'; +import ReactLoading from 'react-loading'; +import Typist from 'react-typist'; +import { Doc } from '../../../../fields/Doc'; +import { Docs } from '../../../documents/Documents'; +import './GPTPopup.scss'; + +export enum GPTPopupMode { + SUMMARY, + EDIT, +} + +interface GPTPopupProps { + visible: boolean; + text: string; + loading: boolean; + mode: GPTPopupMode; + callSummaryApi: (e: React.PointerEvent) => Promise; + callEditApi: (e: React.PointerEvent) => Promise; + replaceText: (replacement: string) => void; + highlightRange?: number[]; +} + +@observer +export class GPTPopup extends React.Component { + static Instance: GPTPopup; + + @observable + private done: boolean = false; + @observable + private sidebarId: string = ''; + + @action + public setDone = (done: boolean) => { + this.done = done; + }; + @action + public setSidebarId = (id: string) => { + this.sidebarId = id; + }; + + public addDoc: (doc: Doc | Doc[], sidebarKey?: string | undefined) => boolean = () => false; + + /** + * Transfers the summarization text to a sidebar annotation text document. + */ + private transferToText = () => { + const newDoc = Docs.Create.TextDocument(this.props.text.trim(), { + _width: 200, + _height: 50, + _fitWidth: true, + _autoHeight: true, + }); + this.addDoc(newDoc, this.sidebarId); + }; + + constructor(props: GPTPopupProps) { + super(props); + GPTPopup.Instance = this; + } + + componentDidUpdate = () => { + if (this.props.loading) { + this.setDone(false); + } + }; + + summaryBox = () => ( + <> +
+ {this.heading('SUMMARY')} +
+ {!this.props.loading && + (!this.done ? ( + { + setTimeout(() => { + this.setDone(true); + }, 500); + }}> + {this.props.text} + + ) : ( + this.props.text + ))} +
+
+ {!this.props.loading && ( +
+ {this.done ? ( + <> + + + + ) : ( +
+ Summarizing + + +
+ )} +
+ )} + + ); + + editBox = () => { + const hr = this.props.highlightRange; + return ( + hr && ( + <> +
+ {this.heading('TEXT EDIT SUGGESTIONS')} +
+
+ {this.props.text.slice(0, hr[0])} {this.props.text.slice(hr[0], hr[1])} {this.props.text.slice(hr[1])} +
+
+
+ {!this.props.loading && ( +
+ <> + + + +
+ )} + {this.aiWarning()} + + ) + ); + }; + + aiWarning = () => + this.done ? ( +
+ + AI generated responses can contain inaccurate or misleading content. +
+ ) : ( + <> + ); + + heading = (headingText: string) => ( +
+ + {this.props.loading && } +
+ ); + + render() { + return ( +
+ {this.props.mode === GPTPopupMode.SUMMARY ? this.summaryBox() : this.editBox()} +
+ ); + } +} diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 3f891789a..d17d0e13c 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -24,7 +24,7 @@ import { AnchorMenu } from './AnchorMenu'; import { Annotation } from './Annotation'; import './PDFViewer.scss'; import React = require('react'); -import { GPTPopup } from './GPTPopup'; +import { GPTPopup } from './GPTPopup/GPTPopup'; const PDFJSViewer = require('pdfjs-dist/web/pdf_viewer'); const pdfjsLib = require('pdfjs-dist'); const _global = (window /* browser */ || global) /* node */ as any; -- cgit v1.2.3-70-g09d2 From 650cc00d18c97f89aef77f50703aa9b525dd0368 Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 14 Apr 2023 11:12:49 -0400 Subject: got rid of CollectionView prop. fixed DocFocusOrOpen to toggle targets and zoom correctly. --- src/client/util/DocumentManager.ts | 12 +++++++----- .../util/Import & Export/DirectoryImportBox.tsx | 2 +- src/client/util/LinkFollower.ts | 2 +- src/client/views/MainView.tsx | 1 - src/client/views/PropertiesDocBacklinksSelector.tsx | 2 +- src/client/views/PropertiesDocContextSelector.tsx | 21 +-------------------- src/client/views/SidebarAnnos.tsx | 1 - src/client/views/StyleProvider.tsx | 4 ++-- src/client/views/TemplateMenu.tsx | 1 - src/client/views/collections/CollectionMenu.tsx | 1 - .../views/collections/CollectionNoteTakingView.tsx | 2 +- .../views/collections/CollectionStackingView.tsx | 2 +- src/client/views/collections/CollectionSubView.tsx | 6 +++--- src/client/views/collections/CollectionTreeView.tsx | 6 +++--- src/client/views/collections/CollectionView.tsx | 1 - src/client/views/collections/TabDocView.tsx | 1 - src/client/views/collections/TreeView.tsx | 6 +++--- .../collectionFreeForm/CollectionFreeFormView.tsx | 11 +++++++---- src/client/views/linking/LinkMenuItem.tsx | 2 +- src/client/views/nodes/AudioBox.tsx | 1 - src/client/views/nodes/DocumentView.tsx | 14 +++++++------- src/client/views/nodes/ImageBox.tsx | 1 - src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx | 1 - src/client/views/nodes/PDFBox.tsx | 1 - src/client/views/nodes/ScreenshotBox.tsx | 1 - src/client/views/nodes/VideoBox.tsx | 2 -- src/client/views/nodes/WebBox.tsx | 1 - .../views/nodes/formattedText/FormattedTextBox.tsx | 1 - src/client/views/pdf/PDFViewer.tsx | 1 - 29 files changed, 39 insertions(+), 69 deletions(-) (limited to 'src/client/views/pdf/PDFViewer.tsx') diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index ad89e8653..e01457b4f 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -227,6 +227,7 @@ export class DocumentManager { let rootContextView = docViewPath.shift(); await (rootContextView && this.focusViewsInPath(rootContextView, options, async () => ({ childDocView: docViewPath.shift(), viewSpec: undefined }))); if (options.toggleTarget && (!options.didMove || targetDocView.rootDoc.hidden)) targetDocView.rootDoc.hidden = !targetDocView.rootDoc.hidden; + else if (options.openLocation?.startsWith(OpenWhere.toggle) && !options.didMove && rootContextView) MainView.addDocTabFunc(rootContextView.rootDoc, options.openLocation); }; // shows a document by first: @@ -246,10 +247,9 @@ export class DocumentManager { const viewIndex = docContextPath.findIndex(doc => this.getDocumentView(doc)); if (viewIndex !== -1) return res(this.getDocumentView(docContextPath[viewIndex])!); options.didMove = true; - docContextPath.some(doc => TabDocView.Activate(doc)) || MainView.addDocTabFunc(docContextPath[0], options.openLocation as OpenWhere); + docContextPath.some(doc => TabDocView.Activate(doc)) || MainView.addDocTabFunc(docContextPath[0], options.openLocation ?? OpenWhere.addRight); this.AddViewRenderedCb(docContextPath[0], dv => res(dv)); }); - docContextPath.shift(); const childViewIterator = async (docView: DocumentView) => { const innerDoc = docContextPath.shift(); @@ -297,15 +297,17 @@ export class DocumentManager { } } } -export function DocFocusOrOpen(doc: Doc, options: DocFocusOptions = { willZoomCentered: true, openLocation: OpenWhere.addRight }, containingDoc?: Doc) { +export function DocFocusOrOpen(doc: Doc, options: DocFocusOptions = { willZoomCentered: true, zoomScale: 0, openLocation: OpenWhere.toggleRight }, containingDoc?: Doc) { const func = () => { const cv = DocumentManager.Instance.getDocumentView(containingDoc); const dv = DocumentManager.Instance.getDocumentView(doc, cv); if (dv && (!containingDoc || dv.props.docViewPath().lastElement()?.Document === containingDoc)) { DocumentManager.Instance.showDocumentView(dv, options).then(() => dv && Doc.linkFollowHighlight(dv.rootDoc)); } else { - const showDoc = Doc.BestAlias(DocCast((containingDoc ?? doc.context) !== Doc.MyFilesystem ? containingDoc ?? doc.context : undefined, doc)); - DocumentManager.Instance.showDocument(showDoc, { ...options, toggleTarget: undefined }, () => DocumentManager.Instance.showDocument(doc, options)).then(() => { + const container = DocCast(containingDoc ?? doc.context); + const showDoc = !Doc.IsSystem(container) ? container : doc; + options.toggleTarget = undefined; + DocumentManager.Instance.showDocument(showDoc, options, () => DocumentManager.Instance.showDocument(doc, { ...options, openLocation: undefined })).then(() => { const cv = DocumentManager.Instance.getDocumentView(containingDoc); const dv = DocumentManager.Instance.getDocumentView(doc, cv); dv && Doc.linkFollowHighlight(dv.rootDoc); diff --git a/src/client/util/Import & Export/DirectoryImportBox.tsx b/src/client/util/Import & Export/DirectoryImportBox.tsx index 76b1323c4..b9bb22564 100644 --- a/src/client/util/Import & Export/DirectoryImportBox.tsx +++ b/src/client/util/Import & Export/DirectoryImportBox.tsx @@ -157,7 +157,7 @@ export class DirectoryImportBox extends React.Component { x: NumCast(doc.x), y: NumCast(doc.y) + offset, }; - const parent = _.nth(this.props.docViewPath(), -2); // last element of path is this box's document view, 2nd to last is any collection or other document that may contain it. + const parent = this.props.DocumentView?.().props.docViewPath().lastElement(); if (parent?.rootDoc.type === DocumentType.COL) { let importContainer: Doc; if (docs.length < 50) { diff --git a/src/client/util/LinkFollower.ts b/src/client/util/LinkFollower.ts index d4d7c66f5..ba2edb65c 100644 --- a/src/client/util/LinkFollower.ts +++ b/src/client/util/LinkFollower.ts @@ -75,7 +75,7 @@ export class LinkFollower { zoomTime: NumCast(sourceDoc.followLinkTransitionTime, 500), zoomScale: Cast(sourceDoc.followLinkZoomScale, 'number', null), easeFunc: StrCast(sourceDoc.followLinkEase, 'ease') as any, - openLocation: StrCast(sourceDoc.followLinkLocation, OpenWhere.lightbox), + openLocation: StrCast(sourceDoc.followLinkLocation, OpenWhere.lightbox) as OpenWhere, effect: sourceDoc, zoomTextSelections: BoolCast(sourceDoc.followLinkZoomText), }; diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 0384d925e..ccc9a7215 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -883,7 +883,6 @@ export class MainView extends React.Component { isSelected={returnFalse} docViewPath={returnEmptyDoclist} moveDocument={this.moveButtonDoc} - CollectionView={undefined} addDocument={this.addButtonDoc} addDocTab={MainView.addDocTabFunc} pinToPres={emptyFunction} diff --git a/src/client/views/PropertiesDocBacklinksSelector.tsx b/src/client/views/PropertiesDocBacklinksSelector.tsx index 00c3400cb..935d551c6 100644 --- a/src/client/views/PropertiesDocBacklinksSelector.tsx +++ b/src/client/views/PropertiesDocBacklinksSelector.tsx @@ -25,7 +25,7 @@ export class PropertiesDocBacklinksSelector extends React.Component { if (!this.props.DocView) return; col = Doc.IsPrototype(col) ? Doc.MakeDelegate(col) : col; - if (col._viewType === CollectionViewType.Freeform) { - col._panX = NumCast(target.x) + NumCast(target._width) / 2; - col._panY = NumCast(target.y) + NumCast(target._height) / 2; - } - col.hidden = false; - //this.props.addDocTab(col, (OpenWhere.toggle + ':' + OpenWhereMod.right) as OpenWhere); - setTimeout( - () => - this.props.DocView && - DocFocusOrOpen( - Doc.GetProto(this.props.DocView.props.Document), - { - // - willZoomCentered: true, - openLocation: (OpenWhere.toggle + ':' + OpenWhereMod.right) as OpenWhere, - }, - col - ), - 100 - ); + DocFocusOrOpen(Doc.GetProto(this.props.DocView.props.Document), undefined, col); }; render() { diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx index 2d2b0f83e..b0aab968e 100644 --- a/src/client/views/SidebarAnnos.tsx +++ b/src/client/views/SidebarAnnos.tsx @@ -243,7 +243,6 @@ export class SidebarAnnos extends React.Component { removeDocument={this.removeDocument} moveDocument={this.moveDocument} addDocument={this.addDocument} - CollectionView={undefined} ScreenToLocalTransform={this.screenToLocalTransform} renderDepth={this.props.renderDepth + 1} viewType={CollectionViewType.Stacking} diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index 739d6d819..5f16e0ebd 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -5,7 +5,7 @@ import { Shadows } from 'browndash-components'; import { action, runInAction } from 'mobx'; import { extname } from 'path'; import { Doc, Opt, StrListCast } from '../../fields/Doc'; -import { BoolCast, Cast, ImageCast, NumCast, StrCast } from '../../fields/Types'; +import { BoolCast, Cast, DocCast, ImageCast, NumCast, StrCast } from '../../fields/Types'; import { DashColor, lightOrDark, Utils } from '../../Utils'; import { CollectionViewType, DocumentType } from '../documents/DocumentTypes'; import { DocFocusOrOpen, DocumentManager } from '../util/DocumentManager'; @@ -383,7 +383,7 @@ export function DashboardStyleProvider(doc: Opt, props: Opt {DashboardToggleButton(doc, 'hidden', 'eye-slash', 'eye', () => { - DocFocusOrOpen(doc, { toggleTarget: true, willPan: true, openLocation: OpenWhere.addRight }, props?.docViewPath().lastElement()?.rootDoc); + DocFocusOrOpen(doc, { toggleTarget: true, willZoomCentered: true, zoomScale: 0 }, DocCast(doc?.context ?? doc?.annotationOn)); })} ); diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx index c5a501aa6..1c816aa05 100644 --- a/src/client/views/TemplateMenu.tsx +++ b/src/client/views/TemplateMenu.tsx @@ -113,7 +113,6 @@ export class TemplateMenu extends React.Component { {templateMenu} { isSelected={returnFalse} docViewPath={returnEmptyDoclist} moveDocument={returnFalse} - CollectionView={undefined} addDocument={returnFalse} addDocTab={returnFalse} pinToPres={emptyFunction} diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx index 80e81bc1c..99d4d0bee 100644 --- a/src/client/views/collections/CollectionNoteTakingView.tsx +++ b/src/client/views/collections/CollectionNoteTakingView.tsx @@ -162,7 +162,7 @@ export class CollectionNoteTakingView extends CollectionSubView() { } @action - moveDocument = (doc: Doc, targetCollection: Doc | undefined, addDocument: (document: Doc) => boolean): boolean => { + moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[], annotationKey?: string) => boolean, annotationKey?: string): boolean => { return this.props.removeDocument?.(doc) && addDocument?.(doc) ? true : false; }; diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 67f5dc9f4..bdad325d5 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -230,7 +230,7 @@ export class CollectionStackingView extends CollectionSubView this.props.isAnyChildContentActive(); @action - moveDocument = (doc: Doc, targetCollection: Doc | undefined, addDocument: (document: Doc) => boolean): boolean => { + moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean): boolean => { return this.props.removeDocument?.(doc) && addDocument?.(doc) ? true : false; }; createRef = (ele: HTMLDivElement | null) => { diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 132ed6fb6..5581ac8fe 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -19,7 +19,6 @@ import { DocComponent } from '../DocComponent'; import React = require('react'); export interface SubCollectionViewProps extends CollectionViewProps { - CollectionView: Opt; isAnyChildContentActive: () => boolean; } @@ -201,8 +200,9 @@ export function CollectionSubView(moreProps?: X) { } } - addDocument = (doc: Doc | Doc[]) => this.props.addDocument?.(doc) || false; - + addDocument = (doc: Doc | Doc[], annotationKey?: string) => this.props.addDocument?.(doc, annotationKey) || false; + removeDocument = (doc: Doc | Doc[], annotationKey?: string) => this.props.removeDocument?.(doc, annotationKey) || false; + moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[], annotationKey?: string) => boolean, annotationKey?: string) => this.props.moveDocument?.(doc, targetCollection, addDocument); @action protected onInternalDrop(e: Event, de: DragManager.DropEvent): boolean { const docDragData = de.complete.docDragData; diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 92932fb61..f81c17a7b 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -374,10 +374,10 @@ export class CollectionTreeView extends CollectionSubView this.props.onChildClick?.() || ScriptCast(this.doc.onChildClick); panelWidth = () => Math.max(0, this.props.PanelWidth() - 2 * this.marginX() * (this.props.NativeDimScaling?.() || 1)); - addAnnotationDocument = (doc: Doc | Doc[]) => this.props.CollectionView?.addDocument(doc, `${this.props.fieldKey}-annotations`) || false; - remAnnotationDocument = (doc: Doc | Doc[]) => this.props.CollectionView?.removeDocument(doc, `${this.props.fieldKey}-annotations`) || false; + addAnnotationDocument = (doc: Doc | Doc[]) => this.addDocument(doc, `${this.props.fieldKey}-annotations`) || false; + remAnnotationDocument = (doc: Doc | Doc[]) => this.removeDocument(doc, `${this.props.fieldKey}-annotations`) || false; moveAnnotationDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[], annotationKey?: string) => boolean) => - this.props.CollectionView?.moveDocument(doc, targetCollection, addDocument, `${this.props.fieldKey}-annotations`) || false; + this.moveDocument(doc, targetCollection, addDocument, `${this.props.fieldKey}-annotations`) || false; @observable _headerHeight = 0; @computed get content() { diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 53fbcc3cc..d7a889362 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -276,7 +276,6 @@ export class CollectionView extends ViewBoxAnnotatableComponent {
boolean; panelWidth: () => number; panelHeight: () => number; - addDocument: (doc: Doc | Doc[], relativeTo?: Doc, before?: boolean) => boolean; + addDocument: (doc: Doc | Doc[], annotationKey?: string, relativeTo?: Doc, before?: boolean) => boolean; removeDoc: ((doc: Doc | Doc[]) => boolean) | undefined; moveDocument: DragManager.MoveFunction; isContentActive: (outsideReaction?: boolean) => boolean; @@ -373,7 +373,7 @@ export class TreeView extends React.Component { dropping: boolean = false; dropDocuments(droppedDocuments: Doc[], before: boolean, inside: number | boolean, dropAction: dropActionType, removeDocument: DragManager.RemoveFunction | undefined, moveDocument: DragManager.MoveFunction | undefined, forceAdd: boolean) { - const parentAddDoc = (doc: Doc | Doc[]) => this.props.addDocument(doc, undefined, before); + const parentAddDoc = (doc: Doc | Doc[]) => this.props.addDocument(doc, undefined, undefined, before); const localAdd = (doc: Doc | Doc[]) => { const innerAdd = (doc: Doc) => { const dataIsComputed = ComputedField.WithoutComputed(() => FieldValue(this.dataDoc[this.fieldKey])) instanceof ComputedField; @@ -1186,7 +1186,7 @@ export class TreeView extends React.Component { }; const indent = i === 0 ? undefined : (editTitle: boolean) => dentDoc(editTitle, docs[i - 1], undefined, treeViewRefs.get(docs[i - 1])); const outdent = !parentCollectionDoc ? undefined : (editTitle: boolean) => dentDoc(editTitle, parentCollectionDoc, containerPrevSibling, parentTreeView instanceof TreeView ? parentTreeView.props.parentTreeView : undefined); - const addDocument = (doc: Doc | Doc[], relativeTo?: Doc, before?: boolean) => add(doc, relativeTo ?? docs[i], before !== undefined ? before : false); + const addDocument = (doc: Doc | Doc[], annotationKey?: string, relativeTo?: Doc, before?: boolean) => add(doc, relativeTo ?? docs[i], before !== undefined ? before : false); const childLayout = Doc.Layout(pair.layout); const rowHeight = () => { const aspect = Doc.NativeAspect(childLayout); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index a3f5e73fb..33fc2ddf3 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -311,7 +311,7 @@ export class CollectionFreeFormView extends CollectionSubView boolean; // whether the root of a template has been selected addDocTab: (doc: Doc, where: OpenWhere) => boolean; filterAddDocument?: (doc: Doc[]) => boolean; // allows a document that renders a Collection view to filter or modify any documents added to the collection (see PresBox for an example) - addDocument?: (doc: Doc | Doc[]) => boolean; - removeDocument?: (doc: Doc | Doc[]) => boolean; - moveDocument?: (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[]) => boolean) => boolean; + addDocument?: (doc: Doc | Doc[], annotationKey?: string) => boolean; + removeDocument?: (doc: Doc | Doc[], annotationKey?: string) => boolean; + moveDocument?: (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[], annotationKey?: string) => boolean) => boolean; pinToPres: (document: Doc, pinProps: PinProps) => void; ScreenToLocalTransform: () => Transform; bringToFront: (doc: Doc, sendToBack?: boolean) => void; diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 22ecaa299..7c98aa6e4 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -478,7 +478,6 @@ export class ImageBox extends ViewBoxAnnotatableComponent <> diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index b144c9318..7a7d4fe37 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -963,7 +963,6 @@ export class VideoBox extends ViewBoxAnnotatableComponent { renderDepth={this.props.renderDepth + 1} isAnnotationOverlay={true} fieldKey={this.props.fieldKey + '-annotations'} - CollectionView={undefined} getScrollHeight={this.getScrollHeight} setPreviewCursor={this.setPreviewCursor} setBrushViewer={this.setBrushViewer} -- cgit v1.2.3-70-g09d2 From 8619a61dd33cdbfa1b8fe3e762306bbf52dbcbad Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 17 Apr 2023 15:06:15 -0400 Subject: fixed text selection on pdfs --- src/client/views/pdf/PDFViewer.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/client/views/pdf/PDFViewer.tsx') diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index d82a7d1ae..b9a22473a 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -422,7 +422,7 @@ export class PDFViewer extends React.Component { const clientRects = selRange.getClientRects(); for (let i = 0; i < clientRects.length; i++) { const rect = clientRects.item(i); - if (rect && rect.width !== this._mainCont.current.clientWidth && rect.width) { + if (rect?.width && rect.width < this._mainCont.current.clientWidth / this.props.ScreenToLocalTransform().Scale) { const scaleX = this._mainCont.current.offsetWidth / boundingRect.width; const pdfScale = NumCast(this.props.layoutDoc._viewScale, 1); const annoBox = document.createElement('div'); -- cgit v1.2.3-70-g09d2 From 4846ff09a02cce1da4f0b8984e387d7d204837f1 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 26 Apr 2023 10:58:58 -0400 Subject: fixed filters - checkboxes generated when options are less than 20, added -undefined- as distinct value option. fixed pointer evcents for docs on pdfs --- src/client/util/CurrentUserUtils.ts | 7 +- src/client/util/DragManager.ts | 18 +- src/client/views/FilterPanel.scss | 12 +- src/client/views/FilterPanel.tsx | 41 +++-- .../collectionFreeForm/CollectionFreeFormView.tsx | 1 + src/client/views/nodes/DocumentView.tsx | 2 + src/client/views/nodes/FilterBox.scss | 189 --------------------- src/client/views/nodes/FilterBox.tsx | 0 .../views/nodes/formattedText/FormattedTextBox.tsx | 6 +- src/client/views/pdf/PDFViewer.tsx | 3 +- src/fields/Doc.ts | 2 +- 11 files changed, 57 insertions(+), 224 deletions(-) delete mode 100644 src/client/views/nodes/FilterBox.scss delete mode 100644 src/client/views/nodes/FilterBox.tsx (limited to 'src/client/views/pdf/PDFViewer.tsx') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 4a90edd49..5475873a9 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -49,6 +49,7 @@ interface Button { ignoreClick?: boolean; buttonText?: string; backgroundColor?: string; + waitForDoubleClickToClick?: boolean; // fields that do not correspond to DocumentOption fields scripts?: { script?: string; onClick?: string; onDoubleClick?: string } @@ -613,8 +614,9 @@ export class CurrentUserUtils { static freeTools(): Button[] { return [ - { title: "Bottom", icon: "arrows-down-to-line",toolTip: "Make doc topmost", btnType: ButtonType.ClickButton, expertMode: false, funcs: {}, scripts: { onClick: 'sendToBack()'}}, // Only when floating document is selected in freeform - { title: "Top", icon: "arrows-up-to-line", toolTip: "Make doc bottommost", btnType: ButtonType.ClickButton, expertMode: false, funcs: {}, scripts: { onClick: 'bringToFront()'}}, // Only when floating document is selected in freeform + { title: "Bottom", icon: "arrows-down-to-line",toolTip: "Make doc topmost", btnType: ButtonType.ClickButton, expertMode: false, funcs: {}, scripts: { onClick: 'sendToBack()'}}, // Only when floating document is selected in freeform + { title: "Top", icon: "arrows-up-to-line", toolTip: "Make doc bottommost", btnType: ButtonType.ClickButton, expertMode: false, funcs: {}, scripts: { onClick: 'bringToFront()'}}, // Only when floating document is selected in freeform + { title: "Z order", icon: "z", toolTip: "Bring Forward on Drag (double click to set for all)",waitForDoubleClickToClick:true, btnType: ButtonType.ToggleButton, expertMode: false, funcs: {}, scripts: { onClick: 'toggleRaiseOnDrag(false, _readOnly_)', onDoubleClick:`{ return toggleRaiseOnDrag(true, _readOnly_)`}}, // Only when floating document is selected in freeform ] } static viewTools(): Button[] { @@ -689,7 +691,6 @@ export class CurrentUserUtils { { title: "Fill", icon: "fill-drip", toolTip: "Background Fill Color",btnType: ButtonType.ColorButton, expertMode: false, ignoreClick: true, width: 20, scripts: { script: 'return setBackgroundColor(value, _readOnly_)'}}, // Only when a document is selected { title: "Header", icon: "heading", toolTip: "Header Color", btnType: ButtonType.ColorButton, expertMode: false, ignoreClick: true, scripts: { script: 'return setHeaderColor(value, _readOnly_)'}}, { title: "Overlay", icon: "layer-group", toolTip: "Overlay", btnType: ButtonType.ToggleButton, expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode, true)'}, scripts: { onClick: 'toggleOverlay(_readOnly_)'}}, // Only when floating document is selected in freeform - { title: "Z order", icon: "z", toolTip: "Bring Forward on Drag",btnType: ButtonType.ToggleButton, expertMode: false, toolType:CollectionViewType.Freeform, funcs: {}, scripts: { onClick: 'toggleRaiseOnDrag(_readOnly_)'}}, // Only when floating document is selected in freeform { title: "Back", icon: "chevron-left", toolTip: "Prev Animation Frame", btnType: ButtonType.ClickButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode)'}, width: 20, scripts: { onClick: 'prevKeyFrame(_readOnly_)'}}, { title: "Num", icon:"",toolTip: "Frame Number (click to toggle edit mode)",btnType: ButtonType.TextButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode)', buttonText: 'selectedDocs()?.lastElement()?.currentFrame?.toString()'}, width: 20, scripts: { onClick: '{ return curKeyFrame(_readOnly_);}'}}, { title: "Fwd", icon: "chevron-right", toolTip: "Next Animation Frame", btnType: ButtonType.ClickButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode)'}, width: 20, scripts: { onClick: 'nextKeyFrame(_readOnly_)'}}, diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 404c85eb2..b6de5604d 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -11,6 +11,7 @@ import * as globalCssVariables from '../views/global/globalCssVariables.scss'; import { Colors } from '../views/global/globalEnums'; import { DocumentView } from '../views/nodes/DocumentView'; import { ScriptingGlobals } from './ScriptingGlobals'; +import { SelectionManager } from './SelectionManager'; import { SnappingManager } from './SnappingManager'; import { UndoManager } from './UndoManager'; @@ -597,7 +598,18 @@ export namespace DragManager { } } -ScriptingGlobals.add(function toggleRaiseOnDrag(readOnly?: boolean) { - if (readOnly) return DragManager.GetRaiseWhenDragged() ? Colors.MEDIUM_BLUE : 'transparent'; - DragManager.SetRaiseWhenDragged(!DragManager.GetRaiseWhenDragged()); +ScriptingGlobals.add(function toggleRaiseOnDrag(forAllDocs: boolean, readOnly?: boolean) { + if (readOnly) { + if (SelectionManager.Views().length) + return SelectionManager.Views().some(dv => dv.rootDoc.raiseWhenDragged) + ? Colors.MEDIUM_BLUE + : SelectionManager.Views().some(dv => dv.rootDoc.raiseWhenDragged === false) + ? 'transparent' + : DragManager.GetRaiseWhenDragged() + ? Colors.MEDIUM_BLUE_ALT + : Colors.PINK; + return DragManager.GetRaiseWhenDragged() ? Colors.PINK : 'transparent'; + } + if (!forAllDocs) SelectionManager.Views().map(dv => (dv.rootDoc.raiseWhenDragged ? (dv.rootDoc.raiseWhenDragged = undefined) : dv.rootDoc.raiseWhenDragged === false ? (dv.rootDoc.raiseWhenDragged = true) : (dv.rootDoc.raiseWhenDragged = false))); + else DragManager.SetRaiseWhenDragged(!DragManager.GetRaiseWhenDragged()); }); diff --git a/src/client/views/FilterPanel.scss b/src/client/views/FilterPanel.scss index 7f907c8d4..c903f29ee 100644 --- a/src/client/views/FilterPanel.scss +++ b/src/client/views/FilterPanel.scss @@ -33,9 +33,10 @@ // } .filterBox-select { - // width: 90%; + display: flex; + width: 100%; margin-top: 5px; - // margin-bottom: 15px; + background: white; } .filterBox-saveBookmark { @@ -150,8 +151,8 @@ .filterBox-treeView { display: flex; flex-direction: column; - width: 200px; - position: absolute; + width: 100%; + position: relative; right: 0; top: 0; z-index: 1; @@ -184,6 +185,7 @@ display: inline-block; width: 100%; margin-bottom: 10px; - //height: calc(100% - 30px); + margin-left: 5px; + overflow: auto; } } diff --git a/src/client/views/FilterPanel.tsx b/src/client/views/FilterPanel.tsx index d35494f26..a237249c1 100644 --- a/src/client/views/FilterPanel.tsx +++ b/src/client/views/FilterPanel.tsx @@ -51,10 +51,12 @@ export class FilterPanel extends React.Component { const keys = new Set(noviceFields); this.allDocs.forEach(doc => SearchBox.documentKeys(doc).filter(key => keys.add(key))); - return Array.from(keys.keys()) + const sortedKeys = Array.from(keys.keys()) .filter(key => key[0]) .filter(key => key[0] === '#' || key.indexOf('lastModified') !== -1 || (key[0] === key[0].toUpperCase() && !key.startsWith('_')) || noviceFields.includes(key) || !Doc.noviceMode) .sort(); + noviceFields.forEach(key => sortedKeys.splice(sortedKeys.indexOf(key), 1)); + return [...noviceFields, ...sortedKeys]; } /** @@ -129,7 +131,7 @@ export class FilterPanel extends React.Component { maxVal = Math.max(num, maxVal); } }); - if (facetHeader === 'text' || (facetValues.rtFields / allCollectionDocs.length > 0.1 && facetValues.rtFields > 20)) { + if (facetHeader === 'text' || (facetValues.rtFields / allCollectionDocs.length > 0.1 && facetValues.strings.length > 20)) { this._chosenFacets.set(facetHeader, 'text'); } else if (facetHeader !== 'tags' && nonNumbers / facetValues.strings.length < 0.1) { } else { @@ -140,7 +142,7 @@ export class FilterPanel extends React.Component { facetValues = (facetHeader: string) => { const allCollectionDocs = new Set(); SearchBox.foreachRecursiveDoc(this.targetDocChildren, (depth: number, doc: Doc) => allCollectionDocs.add(doc)); - const set = new Set(); + const set = new Set([String.fromCharCode(127) + '--undefined--']); if (facetHeader === 'tags') allCollectionDocs.forEach(child => Field.toString(child[facetHeader] as Field) @@ -158,32 +160,29 @@ export class FilterPanel extends React.Component { let nonNumbers = 0; facetValues.map(val => Number.isNaN(Number(val)) && nonNumbers++); - const facetValueDocSet = (nonNumbers / facetValues.length > 0.1 ? facetValues.sort() : facetValues.sort((n1: string, n2: string) => Number(n1) - Number(n2))).map(facetValue => { - return facetValue; - }); - return facetValueDocSet; + return nonNumbers / facetValues.length > 0.1 ? facetValues.sort() : facetValues.sort((n1: string, n2: string) => Number(n1) - Number(n2)); }; render() { const options = this._allFacets.filter(facet => this.currentFacets.indexOf(facet) === -1).map(facet => ({ value: facet, label: facet })); return ( -
-
- -
filters together
-
- +
- this.facetClick((val as UserOptions).value)} onKeyDown={e => e.stopPropagation()} value={null} closeMenuOnSelect={true} /> +
+
+ +
{' '}
-
+
{Array.from(this.activeFacets.keys()).map(facetHeader => (
{facetHeader} diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 3333befc6..719a39e8d 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1972,6 +1972,7 @@ export class CollectionFreeFormView extends CollectionSubView [...this.props.docFilters(), ...StrListCast(this.layoutDoc.docFilters)]; contentPointerEvents = () => (!this.disableClickScriptFunc && this.onClickHandler ? 'none' : this.pointerEvents); @computed get contents() { TraceMobx(); @@ -898,6 +899,7 @@ export class DocumentViewInternal extends DocComponent div, - > div > div { - width: 100%; - height: 100%; - } - } - - .filterBox-tree { - display: inline-block; - width: 100%; - margin-bottom: 10px; - //height: calc(100% - 30px); - } -} diff --git a/src/client/views/nodes/FilterBox.tsx b/src/client/views/nodes/FilterBox.tsx deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 82a26fa86..399e6bfdf 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -852,7 +852,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent this.generateImage(), icon: 'star' }); optionItems.push({ description: `Ask GPT-3`, event: () => this.askGPT(), icon: 'lightbulb' }); - optionItems.push({ description: !this.Document._singleLine ? 'Make Single Line' : 'Make Multi Line', event: () => (this.layoutDoc._singleLine = !this.layoutDoc._singleLine), icon: !this.Document._singleLine ? 'grip-lines' : 'bars' }); + optionItems.push({ + description: !this.Document._singleLine ? 'Create New Doc on Carriage Return' : 'Allow Carriage Returns', + event: () => (this.layoutDoc._singleLine = !this.layoutDoc._singleLine), + icon: !this.Document._singleLine ? 'grip-lines' : 'bars', + }); optionItems.push({ description: `${this.Document._autoHeight ? 'Lock' : 'Auto'} Height`, event: () => (this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight), icon: this.Document._autoHeight ? 'lock' : 'unlock' }); !options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'eye' }); this._downX = this._downY = Number.NaN; diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 68241e61f..20803bba8 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -536,7 +536,8 @@ export class PDFViewer extends React.Component { NativeWidth={returnZero} NativeHeight={returnZero} setContentView={emptyFunction} // override setContentView to do nothing - pointerEvents={SnappingManager.GetIsDragging() ? returnAll : returnNone} + pointerEvents={SnappingManager.GetIsDragging() ? returnAll : returnNone} // freeform view doesn't get events unless something is being dragged onto it. + childPointerEvents={'all'} // but freeform children need to get events to allow text editing, etc renderDepth={this.props.renderDepth + 1} isAnnotationOverlay={true} fieldKey={this.props.fieldKey + '-annotations'} diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index b0033b977..0c2ee35fd 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -1391,7 +1391,7 @@ export namespace Doc { return vals.some(v => v.includes(value)); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring } const fieldStr = Field.toString(fieldVal as Field); - return fieldStr.includes(value); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring + return fieldStr.includes(value) || (value === String.fromCharCode(127) + '--undefined--' && fieldVal === undefined); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring } export function deiconifyView(doc: Doc) { -- cgit v1.2.3-70-g09d2 From dadd7c13064f08fa2220575c0988b4dcadb6abdb Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 27 Apr 2023 23:35:18 -0400 Subject: removing unused code - viewspecscript and x,y in slowloaddocumnts --- src/client/documents/Documents.ts | 5 +-- src/client/views/PreviewCursor.tsx | 24 ++-------- src/client/views/collections/CollectionSubView.tsx | 51 ++++++---------------- .../collections/collectionFreeForm/MarqueeView.tsx | 11 +---- src/client/views/pdf/PDFViewer.tsx | 2 +- 5 files changed, 21 insertions(+), 72 deletions(-) (limited to 'src/client/views/pdf/PDFViewer.tsx') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index b81ca6b2b..2187f8231 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -1207,11 +1207,10 @@ export namespace DocUtils { * @param docs * @param docFilters * @param docRangeFilters - * @param viewSpecScript + * @param parentCollection * Given a list of docs and docFilters, @returns the list of Docs that match those filters */ - export function FilterDocs(docs: Doc[], docFilters: string[], docRangeFilters: string[], viewSpecScript?: ScriptField, parentCollection?: Doc) { - const childDocs = viewSpecScript ? docs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result) : docs; + export function FilterDocs(childDocs: Doc[], docFilters: string[], docRangeFilters: string[], parentCollection?: Doc) { if (!docFilters?.length && !docRangeFilters?.length) { return childDocs.filter(d => !d.cookies); // remove documents that need a cookie if there are no filters to provide one } diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx index 95ae65d7a..c7a603897 100644 --- a/src/client/views/PreviewCursor.tsx +++ b/src/client/views/PreviewCursor.tsx @@ -19,16 +19,7 @@ export class PreviewCursor extends React.Component<{}> { static _addDocument: (doc: Doc | Doc[]) => boolean; static _addLiveTextDoc: (doc: Doc) => void; static _nudge?: undefined | ((x: number, y: number) => boolean); - static _slowLoadDocuments?: ( - files: File[] | string, - options: DocumentOptions, - generatedDocuments: Doc[], - text: string, - completed: ((doc: Doc[]) => void) | undefined, - clientX: number, - clientY: number, - addDocument: (doc: Doc | Doc[]) => boolean - ) => Promise; + static _slowLoadDocuments?: (files: File[] | string, options: DocumentOptions, generatedDocuments: Doc[], text: string, completed: ((doc: Doc[]) => void) | undefined, addDocument: (doc: Doc | Doc[]) => boolean) => Promise; @observable static _clickPoint = [0, 0]; @observable public static Visible = false; constructor(props: any) { @@ -57,7 +48,7 @@ export class PreviewCursor extends React.Component<{}> { x: newPoint[0], y: newPoint[1], }; - PreviewCursor._slowLoadDocuments?.(plain.split('v=')[1].split('&')[0], options, generatedDocuments, '', undefined, newPoint[0], newPoint[1], PreviewCursor._addDocument).then(batch.end); + PreviewCursor._slowLoadDocuments?.(plain.split('v=')[1].split('&')[0], options, generatedDocuments, '', undefined, PreviewCursor._addDocument).then(batch.end); } else if (re.test(plain)) { const url = plain; undoBatch(() => @@ -185,16 +176,7 @@ export class PreviewCursor extends React.Component<{}> { getTransform: () => Transform, addDocument: undefined | ((doc: Doc | Doc[]) => boolean), nudge: undefined | ((nudgeX: number, nudgeY: number) => boolean), - slowLoadDocuments: ( - files: File[] | string, - options: DocumentOptions, - generatedDocuments: Doc[], - text: string, - completed: ((doc: Doc[]) => void) | undefined, - clientX: number, - clientY: number, - addDocument: (doc: Doc | Doc[]) => boolean - ) => Promise + slowLoadDocuments: (files: File[] | string, options: DocumentOptions, generatedDocuments: Doc[], text: string, completed: ((doc: Doc[]) => void) | undefined, addDocument: (doc: Doc | Doc[]) => boolean) => Promise ) { this._clickPoint = [x, y]; this._onKeyPress = onKeyPress; diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 5581ac8fe..5b9453666 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -110,9 +110,7 @@ export function CollectionSubView(moreProps?: X) { rawdocs = rootDoc && !this.props.isAnnotationOverlay ? [Doc.GetProto(rootDoc)] : []; } - const docs = rawdocs.filter(d => !(d instanceof Promise) && GetEffectiveAcl(Doc.GetProto(d)) !== AclPrivate && (this.props.ignoreUnrendered || !d.unrendered)).map(d => d as Doc); - const viewSpecScript = Cast(this.props.Document.viewSpecScript, ScriptField); - const childDocs = viewSpecScript ? docs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result) : docs; + const childDocs = rawdocs.filter(d => !(d instanceof Promise) && GetEffectiveAcl(Doc.GetProto(d)) !== AclPrivate && (this.props.ignoreUnrendered || !d.unrendered)).map(d => d as Doc); const childDocFilters = this.childDocFilters(); const docRangeFilters = this.childDocRangeFilters(); @@ -126,24 +124,23 @@ export function CollectionSubView(moreProps?: X) { // dragging facets const dragged = this.props.docFilters?.().some(f => f.includes(Utils.noDragsDocFilter)); if (dragged && DragManager.docsBeingDragged.includes(d)) return false; - let notFiltered = d.z || Doc.IsSystem(d) || DocUtils.FilterDocs([d], this.unrecursiveDocFilters(), docRangeFilters, viewSpecScript, this.props.Document).length > 0; + let notFiltered = d.z || Doc.IsSystem(d) || DocUtils.FilterDocs([d], this.unrecursiveDocFilters(), docRangeFilters, this.props.Document).length > 0; if (notFiltered) { - notFiltered = (!searchDocs.length || searchDocs.includes(d)) && DocUtils.FilterDocs([d], childDocFilters, docRangeFilters, viewSpecScript, this.props.Document).length > 0; + notFiltered = (!searchDocs.length || searchDocs.includes(d)) && DocUtils.FilterDocs([d], childDocFilters, docRangeFilters, this.props.Document).length > 0; const fieldKey = Doc.LayoutFieldKey(d); - const annos = !Field.toString(Doc.LayoutField(d) as Field).includes('CollectionView'); + const annos = !Field.toString(Doc.LayoutField(d) as Field).includes(CollectionView.name); const data = d[annos ? fieldKey + '-annotations' : fieldKey]; if (data !== undefined) { let subDocs = DocListCast(data); if (subDocs.length > 0) { let newarray: Doc[] = []; - notFiltered = notFiltered || (!searchDocs.length && DocUtils.FilterDocs(subDocs, childDocFilters, docRangeFilters, viewSpecScript, d).length); + notFiltered = notFiltered || (!searchDocs.length && DocUtils.FilterDocs(subDocs, childDocFilters, docRangeFilters, d).length); while (subDocs.length > 0 && !notFiltered) { newarray = []; subDocs.forEach(t => { const fieldKey = Doc.LayoutFieldKey(t); - const annos = !Field.toString(Doc.LayoutField(t) as Field).includes('CollectionView'); - notFiltered = - notFiltered || ((!searchDocs.length || searchDocs.includes(t)) && ((!childDocFilters.length && !docRangeFilters.length) || DocUtils.FilterDocs([t], childDocFilters, docRangeFilters, viewSpecScript, d).length)); + const annos = !Field.toString(Doc.LayoutField(t) as Field).includes(CollectionView.name); + notFiltered = notFiltered || ((!searchDocs.length || searchDocs.includes(t)) && ((!childDocFilters.length && !docRangeFilters.length) || DocUtils.FilterDocs([t], childDocFilters, docRangeFilters, d).length)); DocListCast(t[annos ? fieldKey + '-annotations' : fieldKey]).forEach(newdoc => newarray.push(newdoc)); }); subDocs = newarray; @@ -348,7 +345,7 @@ export function CollectionSubView(moreProps?: X) { if ((uriList || text).includes('www.youtube.com/watch') || text.includes('www.youtube.com/embed')) { const batch = UndoManager.StartBatch('youtube upload'); const generatedDocuments: Doc[] = []; - this.slowLoadDocuments((uriList || text).split('v=')[1].split('&')[0], options, generatedDocuments, text, completed, e.clientX, e.clientY, addDocument).then(batch.end); + this.slowLoadDocuments((uriList || text).split('v=')[1].split('&')[0], options, generatedDocuments, text, completed, addDocument).then(batch.end); return; } @@ -370,18 +367,8 @@ export function CollectionSubView(moreProps?: X) { // } } if (uriList) { - // const existingWebDoc = await Hypothesis.findWebDoc(uriList); - // if (existingWebDoc) { - // const alias = Doc.MakeAlias(existingWebDoc); - // alias.x = options.x; - // alias.y = options.y; - // alias._nativeWidth = 850; - // alias._height = 512; - // alias._width = 400; - // addDocument(alias); - // } else - { - const newDoc = Docs.Create.WebDocument(uriList.split('#annotations:')[0], { + addDocument( + Docs.Create.WebDocument(uriList.split('#annotations:')[0], { // clean hypothes.is URLs that reference a specific annotation (eg. https://en.wikipedia.org/wiki/Cartoon#annotations:t7qAeNbCEeqfG5972KR2Ig) ...options, title: uriList.split('#annotations:')[0], @@ -389,9 +376,8 @@ export function CollectionSubView(moreProps?: X) { _height: 512, _nativeWidth: 850, useCors: true, - }); - addDocument(newDoc); - } + }) + ); return; } @@ -437,19 +423,10 @@ export function CollectionSubView(moreProps?: X) { }); } } - this.slowLoadDocuments(files, options, generatedDocuments, text, completed, e.clientX, e.clientY, addDocument).then(batch.end); + this.slowLoadDocuments(files, options, generatedDocuments, text, completed, addDocument).then(batch.end); } - slowLoadDocuments = async ( - files: File[] | string, - options: DocumentOptions, - generatedDocuments: Doc[], - text: string, - completed: ((doc: Doc[]) => void) | undefined, - clientX: number, - clientY: number, - addDocument: (doc: Doc | Doc[]) => boolean - ) => { + slowLoadDocuments = async (files: File[] | string, options: DocumentOptions, generatedDocuments: Doc[], text: string, completed: ((doc: Doc[]) => void) | undefined, addDocument: (doc: Doc | Doc[]) => boolean) => { // create placeholder docs // inside placeholder docs have some func that diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index e5f47823c..11d466b0f 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -39,16 +39,7 @@ interface MarqueeViewProps { nudge?: (x: number, y: number, nudgeTime?: number) => boolean; ungroup?: () => void; setPreviewCursor?: (func: (x: number, y: number, drag: boolean, hide: boolean) => void) => void; - slowLoadDocuments: ( - files: File[] | string, - options: DocumentOptions, - generatedDocuments: Doc[], - text: string, - completed: ((doc: Doc[]) => void) | undefined, - clientX: number, - clientY: number, - addDocument: (doc: Doc | Doc[]) => boolean - ) => Promise; + slowLoadDocuments: (files: File[] | string, options: DocumentOptions, generatedDocuments: Doc[], text: string, completed: ((doc: Doc[]) => void) | undefined, addDocument: (doc: Doc | Doc[]) => boolean) => Promise; } export interface MarqueeViewBounds { diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 20803bba8..fce67e7fc 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -90,7 +90,7 @@ export class PDFViewer extends React.Component { @observable isAnnotating = false; // key where data is stored @computed get allAnnotations() { - return DocUtils.FilterDocs(DocListCast(this.props.dataDoc[this.props.fieldKey + '-annotations']), this.props.docFilters(), this.props.docRangeFilters(), undefined); + return DocUtils.FilterDocs(DocListCast(this.props.dataDoc[this.props.fieldKey + '-annotations']), this.props.docFilters(), this.props.docRangeFilters()); } @computed get inlineTextAnnotations() { return this.allAnnotations.filter(a => a.textInlineAnnotations); -- cgit v1.2.3-70-g09d2 From dca61505ba138eef3819b16b760ec81becf9329e Mon Sep 17 00:00:00 2001 From: bobzel Date: Sat, 13 May 2023 09:55:48 -0400 Subject: changed EditableViews to support oneline and multiline. Also added transformer UI to allow documents to be entered. changed transformer to write doc id's, not variables.. made schema view support oneline and fixed bug with docdecoration hader occluding things invisibly. updated web pages to be zoomable and for its anchors to update web page and scroll location properly. made autolinkanchor directly go to target on click. --- package-lock.json | 39 +++ src/client/documents/Documents.ts | 4 +- src/client/util/CurrentUserUtils.ts | 5 +- src/client/util/DocumentManager.ts | 24 +- src/client/util/Scripting.ts | 30 +- src/client/views/DocumentDecorations.tsx | 8 +- src/client/views/EditableView.scss | 11 +- src/client/views/EditableView.tsx | 98 ++++-- src/client/views/MarqueeAnnotator.tsx | 3 +- src/client/views/ScriptingRepl.scss | 5 +- src/client/views/ScriptingRepl.tsx | 113 ++++--- .../views/collections/CollectionStackingView.scss | 6 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 18 +- .../collectionSchema/CollectionSchemaView.scss | 4 +- .../collectionSchema/CollectionSchemaView.tsx | 23 +- .../collectionSchema/SchemaColumnHeader.tsx | 1 + .../collections/collectionSchema/SchemaRowBox.tsx | 10 +- .../collectionSchema/SchemaTableCell.tsx | 44 ++- .../views/nodes/CollectionFreeFormDocumentView.tsx | 3 +- src/client/views/nodes/DocumentIcon.tsx | 56 ++-- src/client/views/nodes/DocumentView.tsx | 4 +- src/client/views/nodes/KeyValueBox.tsx | 6 +- src/client/views/nodes/KeyValuePair.scss | 64 ++-- src/client/views/nodes/KeyValuePair.tsx | 8 +- src/client/views/nodes/WebBox.tsx | 359 ++++++++++----------- src/client/views/nodes/button/FontIconBox.tsx | 13 +- .../views/nodes/formattedText/DashFieldView.tsx | 150 ++------- .../views/nodes/formattedText/FormattedTextBox.tsx | 3 +- src/client/views/nodes/formattedText/marks_rts.ts | 2 +- src/client/views/nodes/trails/PresBox.tsx | 18 +- src/client/views/pdf/PDFViewer.tsx | 3 +- src/fields/Doc.ts | 2 +- 32 files changed, 605 insertions(+), 532 deletions(-) (limited to 'src/client/views/pdf/PDFViewer.tsx') diff --git a/package-lock.json b/package-lock.json index 196906718..111d1838a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5597,6 +5597,16 @@ "integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=", "dev": true }, + "d": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", + "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", + "dev": true, + "requires": { + "es5-ext": "^0.10.50", + "type": "^1.0.1" + } + }, "d3": { "version": "7.8.4", "resolved": "https://registry.npmjs.org/d3/-/d3-7.8.4.tgz", @@ -6977,6 +6987,28 @@ "is-symbol": "^1.0.2" } }, + "es5-ext": { + "version": "0.10.62", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.62.tgz", + "integrity": "sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==", + "dev": true, + "requires": { + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.3", + "next-tick": "^1.1.0" + } + }, + "es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, "es6-promise": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.2.1.tgz", @@ -6988,6 +7020,7 @@ "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", "dev": true, "requires": { + "d": "^1.0.1", "ext": "^1.1.2" } }, @@ -22660,6 +22693,12 @@ "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" }, + "type": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", + "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==", + "dev": true + }, "type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 5a7894c08..bb12ce568 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -1054,8 +1054,8 @@ export namespace Docs { return inst; } - export function WebanchorDocument(url?: string, options: DocumentOptions = {}, id?: string) { - return InstanceFromProto(Prototypes.get(DocumentType.MARKER), url, options, id); + export function WebanchorDocument(options: DocumentOptions = {}, id?: string) { + return InstanceFromProto(Prototypes.get(DocumentType.MARKER), undefined, options, id); } export function CollectionAnchorDocument(options: DocumentOptions = {}, id?: string) { diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 80a5f0993..fbfe306a4 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -669,7 +669,10 @@ export class CurrentUserUtils { } static schemaTools():Button[] { - return [{ title: "Show preview", toolTip: "Show selection preview", btnType: ButtonType.ToggleButton, buttonText: "Show Preview", icon: "eye", scripts:{ onClick: '{ return toggleSchemaPreview(_readOnly_); }'}, }]; + return [ + {title: "Show preview", toolTip: "Show selection preview", btnType: ButtonType.ToggleButton, buttonText: "Show Preview", icon: "eye", scripts:{ onClick: '{ return toggleSchemaPreview(_readOnly_); }'} }, + {title: "Single Lines", toolTip: "Single Line Rows", btnType: ButtonType.ToggleButton, buttonText: "Single Line", icon: "eye", scripts:{ onClick: '{ return toggleSingleLineSchema(_readOnly_); }'} }, + ]; } static webTools() { diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 3a192f712..695b003a6 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -1,4 +1,4 @@ -import { action, observable, ObservableSet } from 'mobx'; +import { action, computed, observable, ObservableSet } from 'mobx'; import { AnimationSym, Doc, Opt } from '../../fields/Doc'; import { Id } from '../../fields/FieldSymbols'; import { listSpec } from '../../fields/Schema'; @@ -10,6 +10,7 @@ import { TabDocView } from '../views/collections/TabDocView'; import { LightboxView } from '../views/LightboxView'; import { DocFocusOptions, DocumentView, DocumentViewInternal, OpenWhere, OpenWhereMod } from '../views/nodes/DocumentView'; import { FormattedTextBox } from '../views/nodes/formattedText/FormattedTextBox'; +import { KeyValueBox } from '../views/nodes/KeyValueBox'; import { LinkAnchorBox } from '../views/nodes/LinkAnchorBox'; import { PresBox } from '../views/nodes/trails'; import { ScriptingGlobals } from './ScriptingGlobals'; @@ -18,10 +19,13 @@ const { Howl } = require('howler'); export class DocumentManager { //global holds all of the nodes (regardless of which collection they're in) - @observable public DocumentViews = new Set(); + @observable _documentViews = new Set(); @observable public LinkAnchorBoxViews: DocumentView[] = []; @observable public RecordingEvent = 0; @observable public LinkedDocumentViews: { a: DocumentView; b: DocumentView; l: Doc }[] = []; + @computed public get DocumentViews() { + return Array.from(this._documentViews).filter(view => !(view.ComponentView instanceof KeyValueBox)); + } private static _instance: DocumentManager; public static get Instance(): DocumentManager { @@ -78,7 +82,7 @@ export class DocumentManager { // this.LinkedDocumentViews.forEach(view => console.log(" LV = " + view.a.props.Document.title + "/" + view.a.props.LayoutTemplateString + " --> " + // view.b.props.Document.title + "/" + view.b.props.LayoutTemplateString)); } else { - this.DocumentViews.add(view); + this._documentViews.add(view); } this.callAddViewFuncs(view); }; @@ -96,7 +100,7 @@ export class DocumentManager { const index = this.LinkAnchorBoxViews.indexOf(view); this.LinkAnchorBoxViews.splice(index, 1); } else { - this.DocumentViews.delete(view); + this._documentViews.delete(view); } SelectionManager.DeselectView(view); }); @@ -104,13 +108,13 @@ export class DocumentManager { //gets all views public getDocumentViewsById(id: string) { const toReturn: DocumentView[] = []; - Array.from(DocumentManager.Instance.DocumentViews).map(view => { + DocumentManager.Instance.DocumentViews.forEach(view => { if (view.rootDoc[Id] === id) { toReturn.push(view); } }); if (toReturn.length === 0) { - Array.from(DocumentManager.Instance.DocumentViews).map(view => { + DocumentManager.Instance.DocumentViews.forEach(view => { const doc = view.rootDoc.proto; if (doc && doc[Id] && doc[Id] === id) { toReturn.push(view); @@ -133,7 +137,7 @@ export class DocumentManager { // ((DocCast(dv.rootDoc.annotationOn)?.data as any)?.url?.href && (DocCast(dv.rootDoc.annotationOn)?.data as any)?.url?.href === (DocCast(toFind.annotationOn)?.data as any)?.url?.href) // )?.rootDoc ?? toFind; - const docViewArray = Array.from(DocumentManager.Instance.DocumentViews); + const docViewArray = DocumentManager.Instance.DocumentViews; const passes = !doc ? [] : preferredCollection ? [preferredCollection, undefined] : [undefined]; return passes.reduce( (pass, toReturn) => @@ -146,7 +150,7 @@ export class DocumentManager { public getLightboxDocumentView = (toFind: Doc, originatingDoc: Opt = undefined): DocumentView | undefined => { const views: DocumentView[] = []; - Array.from(DocumentManager.Instance.DocumentViews).map(view => LightboxView.IsLightboxDocView(view.docViewPath) && Doc.AreProtosEqual(view.rootDoc, toFind) && views.push(view)); + DocumentManager.Instance.DocumentViews.forEach(view => LightboxView.IsLightboxDocView(view.docViewPath) && Doc.AreProtosEqual(view.rootDoc, toFind) && views.push(view)); return views?.find(view => view.ContentDiv?.getBoundingClientRect().width /*&& view.props.focus !== returnFalse) || views?.find(view => view.props.focus !== returnFalse*/) || (views.length ? views[0] : undefined); }; public getFirstDocumentView = (toFind: Doc, originatingDoc: Opt = undefined): DocumentView | undefined => { @@ -164,8 +168,8 @@ export class DocumentManager { toFindIn; const toReturn: DocumentView[] = []; - const docViews = Array.from(DocumentManager.Instance.DocumentViews).filter(view => !LightboxView.IsLightboxDocView(view.docViewPath)); - const lightViews = Array.from(DocumentManager.Instance.DocumentViews).filter(view => LightboxView.IsLightboxDocView(view.docViewPath)); + const docViews = DocumentManager.Instance.DocumentViews.filter(view => !LightboxView.IsLightboxDocView(view.docViewPath)); + const lightViews = DocumentManager.Instance.DocumentViews.filter(view => LightboxView.IsLightboxDocView(view.docViewPath)); // heuristic to return the "best" documents first: // choose a document in the lightbox first diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts index f17a98616..70c2e3842 100644 --- a/src/client/util/Scripting.ts +++ b/src/client/util/Scripting.ts @@ -7,8 +7,6 @@ import * as typescriptlib from '!!raw-loader!./type_decls.d'; import * as ts from 'typescript'; import { Doc, Field } from '../../fields/Doc'; -import { ToScriptString } from '../../fields/FieldSymbols'; -import { ObjectField } from '../../fields/ObjectField'; import { RefField } from '../../fields/RefField'; import { ScriptField } from '../../fields/ScriptField'; import { scriptingGlobals, ScriptingGlobals } from './ScriptingGlobals'; @@ -60,7 +58,14 @@ function Run(script: string | undefined, customParams: string[], diagnostics: an // let fieldTypes = [Doc, ImageField, PdfField, VideoField, AudioField, List, RichTextField, ScriptField, ComputedField, CompileScript]; // let paramNames = ["Docs", ...fieldTypes.map(fn => fn.name)]; // let params: any[] = [Docs, ...fieldTypes]; - const compiledFunction = new Function(...paramNames, `return ${script}`); + const compiledFunction = (() => { + try { + return new Function(...paramNames, `return ${script}`); + } catch { + return undefined; + } + })(); + if (!compiledFunction) return { compiled: false, errors }; const { capturedVariables = {} } = options; const run = (args: { [name: string]: any } = {}, onError?: (e: any) => void, errorVal?: any): ScriptResult => { const argsArray: any[] = []; @@ -155,7 +160,7 @@ export type Traverser = (node: ts.Node, indentation: string) => boolean | void; export type TraverserParam = Traverser | { onEnter: Traverser; onLeave: Traverser }; export type Transformer = { transformer: ts.TransformerFactory; - getVars?: () => { capturedVariables: { [name: string]: Field } }; + getVars?: () => { [name: string]: Field }; }; export interface ScriptOptions { requiredType?: string; // does function required a typed return value @@ -205,16 +210,17 @@ export function CompileScript(script: string, options: ScriptOptions = {}): Comp if (options.transformer) { const sourceFile = ts.createSourceFile('script.ts', script, ts.ScriptTarget.ES2015, true); const result = ts.transform(sourceFile, [options.transformer.transformer]); - if (options.transformer.getVars) { - const newCaptures = options.transformer.getVars(); + const newCaptures = options.transformer.getVars?.(); + if (Object.keys(newCaptures ?? {}).length) { // tslint:disable-next-line: prefer-object-spread - options.capturedVariables = Object.assign(capturedVariables, newCaptures.capturedVariables) as any; + //options.capturedVariables = Object.assign(capturedVariables, newCaptures!) as any; + + const transformed = result.transformed; + const printer = ts.createPrinter({ + newLine: ts.NewLineKind.LineFeed, + }); + script = printer.printFile(transformed[0]); } - const transformed = result.transformed; - const printer = ts.createPrinter({ - newLine: ts.NewLineKind.LineFeed, - }); - script = printer.printFile(transformed[0]); result.dispose(); } const paramNames: string[] = []; diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 85d36dbf8..26b64bfdd 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -867,11 +867,11 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P width: bounds.r - bounds.x + this._resizeBorderWidth + 'px', height: bounds.b - bounds.y + this._resizeBorderWidth + this._titleHeight + 'px', }}> -
- {hideDeleteButton ?
: topBtn('close', 'times', undefined, e => this.onCloseClick(true), 'Close')} - {hideResizers || hideDeleteButton ?
: topBtn('minimize', 'window-maximize', undefined, e => this.onCloseClick(undefined), 'Minimize')} +
+ {hideDeleteButton ? null : topBtn('close', 'times', undefined, e => this.onCloseClick(true), 'Close')} + {hideResizers || hideDeleteButton ? null : topBtn('minimize', 'window-maximize', undefined, e => this.onCloseClick(undefined), 'Minimize')} {hideTitle ? null : titleArea} - {hideOpenButton ?
: topBtn('open', 'external-link-alt', this.onMaximizeDown, undefined, 'Open in Lightbox (ctrl: as alias, shift: in new collection)')} + {hideOpenButton ? null : topBtn('open', 'external-link-alt', this.onMaximizeDown, undefined, 'Open in Lightbox (ctrl: as alias, shift: in new collection)')}
{hideResizers ? null : ( <> diff --git a/src/client/views/EditableView.scss b/src/client/views/EditableView.scss index ed7ec9dc1..0955ba8ff 100644 --- a/src/client/views/EditableView.scss +++ b/src/client/views/EditableView.scss @@ -10,12 +10,14 @@ .editableView-container-editing-oneLine { width: 100%; + height: max-content; span { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - display: block; + p { + white-space: pre; + overflow: hidden; + text-overflow: ellipsis; + } } input { @@ -33,4 +35,3 @@ border: none; outline: none; } - \ No newline at end of file diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx index d1311a60a..6b4132814 100644 --- a/src/client/views/EditableView.tsx +++ b/src/client/views/EditableView.tsx @@ -1,9 +1,11 @@ import React = require('react'); -import { action, observable } from 'mobx'; +import { action, IReactionDisposer, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; import * as Autosuggest from 'react-autosuggest'; import { ObjectField } from '../../fields/ObjectField'; import './EditableView.scss'; +import { DocumentIconContainer } from './nodes/DocumentIcon'; +import { OverlayView } from './OverlayView'; export interface EditableProps { /** @@ -37,7 +39,8 @@ export interface EditableProps { onChange: (e: React.ChangeEvent, { newValue }: { newValue: string }) => void; autosuggestProps: Autosuggest.AutosuggestProps; }; - oneLine?: boolean; + oneLine?: boolean; // whether to display the editable view as a single input line or as a textarea + allowCRs?: boolean; // can carriage returns be entered editing?: boolean; isEditingCallback?: (isEditing: boolean) => void; menuCallback?: (x: number, y: number) => void; @@ -45,6 +48,7 @@ export interface EditableProps { showMenuOnLoad?: boolean; background?: string | undefined; placeholder?: string; + wrap?: string; // nowrap, pre-wrap, etc } /** @@ -55,7 +59,9 @@ export interface EditableProps { @observer export class EditableView extends React.Component { private _ref = React.createRef(); - private _inputref = React.createRef(); + private _inputref: HTMLInputElement | HTMLTextAreaElement | null = null; + _overlayDisposer?: () => void; + _editingDisposer?: IReactionDisposer; @observable _editing: boolean = false; constructor(props: EditableProps) { @@ -63,21 +69,53 @@ export class EditableView extends React.Component { this._editing = this.props.editing ? true : false; } + componentDidMount(): void { + this._editingDisposer = reaction( + () => this._editing, + editing => { + if (editing) { + setTimeout(() => { + if (this._inputref?.value.startsWith('=') || this._inputref?.value.startsWith(':=')) { + this._overlayDisposer?.(); + this._overlayDisposer = OverlayView.Instance.addElement(, { x: 0, y: 0 }); + } + }); + } else { + this._overlayDisposer?.(); + this._overlayDisposer = undefined; + } + }, + { fireImmediately: true } + ); + } + @action componentDidUpdate() { if (this._editing && this.props.editing === false) { - this._inputref.current?.value && this.finalizeEdit(this._inputref.current.value, false, true, false); + this._inputref?.value && this.finalizeEdit(this._inputref.value, false, true, false); } else if (this.props.editing !== undefined) { this._editing = this.props.editing; } } componentWillUnmount() { - this._inputref.current?.value && this.finalizeEdit(this._inputref.current.value, false, true, false); + this._overlayDisposer?.(); + this._editingDisposer?.(); + this._inputref?.value && this.finalizeEdit(this._inputref.value, false, true, false); } + onChange = (e: React.ChangeEvent) => { + const targVal = (e.target as any).value; + if (!(targVal.startsWith(':=') || targVal.startsWith('='))) { + this._overlayDisposer?.(); + this._overlayDisposer = undefined; + } else if (!this._overlayDisposer) { + this._overlayDisposer = OverlayView.Instance.addElement(, { x: 0, y: 0 }); + } + }; + @action - onKeyDown = (e: React.KeyboardEvent) => { + onKeyDown = (e: React.KeyboardEvent) => { switch (e.key) { case 'Tab': e.stopPropagation(); @@ -89,13 +127,15 @@ export class EditableView extends React.Component { if (!e.currentTarget.value) this.props.OnEmpty?.(); break; case 'Enter': - e.stopPropagation(); - if (!e.ctrlKey) { - this.finalizeEdit(e.currentTarget.value, e.shiftKey, false, true); - } else if (this.props.OnFillDown) { - this.props.OnFillDown(e.currentTarget.value); - this._editing = false; - this.props.isEditingCallback?.(false); + if (this.props.allowCRs !== true) { + e.stopPropagation(); + if (!e.ctrlKey) { + this.finalizeEdit(e.currentTarget.value, e.shiftKey, false, true); + } else if (this.props.OnFillDown) { + this.props.OnFillDown(e.currentTarget.value); + this._editing = false; + this.props.isEditingCallback?.(false); + } } break; case 'Escape': @@ -103,9 +143,6 @@ export class EditableView extends React.Component { this._editing = false; this.props.isEditingCallback?.(false); break; - case ':': - this.props.menuCallback?.(e.currentTarget.getBoundingClientRect().x, e.currentTarget.getBoundingClientRect().y); - break; case 'ArrowUp': case 'ArrowDown': case 'ArrowLeft': @@ -117,6 +154,12 @@ export class EditableView extends React.Component { case 'Meta': case 'Control': break; + case ':': + if (this.props.menuCallback) { + this.props.menuCallback(e.currentTarget.getBoundingClientRect().x, e.currentTarget.getBoundingClientRect().y); + break; + } + default: if (this.props.textCallback?.(e.key)) { this._editing = false; @@ -186,15 +229,32 @@ export class EditableView extends React.Component { onChange: this.props.autosuggestProps.onChange, }} /> - ) : ( + ) : this.props.oneLine !== false && this.props.GetValue()?.toString().indexOf('\n') === -1 ? ( (this._inputref = r)} style={{ display: this.props.display, overflow: 'auto', fontSize: this.props.fontSize, minWidth: 20, background: this.props.background }} placeholder={this.props.placeholder} onBlur={e => this.finalizeEdit(e.currentTarget.value, false, true, false)} defaultValue={this.props.GetValue()} autoFocus={true} + onChange={this.onChange} + onKeyDown={this.onKeyDown} + onKeyPress={this.stopPropagation} + onPointerDown={this.stopPropagation} + onClick={this.stopPropagation} + onPointerUp={this.stopPropagation} + /> + ) : ( +