From 403d1c4fece9efa663e0fd7161afff9f27cf670c Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 16 Dec 2022 15:07:50 -0500 Subject: fixed problem with undo. regularized all linkfollowing anchor fields to start with followLink. added ease vs linear flag for scroll transitions in link and preselmeent navigations. added link follow to move target to specified offset from source. shifted from setting dropAction on items to setting childDropAction on collections --- src/client/views/DocumentButtonBar.scss | 17 +++ src/client/views/DocumentButtonBar.tsx | 68 +++++++--- src/client/views/GestureOverlay.tsx | 1 - src/client/views/PropertiesView.tsx | 88 +++++++++---- .../views/collections/CollectionNoteTakingView.tsx | 4 +- .../views/collections/CollectionPileView.tsx | 2 +- .../views/collections/CollectionStackingView.tsx | 4 +- .../views/collections/CollectionTimeView.tsx | 2 +- src/client/views/collections/TabDocView.tsx | 5 + .../collectionFreeForm/CollectionFreeFormView.tsx | 6 +- .../collectionLinear/CollectionLinearView.tsx | 3 +- src/client/views/linking/LinkPopup.scss | 5 +- src/client/views/linking/LinkPopup.tsx | 13 +- .../views/nodes/CollectionFreeFormDocumentView.tsx | 2 +- src/client/views/nodes/DocumentLinksButton.tsx | 2 +- src/client/views/nodes/DocumentView.tsx | 11 +- src/client/views/nodes/LabelBox.tsx | 140 ++++++++++++--------- src/client/views/nodes/WebBox.tsx | 8 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 5 +- src/client/views/nodes/trails/PresBox.tsx | 37 ++++-- src/client/views/nodes/trails/PresEnums.ts | 1 + src/client/views/pdf/AnchorMenu.tsx | 3 +- src/client/views/pdf/PDFViewer.tsx | 21 ++-- src/client/views/search/SearchBox.tsx | 29 ++++- 24 files changed, 318 insertions(+), 159 deletions(-) (limited to 'src/client/views') diff --git a/src/client/views/DocumentButtonBar.scss b/src/client/views/DocumentButtonBar.scss index f9c988fdd..835d6c8bb 100644 --- a/src/client/views/DocumentButtonBar.scss +++ b/src/client/views/DocumentButtonBar.scss @@ -28,6 +28,15 @@ $linkGap: 3px; height: 20px; align-items: center; } +.documentButtonBar-linkTypes { + position: absolute; + display: none; + width: 60px; + top: -14px; + background: black; + height: 20px; + align-items: center; +} .documentButtonBar-followTypes { width: 20px; display: none; @@ -55,6 +64,14 @@ $linkGap: 3px; } } } +.documentButtonBar-link { + color: white; + &:hover { + .documentButtonBar-linkTypes { + display: flex; + } + } +} .documentButtonBar-pinIcon { &:hover { diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index 5969d55e9..90c6c040c 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -1,24 +1,24 @@ import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Tooltip } from '@material-ui/core'; -import { action, computed, observable, runInAction } from 'mobx'; +import { action, computed, observable, runInAction, trace } from 'mobx'; import { observer } from 'mobx-react'; import { Doc } from '../../fields/Doc'; import { RichTextField } from '../../fields/RichTextField'; -import { BoolCast, Cast, NumCast } from '../../fields/Types'; +import { Cast, NumCast } from '../../fields/Types'; import { emptyFunction, returnFalse, setupMoveUpEvents, simulateMouseClick } from '../../Utils'; import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager'; import { Pulls, Pushes } from '../apis/google_docs/GoogleApiClientUtils'; import { Docs } from '../documents/Documents'; import { DragManager } from '../util/DragManager'; import { SelectionManager } from '../util/SelectionManager'; -import { SettingsManager } from '../util/SettingsManager'; import { SharingManager } from '../util/SharingManager'; import { undoBatch, UndoManager } from '../util/UndoManager'; import { CollectionDockingView } from './collections/CollectionDockingView'; import { TabDocView } from './collections/TabDocView'; import './DocumentButtonBar.scss'; import { Colors } from './global/globalEnums'; +import { LinkPopup } from './linking/LinkPopup'; import { MetadataEntryMenu } from './MetadataEntryMenu'; import { DocumentLinksButton } from './nodes/DocumentLinksButton'; import { DocumentView, DocumentViewInternal, OpenWhereMod } from './nodes/DocumentView'; @@ -26,6 +26,7 @@ import { DashFieldView } from './nodes/formattedText/DashFieldView'; import { GoogleRef } from './nodes/formattedText/FormattedTextBox'; import { TemplateMenu } from './TemplateMenu'; import React = require('react'); +import { DocumentType } from '../documents/DocumentTypes'; const higflyout = require('@hig/flyout'); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; @@ -255,6 +256,28 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV ); } + @observable subLink = ''; + @computed get linkButton() { + const targetDoc = this.view0?.props.Document; + return !targetDoc || !this.view0 ? null : ( +
+
+ search for target
}> +
+ +
+ +
+
+ +
+ + ); + } + @observable subPin = ''; @computed get pinButton() { @@ -284,7 +307,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV .views() .filter(v => v) .map(dv => dv!.rootDoc); - TabDocView.PinDoc(docs, { pinAudioPlay: true, pinDocLayout, pinDocContent, activeFrame: Cast(docs.lastElement()?.activeFrame, 'number', null) }); + TabDocView.PinDoc(docs, { pinAudioPlay: true, pinDocLayout, pinDocContent, activeFrame: Cast(docs.lastElement()?.activeFrame, 'number', null), currentFrame: Cast(docs.lastElement()?.currentFrame, 'number', null) }); e.stopPropagation(); }} /> @@ -319,12 +342,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV get shareButton() { const targetDoc = this.view0?.props.Document; return !targetDoc ? null : ( - -
{'Open Sharing Manager'}
- - }> + {'Open Sharing Manager'}}>
SharingManager.Instance.open(this.view0, targetDoc)}>
@@ -347,12 +365,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV get metadataButton() { const view0 = this.view0; return !view0 ? null : ( - -
Show metadata panel
- - }> + Show metadata panel}>
(DocumentV simulateMouseClick(child, e.clientX, e.clientY - 30, e.screenX, e.screenY - 30); }; + @observable _showLinkPopup = false; + @action + toggleLinkSearch = (e: React.PointerEvent) => { + this._showLinkPopup = !this._showLinkPopup; + e.stopPropagation(); + }; render() { if (!this.view0) return null; @@ -476,9 +495,20 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
-
- -
+ {this._showLinkPopup ? ( +
+ (link.linkDisplay = !this.props.views().lastElement()?.rootDoc.isLinkButton)} + linkCreateAnchor={() => this.props.views().lastElement()?.ComponentView?.getAnchor?.()} + linkFrom={() => this.props.views().lastElement()?.rootDoc} + /> +
+ ) : ( +
{this.linkButton}
+ )} + {(DocumentLinksButton.StartLink || Doc.UserDoc()['documentLinksButton-fullMenu']) && DocumentLinksButton.StartLink !== doc ? (
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index a29073f14..e3328fb4c 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -122,7 +122,6 @@ export class GestureOverlay extends Touchable { onPointerUp: data.pointerUp ? ScriptField.MakeScript(data.pointerUp) : undefined, onPointerDown: data.pointerDown ? ScriptField.MakeScript(data.pointerDown) : undefined, backgroundColor: data.backgroundColor, - _removeDropProperties: new List(['dropAction']), dragFactory: data.dragFactory, system: true, }) diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index e43b160b8..92c5708aa 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -1414,7 +1414,7 @@ export class PropertiesView extends React.Component { changeFollowBehavior = action((follow: string) => this.sourceAnchor && (this.sourceAnchor.followLinkLocation = follow)); @undoBatch - changeAnimationBehavior = action((behavior: string) => this.sourceAnchor && (this.sourceAnchor.linkAnimEffect = behavior)); + changeAnimationBehavior = action((behavior: string) => this.sourceAnchor && (this.sourceAnchor.followLinkAnimEffect = behavior)); @undoBatch changeEffectDirection = action((effect: PresEffectDirection) => this.sourceAnchor && (this.sourceAnchor.linkAnimDirection = effect)); @@ -1474,8 +1474,20 @@ export class PropertiesView extends React.Component { return selAnchor ?? (LinkManager.currentLink && this.destinationAnchor ? LinkManager.getOppositeAnchor(LinkManager.currentLink, this.destinationAnchor) : LinkManager.currentLink); } - toggleAnchorProp = (e: React.PointerEvent, prop: string, anchor?: Doc) => { - anchor && setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => this.selectedDoc && (anchor[prop] = !anchor[prop])))); + toggleAnchorProp = (e: React.PointerEvent, prop: string, anchor?: Doc, value: any = true, ovalue: any = false, cb: (val: any) => any = val => val) => { + anchor && + setupMoveUpEvents( + this, + e, + returnFalse, + emptyFunction, + undoBatch( + action(() => { + anchor[prop] = anchor[prop] === value ? ovalue : value; + this.selectedDoc && cb(anchor[prop]); + }) + ) + ); }; @computed @@ -1516,7 +1528,7 @@ export class PropertiesView extends React.Component { if (change) scale += change; if (scale < 0.01) scale = 0.01; if (scale > 1) scale = 1; - this.sourceAnchor && (this.sourceAnchor.linkZoomScale = scale); + this.sourceAnchor && (this.sourceAnchor.followLinkZoomScale = scale); }; /** @@ -1532,7 +1544,7 @@ export class PropertiesView extends React.Component { render() { const isNovice = Doc.noviceMode; - const zoom = Number((NumCast(this.sourceAnchor?.linkZoomScale, 1) * 100).toPrecision(3)); + const zoom = Number((NumCast(this.sourceAnchor?.followLinkZoomScale, 1) * 100).toPrecision(3)); const targZoom = this.sourceAnchor?.followLinkZoom; const indent = 30; const hasSelectedAnchor = SelectionManager.Views().some(dv => DocListCast(this.sourceAnchor?.links).includes(LinkManager.currentLink!)); @@ -1610,21 +1622,22 @@ export class PropertiesView extends React.Component {

Follow by

Animation

- this.changeAnimationBehavior(e.currentTarget.value)} value={StrCast(this.sourceAnchor?.followLinkAnimEffect, 'default')}> {[PresEffect.None, PresEffect.Zoom, PresEffect.Lightspeed, PresEffect.Fade, PresEffect.Flip, PresEffect.Rotate, PresEffect.Bounce, PresEffect.Roll].map(effect => ( @@ -1642,9 +1655,9 @@ export class PropertiesView extends React.Component { '0.1', '0.1', '10', - NumCast(this.sourceAnchor?.linkTransitionTime) / 1000, + NumCast(this.sourceAnchor?.followLinkTransitionTime) / 1000, true, - (val: string) => PresBox.SetTransitionTime(val, (timeInMS: number) => this.sourceAnchor && (this.sourceAnchor.linkTransitionTime = timeInMS)), + (val: string) => PresBox.SetTransitionTime(val, (timeInMS: number) => this.sourceAnchor && (this.sourceAnchor.followLinkTransitionTime = timeInMS)), indent )}{' '}
{
+
+

Ease Transitions

+ +
+
+

Capture Offset to Target

+ +
+
+

Center Target (no zoom)

+ +

Zoom %

-
+
this.setZoom(String(zoom), 0.1))}> @@ -1695,18 +1741,18 @@ export class PropertiesView extends React.Component {
- {!targZoom ? null : PresBox.inputter('0', '1', '100', zoom, true, this.setZoom, 30)} + {!targZoom || this.sourceAnchor?.followLinkZoomScale === 0 ? null : PresBox.inputter('0', '1', '100', zoom, true, this.setZoom, 30)}
{ - smoothScroll(500, this._mainCont!, this._mainCont!.scrollHeight); + smoothScroll(500, this._mainCont!, this._mainCont!.scrollHeight, 'ease'); }; // let's dive in and get the actual document we want to drag/move around @@ -193,7 +193,7 @@ export class CollectionNoteTakingView extends CollectionSubView() { const top = found.getBoundingClientRect().top; const localTop = this.props.ScreenToLocalTransform().transformPoint(0, top); if (Math.floor(localTop[1]) !== 0) { - smoothScroll((focusSpeed = options.zoomTime ?? 500), this._mainCont!, localTop[1] + this._mainCont!.scrollTop); + smoothScroll((focusSpeed = options.zoomTime ?? 500), this._mainCont!, localTop[1] + this._mainCont!.scrollTop, options.easeFunc); } } const endFocus = async (moved: boolean) => (options?.afterFocus ? options?.afterFocus(moved) : ViewAdjustment.doNothing); diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx index e95622630..ba90ed8cd 100644 --- a/src/client/views/collections/CollectionPileView.tsx +++ b/src/client/views/collections/CollectionPileView.tsx @@ -120,7 +120,7 @@ export class CollectionPileView extends CollectionSubView() { const doc = this.childDocs[0]; doc.x = e.clientX; doc.y = e.clientY; - this.props.addDocTab(doc, OpenWhere.inParent) && (this.props.removeDocument?.(doc) || false); + this.props.addDocTab(doc, OpenWhere.inParentFromScreen) && (this.props.removeDocument?.(doc) || false); dist = 0; } } diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 08aebc62d..acf59b5da 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -242,7 +242,7 @@ export class CollectionStackingView extends CollectionSubView { - smoothScroll(500, this._mainCont!, this._mainCont!.scrollHeight); + smoothScroll(500, this._mainCont!, this._mainCont!.scrollHeight, 'ease'); }; // let's dive in and get the actual document we want to drag/move around @@ -255,7 +255,7 @@ export class CollectionStackingView extends CollectionSubView options?.afterFocus?.(moved) ?? ViewAdjustment.doNothing; diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx index ac896a8fd..a1466bcd0 100644 --- a/src/client/views/collections/CollectionTimeView.tsx +++ b/src/client/views/collections/CollectionTimeView.tsx @@ -57,7 +57,7 @@ export class CollectionTimeView extends CollectionSubView() { async componentDidMount() { this.props.setContentView?.(this); //const detailView = (await DocCastAsync(this.props.Document.childClickedOpenTemplateView)) || DocUtils.findTemplate("detailView", StrCast(this.rootDoc.type), ""); - ///const childText = "const alias = getAlias(self); switchView(alias, detailView); alias.dropAction='alias'; alias.removeDropProperties=new List(['dropAction']); useRightSplit(alias, shiftKey); "; + ///const childText = "const alias = getAlias(self); switchView(alias, detailView); alias.dropAction='alias'; useRightSplit(alias, shiftKey); "; runInAction(() => { this._childClickedScript = ScriptField.MakeScript('openInLightbox(self)', { this: Doc.name }); this._viewDefDivClick = ScriptField.MakeScript('pivotColumnClick(this,payload)', { payload: 'any' }); diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index a61ae680b..e45e2a1cb 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -281,6 +281,11 @@ export class TabDocView extends React.Component { pinDoc.title = doc.title + ' (move)'; pinDoc.presMovement = PresMovement.Pan; } + if (pinProps?.currentFrame !== undefined) { + pinDoc.presCurrentFrame = pinProps?.currentFrame; + pinDoc.title = doc.title + ' (move)'; + pinDoc.presMovement = PresMovement.Pan; + } if (pinDoc.isInkMask) { pinDoc.presHideAfter = true; pinDoc.presHideBefore = true; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 4818094de..6b9eb7916 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1186,7 +1186,7 @@ export class CollectionFreeFormView extends CollectionSubView { switch (where) { case OpenWhere.inParent: + return this.props.addDocument?.(doc) || false; + case OpenWhere.inParentFromScreen: return ( this.props.addDocument?.( (doc instanceof Doc ? [doc] : doc).map(doc => { @@ -1741,7 +1743,7 @@ export class CollectionFreeFormView extends CollectionSubView Doc | undefined; + linkCreateAnchor?: () => Doc | undefined; + linkCreated?: (link: Doc) => void; // groupType: string; // linkDoc: Doc; // docView: DocumentView; @@ -32,14 +35,10 @@ export class LinkPopup extends React.Component { // TODO: should check for valid URL @undoBatch - makeLinkToURL = (target: string, lcoation: string) => { - ((this.view as any)?.TextView as FormattedTextBox).makeLinkAnchor(undefined, 'onRadd:rightight', target, target); - }; + makeLinkToURL = (target: string, lcoation: string) => ((this.view as any)?.TextView as FormattedTextBox).makeLinkAnchor(undefined, OpenWhere.addRight, target, target); @action - onLinkChange = (e: React.ChangeEvent) => { - this.linkURL = e.target.value; - }; + onLinkChange = (e: React.ChangeEvent) => (this.linkURL = e.target.value); getPWidth = () => 500; getPHeight = () => 500; @@ -67,7 +66,9 @@ export class LinkPopup extends React.Component { Document={Doc.MySearcher} DataDoc={Doc.MySearcher} linkFrom={linkDoc} + linkCreateAnchor={this.props.linkCreateAnchor} linkSearch={true} + linkCreated={this.props.linkCreated} fieldKey="data" dropAction="move" isSelected={returnTrue} diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index bf1f13a06..f8ef87fb1 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -184,7 +184,7 @@ export class CollectionFreeFormDocumentView extends DocComponent - {!DocumentLinksButton.LinkEditorDocView ? this.linkButtonInner : {title}
}>{this.linkButtonInner}} + {title}
}>{this.linkButtonInner}
); } diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index c9fbe7a98..76cc6800a 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -75,6 +75,7 @@ export enum OpenWhere { inPlace = 'inPlace', lightbox = 'lightbox', add = 'add', + addLeft = 'add:left', addRight = 'add:right', addBottom = 'add:bottom', dashboard = 'dashboard', @@ -82,7 +83,10 @@ export enum OpenWhere { fullScreen = 'fullScreen', toggle = 'toggle', replace = 'replace', + replaceRight = 'replace:right', + replaceLeft = 'replace:left', inParent = 'inParent', + inParentFromScreen = 'inParentFromScreen', } export enum OpenWhereMod { none = '', @@ -108,6 +112,7 @@ export interface DocFocusOptions { playAudio?: boolean; // whether to play audio annotation on focus toggleTarget?: boolean; // whether to toggle target on and off originatingDoc?: Doc; // document that triggered the focus + easeFunc?: 'linear' | 'ease'; // transition method for scrolling } export type DocAfterFocusFunc = (notFocused: boolean) => Promise; export type DocFocusFunc = (doc: Doc, options: DocFocusOptions) => void; @@ -737,7 +742,7 @@ export class DocumentViewInternal extends DocComponent{renderDoc}; diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx index b58a9affb..10897b48f 100644 --- a/src/client/views/nodes/LabelBox.tsx +++ b/src/client/views/nodes/LabelBox.tsx @@ -15,16 +15,17 @@ import { FieldView, FieldViewProps } from './FieldView'; import BigText from './LabelBigText'; import './LabelBox.scss'; - export interface LabelBoxProps { label?: string; } @observer -export class LabelBox extends ViewBoxBaseComponent<(FieldViewProps & LabelBoxProps)>() { - public static LayoutString(fieldKey: string) { return FieldView.LayoutString(LabelBox, fieldKey); } +export class LabelBox extends ViewBoxBaseComponent() { + public static LayoutString(fieldKey: string) { + return FieldView.LayoutString(LabelBox, fieldKey); + } public static LayoutStringWithTitle(fieldStr: string, label?: string) { - return !label ? LabelBox.LayoutString(fieldStr) : ``; //e.g., "" + return !label ? LabelBox.LayoutString(fieldStr) : ``; //e.g., "" } private dropDisposer?: DragManager.DragDropDisposer; private _timeout: any; @@ -35,11 +36,12 @@ export class LabelBox extends ViewBoxBaseComponent<(FieldViewProps & LabelBoxPro this._timeout && clearTimeout(this._timeout); } + getAnchor = () => { + return 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); + 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); } protected createDropTarget = (ele: HTMLDivElement) => { @@ -47,36 +49,42 @@ export class LabelBox extends ViewBoxBaseComponent<(FieldViewProps & LabelBoxPro if (ele) { this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this), this.props.Document); } - } + }; - get paramsDoc() { return Doc.AreProtosEqual(this.layoutDoc, this.dataDoc) ? this.dataDoc : this.layoutDoc; } + get paramsDoc() { + return Doc.AreProtosEqual(this.layoutDoc, this.dataDoc) ? this.dataDoc : this.layoutDoc; + } specificContextMenu = (e: React.MouseEvent): void => { const funcs: ContextMenuProps[] = []; - !Doc.noviceMode && funcs.push({ - description: "Clear Script Params", event: () => { - const params = Cast(this.paramsDoc["onClick-paramFieldKeys"], listSpec("string"), []); - params?.map(p => this.paramsDoc[p] = undefined); - }, icon: "trash" - }); + !Doc.noviceMode && + funcs.push({ + description: 'Clear Script Params', + event: () => { + const params = Cast(this.paramsDoc['onClick-paramFieldKeys'], listSpec('string'), []); + params?.map(p => (this.paramsDoc[p] = undefined)); + }, + icon: 'trash', + }); - funcs.length && ContextMenu.Instance.addItem({ description: "OnClick...", noexpand: true, subitems: funcs, icon: "mouse-pointer" }); - } + funcs.length && ContextMenu.Instance.addItem({ description: 'OnClick...', noexpand: true, subitems: funcs, icon: 'mouse-pointer' }); + }; @undoBatch @action drop = (e: Event, de: DragManager.DropEvent) => { const docDragData = de.complete.docDragData; - const params = Cast(this.paramsDoc["onClick-paramFieldKeys"], listSpec("string"), []); + const params = Cast(this.paramsDoc['onClick-paramFieldKeys'], listSpec('string'), []); const missingParams = params?.filter(p => !this.paramsDoc[p]); if (docDragData && missingParams?.includes((e.target as any).textContent)) { - this.paramsDoc[(e.target as any).textContent] = new List(docDragData.droppedDocuments.map((d, i) => - d.onDragStart ? docDragData.draggedDocuments[i] : d)); + this.paramsDoc[(e.target as any).textContent] = new List(docDragData.droppedDocuments.map((d, i) => (d.onDragStart ? docDragData.draggedDocuments[i] : d))); e.stopPropagation(); } - } + }; @observable _mouseOver = false; - @computed get hoverColor() { return this._mouseOver ? StrCast(this.layoutDoc._hoverBackgroundColor) : "unset"; } + @computed get hoverColor() { + return this._mouseOver ? StrCast(this.layoutDoc._hoverBackgroundColor) : 'unset'; + } fitTextToBox = (r: any): any => { const singleLine = BoolCast(this.rootDoc._singleLine, true); @@ -85,63 +93,73 @@ export class LabelBox extends ViewBoxBaseComponent<(FieldViewProps & LabelBoxPro fontSizeFactor: 1, minimumFontSize: NumCast(this.rootDoc._minFontSize, 8), maximumFontSize: NumCast(this.rootDoc._maxFontSize, 1000), - limitingDimension: "both", - horizontalAlign: "center", - verticalAlign: "center", - textAlign: "center", + limitingDimension: 'both', + horizontalAlign: 'center', + verticalAlign: 'center', + textAlign: 'center', singleLine, - whiteSpace: singleLine ? "nowrap" : "pre-wrap" + whiteSpace: singleLine ? 'nowrap' : 'pre-wrap', }; this._timeout = undefined; if (!r) return params; - if (!r.offsetHeight || !r.offsetWidth) return this._timeout = setTimeout(() => this.fitTextToBox(r)); + if (!r.offsetHeight || !r.offsetWidth) return (this._timeout = setTimeout(() => this.fitTextToBox(r))); const parent = r.parentNode; const parentStyle = parent.style; - parentStyle.display = ""; - parentStyle.alignItems = ""; - r.setAttribute("style", ""); - r.style.width = singleLine ? "" : "100%"; + parentStyle.display = ''; + parentStyle.alignItems = ''; + r.setAttribute('style', ''); + r.style.width = singleLine ? '' : '100%'; - r.style.textOverflow = "ellipsis"; - r.style.overflow = "hidden"; + r.style.textOverflow = 'ellipsis'; + r.style.overflow = 'hidden'; BigText(r, params); return params; - } + }; // (!missingParams || !missingParams.length ? "" : "(" + missingParams.map(m => m + ":").join(" ") + ")") render() { - const boxParams = this.fitTextToBox(null);// this causes mobx to trigger re-render when data changes - const params = Cast(this.paramsDoc["onClick-paramFieldKeys"], listSpec("string"), []); + const boxParams = this.fitTextToBox(null); // this causes mobx to trigger re-render when data changes + const params = Cast(this.paramsDoc['onClick-paramFieldKeys'], listSpec('string'), []); const missingParams = params?.filter(p => !this.paramsDoc[p]); params?.map(p => DocListCast(this.paramsDoc[p])); // bcz: really hacky form of prefetching ... const label = this.getTitle(); return ( -
this._mouseOver = false)} - onMouseOver={action(() => this._mouseOver = true)} - ref={this.createDropTarget} onContextMenu={this.specificContextMenu} +
(this._mouseOver = false))} + onMouseOver={action(() => (this._mouseOver = true))} + ref={this.createDropTarget} + onContextMenu={this.specificContextMenu} style={{ boxShadow: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BoxShadow) }}> -
- this.fitTextToBox(r))}> - {label.startsWith("#") ? (null) : label.replace(/([^a-zA-Z])/g, "$1\u200b")} +
+ this.fitTextToBox(r))}> + {label.startsWith('#') ? null : label.replace(/([^a-zA-Z])/g, '$1\u200b')}
-
- {!missingParams?.length ? (null) : missingParams.map(m =>
{m}
)} +
+ {!missingParams?.length + ? null + : missingParams.map(m => ( +
+ {m} +
+ ))}
); } -} \ No newline at end of file +} diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index 64b186489..a3e83f047 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -241,7 +241,7 @@ export class WebBox extends ViewBoxAnnotatableComponent { + goTo = (scrollTop: number, duration: number, easeFunc: 'linear' | 'ease' | undefined) => { if (this._outerRef.current) { const iframeHeight = Math.max(scrollTop, this._scrollHeight - this.panelHeight()); if (duration) { - smoothScroll(duration, [this._outerRef.current], scrollTop); + smoothScroll(duration, [this._outerRef.current], scrollTop, easeFunc); this.setDashScrollTop(scrollTop, duration); } else { this.setDashScrollTop(scrollTop); diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 9e91f6c46..b895043de 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -1120,7 +1120,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent void); setupEditor(config: any, fieldKey: string) { const curText = Cast(this.dataDoc[this.fieldKey], RichTextField, null) || StrCast(this.dataDoc[this.fieldKey]); const rtfField = Cast((!curText && this.layoutDoc[this.fieldKey]) || this.dataDoc[fieldKey], RichTextField); @@ -1302,7 +1303,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent() { this.rootDoc._itemIndex = index; const activeItem: Doc = this.activeItem; const targetDoc: Doc = this.targetDoc; - if (activeItem.presActiveFrame !== undefined) { + const activeFrame = activeItem.presActiveFrame ?? activeItem.presCurrentFrame; + if (activeFrame !== undefined) { const transTime = NumCast(activeItem.presTransition, 500); - const context = DocCast(DocCast(activeItem.presentationTargetDoc).context); + const context = activeItem.presActiveFrame ? DocCast(DocCast(activeItem.presentationTargetDoc).context) : DocCast(activeItem.presentationTargetDoc); if (context) { const ffview = DocumentManager.Instance.getFirstDocumentView(context)?.ComponentView as CollectionFreeFormView; if (ffview) { this._keyTimer = CollectionFreeFormDocumentView.gotoKeyframe(this._keyTimer, ffview.childDocs.slice(), transTime); - context._currentFrame = NumCast(activeItem.presActiveFrame); + context._currentFrame = NumCast(activeFrame); } } } @@ -547,11 +549,12 @@ export class PresBox extends ViewBoxBaseComponent() { LightboxView.SetLightboxDoc(undefined); const options: DocFocusOptions = { willPan: activeItem.presMovement !== PresMovement.None, - willPanZoom: activeItem.presMovement === PresMovement.Zoom || activeItem.presMovement === PresMovement.Jump, - zoomScale: NumCast(activeItem.presZoom, 1), + willPanZoom: activeItem.presMovement === PresMovement.Zoom || activeItem.presMovement === PresMovement.Jump || activeItem.presMovement === PresMovement.Center, + zoomScale: activeItem.presMovement === PresMovement.Center ? 0 : NumCast(activeItem.presZoom, 1), zoomTime: activeItem.presMovement === PresMovement.Jump ? 0 : NumCast(activeItem.presTransition, 500), noSelect: true, originatingDoc: activeItem, + easeFunc: StrCast(activeItem.presEaseFunc, 'ease') as any, }; var containerDocContext = srcContext ? [srcContext] : []; @@ -747,8 +750,8 @@ export class PresBox extends ViewBoxBaseComponent() { }); movementName = action((activeItem: Doc) => { - if (![PresMovement.Zoom, PresMovement.Pan, PresMovement.Jump, PresMovement.None].includes(StrCast(activeItem.presMovement) as any)) { - activeItem.presMovement = PresMovement.Zoom; + if (![PresMovement.Zoom, PresMovement.Pan, PresMovement.Center, PresMovement.Jump, PresMovement.None].includes(StrCast(activeItem.presMovement) as any)) { + return PresMovement.Zoom; } return StrCast(activeItem.presMovement); }); @@ -890,7 +893,7 @@ export class PresBox extends ViewBoxBaseComponent() { else this.regularSelect(doc, ref, drag, focus); }; - static keyEventsWrapper = (e: KeyboardEvent) => PresBox.Instance.keyEvents(e); + static keyEventsWrapper = (e: KeyboardEvent) => PresBox.Instance?.keyEvents(e); // Key for when the presentaiton is active @action @@ -1097,7 +1100,7 @@ export class PresBox extends ViewBoxBaseComponent() { let timeInMS = Number(number) * 1000; if (change) timeInMS += change; if (timeInMS < 100) timeInMS = 100; - if (timeInMS > 10000) timeInMS = 10000; + if (timeInMS > 100000) timeInMS = 100000; setter(timeInMS); }; setTransitionTime = (number: String, change?: number) => { @@ -1155,6 +1158,12 @@ export class PresBox extends ViewBoxBaseComponent() { activeItem.openDocument = !activeItem.openDocument; this.selectedArray.forEach(doc => (doc.openDocument = activeItem.openDocument)); }; + @undoBatch + @action + updateEaseFunc = (activeItem: Doc) => { + activeItem.presEaseFunc = activeItem.presEaseFunc === 'linear' ? 'ease' : 'linear'; + this.selectedArray.forEach(doc => (doc.presEaseFunc = activeItem.presEaseFunc)); + }; @undoBatch @action @@ -1223,7 +1232,7 @@ export class PresBox extends ViewBoxBaseComponent() { let duration = activeItem.presDuration ? NumCast(activeItem.presDuration) / 1000 : 2; if (activeItem.type === DocumentType.AUDIO) duration = NumCast(activeItem.duration); const effect = activeItem.presEffect ? activeItem.presEffect : PresMovement.None; - activeItem.presMovement = activeItem.presMovement ? activeItem.presMovement : PresMovement.Zoom; + // activeItem.presMovement = activeItem.presMovement ? activeItem.presMovement : PresMovement.Zoom; return (
() {
{isPresCollection || (isPresCollection && isPinWithView) ? null : presMovement(PresMovement.None)} + {presMovement(PresMovement.Center)} {presMovement(PresMovement.Zoom)} {presMovement(PresMovement.Pan)} {isPresCollection || (isPresCollection && isPinWithView) ? null : presMovement(PresMovement.Jump)} @@ -1281,7 +1291,7 @@ export class PresBox extends ViewBoxBaseComponent() {
- {PresBox.inputter('0.1', '0.1', '10', transitionSpeed, true, this.setTransitionTime)} + {PresBox.inputter('0.1', '0.1', '100', transitionSpeed, true, this.setTransitionTime)}
Fast
Medium
@@ -1317,6 +1327,11 @@ export class PresBox extends ViewBoxBaseComponent() { Lightbox
+ {'Transition movement style'}
}> +
this.updateEaseFunc(activeItem)}> + {`${StrCast(activeItem.presEaseFunc, 'ease')}`} +
+
{type === DocumentType.AUDIO || type === DocumentType.VID ? null : ( <> diff --git a/src/client/views/nodes/trails/PresEnums.ts b/src/client/views/nodes/trails/PresEnums.ts index 034f7588b..8c8b83fbf 100644 --- a/src/client/views/nodes/trails/PresEnums.ts +++ b/src/client/views/nodes/trails/PresEnums.ts @@ -1,6 +1,7 @@ export enum PresMovement { Zoom = 'zoom', Pan = 'pan', + Center = 'center', Jump = 'jump', None = 'none', } diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx index ee2ae10a7..265328036 100644 --- a/src/client/views/pdf/AnchorMenu.tsx +++ b/src/client/views/pdf/AnchorMenu.tsx @@ -210,14 +210,13 @@ export class AnchorMenu extends AntimodeMenu { ), - //NOTE: link popup is currently in progress {'Find document to link to selected text'}
}> , - , + , AnchorMenu.Instance.StartCropDrag === unimplementedFunction ? ( <> ) : ( diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 0703ca9b4..906dff377 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -76,7 +76,7 @@ export class PDFViewer extends React.Component { private _lastSearch = false; private _viewerIsSetup = false; private _ignoreScroll = false; - private _initialScroll: Opt; + private _initialScroll: { loc: Opt; easeFunc: 'linear' | 'ease' | undefined } | undefined; private _forcedScroll = true; selectionText = () => this._selectionText; @@ -164,6 +164,8 @@ export class PDFViewer extends React.Component { } }; + _scrollStopper: undefined | (() => void); + // 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, scrollTop: number, options: DocFocusOptions) => { @@ -173,12 +175,12 @@ export class PDFViewer extends React.Component { 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)); if (scrollTo !== undefined && scrollTo !== this.props.layoutDoc._scrollTop) { - if (!this._pdfViewer) this._initialScroll = scrollTo; - else if (!options.instant) smoothScroll((focusSpeed = options.zoomTime??500), mainCont, scrollTo); + 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); else this._mainCont.current?.scrollTo({ top: Math.abs(scrollTo || 0) }); } } else { - this._initialScroll = NumCast(this.props.layoutDoc._scrollTop); + this._initialScroll = { loc: NumCast(this.props.layoutDoc._scrollTop), easeFunc: options.easeFunc }; } return focusSpeed; }; @@ -202,7 +204,7 @@ export class PDFViewer extends React.Component { this.gotoPage(NumCast(this.props.Document._curPage, 1)); } document.removeEventListener('pagesinit', this.pagesinit); - var quickScroll: string | undefined = this._initialScroll ? this._initialScroll.toString() : ''; + var quickScroll: { loc?: string; easeFunc?: 'ease' | 'linear' } | undefined = { loc: this._initialScroll ? this._initialScroll.loc?.toString() : '', easeFunc: this._initialScroll ? this._initialScroll.easeFunc : undefined }; this._disposers.scale = reaction( () => NumCast(this.props.layoutDoc._viewScale, 1), scale => (this._pdfViewer.currentScaleValue = scale), @@ -213,7 +215,7 @@ export class PDFViewer extends React.Component { pos => { if (!this._ignoreScroll) { this._showWaiting && this.setupPdfJsViewer(); - const viewTrans = quickScroll ?? StrCast(this.props.Document._viewTransition); + const viewTrans = quickScroll?.loc ?? StrCast(this.props.Document._viewTransition); const durationMiliStr = viewTrans.match(/([0-9]*)ms/); const durationSecStr = viewTrans.match(/([0-9.]*)s/); const duration = durationMiliStr ? Number(durationMiliStr[1]) : durationSecStr ? Number(durationSecStr[1]) * 1000 : 0; @@ -221,7 +223,7 @@ export class PDFViewer extends React.Component { if (duration) { setTimeout( () => { - this._mainCont.current && smoothScroll(duration, this._mainCont.current, pos); + this._mainCont.current && (this._scrollStopper = smoothScroll(duration, this._mainCont.current, pos, this._initialScroll?.easeFunc ?? 'ease', this._scrollStopper)); setTimeout(() => (this._forcedScroll = false), duration); }, this._mainCont.current ? 0 : 250 @@ -236,7 +238,7 @@ export class PDFViewer extends React.Component { ); quickScroll = undefined; if (this._initialScroll !== undefined && this._mainCont.current) { - this._mainCont.current?.scrollTo({ top: Math.abs(this._initialScroll || 0) }); + this._mainCont.current?.scrollTo({ top: Math.abs(this._initialScroll?.loc || 0) }); this._initialScroll = undefined; } }; @@ -295,7 +297,7 @@ export class PDFViewer extends React.Component { @action scrollToAnnotation = (scrollToAnnotation: Doc) => { if (scrollToAnnotation) { - this.scrollFocus(scrollToAnnotation, NumCast(scrollToAnnotation.y), {zoomTime: 500}); + this.scrollFocus(scrollToAnnotation, NumCast(scrollToAnnotation.y), { zoomTime: 500 }); Doc.linkFollowHighlight(scrollToAnnotation); } }; @@ -452,6 +454,7 @@ export class PDFViewer extends React.Component { }; 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) { this._setPreviewCursor(e.clientX, e.clientY, false, false); } diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx index b78c8654f..aac488559 100644 --- a/src/client/views/search/SearchBox.tsx +++ b/src/client/views/search/SearchBox.tsx @@ -4,10 +4,12 @@ import { observer } from 'mobx-react'; import * as React from 'react'; import { DirectLinksSym, Doc, DocListCast, DocListCastAsync, Field } from '../../../fields/Doc'; import { Id } from '../../../fields/FieldSymbols'; -import { StrCast } from '../../../fields/Types'; +import { DocCast, StrCast } from '../../../fields/Types'; +import { StopEvent } from '../../../Utils'; import { DocUtils } from '../../documents/Documents'; import { DocumentType } from '../../documents/DocumentTypes'; import { DocumentManager } from '../../util/DocumentManager'; +import { LinkManager } from '../../util/LinkManager'; import { CollectionDockingView } from '../collections/CollectionDockingView'; import { ViewBoxBaseComponent } from '../DocComponent'; import { FieldView, FieldViewProps } from '../nodes/FieldView'; @@ -20,6 +22,8 @@ const ERROR = 0.03; export interface SearchBoxProps extends FieldViewProps { linkSearch: boolean; linkFrom?: (() => Doc | undefined) | undefined; + linkCreateAnchor?: () => Doc | undefined; + linkCreated?: (link: Doc) => void; } /** @@ -111,10 +115,11 @@ export class SearchBox extends ViewBoxBaseComponent() { // TODO: nda -- Change this method to change what happens when you click on the item. makeLink = action((linkTo: Doc) => { - if (this.props.linkFrom) { - const linkFrom = this.props.linkFrom(); + if (this.props.linkCreateAnchor) { + const linkFrom = this.props.linkCreateAnchor(); if (linkFrom) { - DocUtils.MakeLink({ doc: linkFrom }, { doc: linkTo }); + const link = DocUtils.MakeLink({ doc: linkFrom }, { doc: linkTo }); + link && this.props.linkCreated?.(link); } } }); @@ -380,7 +385,7 @@ export class SearchBox extends ViewBoxBaseComponent() { const query = StrCast(this._searchString); Doc.SetSearchQuery(query); - Array.from(this._results.keys()).forEach(doc => DocumentManager.Instance.getFirstDocumentView(doc)?.ComponentView?.search?.(this._searchString, undefined, true)); + if (!this.props.linkSearch) Array.from(this._results.keys()).forEach(doc => DocumentManager.Instance.getFirstDocumentView(doc)?.ComponentView?.search?.(this._searchString, undefined, true)); this._results.clear(); if (query) { @@ -437,6 +442,8 @@ export class SearchBox extends ViewBoxBaseComponent() { const resultsJSX = Array(); + const fromDoc = this.props.linkFrom?.(); + sortedResults.forEach(result => { var className = 'searchBox-results-scroll-view-result'; @@ -460,6 +467,13 @@ export class SearchBox extends ViewBoxBaseComponent() { e.stopPropagation(); } } + style={{ + fontWeight: DocListCast(fromDoc?.links).find( + link => Doc.AreProtosEqual(LinkManager.getOppositeAnchor(link, fromDoc!), result[0] as Doc) || Doc.AreProtosEqual(DocCast(LinkManager.getOppositeAnchor(link, fromDoc!)?.annotationOn), result[0] as Doc) + ) + ? 'bold' + : '', + }} className={className}>
{title as string}
{formattedType}
@@ -482,7 +496,10 @@ export class SearchBox extends ViewBoxBaseComponent() { defaultValue={''} autoComplete="off" onChange={this.onInputChange} - onKeyPress={e => (e.key === 'Enter' ? this.submitSearch() : null)} + onKeyPress={e => { + e.key === 'Enter' ? this.submitSearch() : null; + e.stopPropagation(); + }} type="text" placeholder="Search..." id="search-input" -- cgit v1.2.3-70-g09d2