aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorbobzel <zzzman@gmail.com>2022-12-16 15:07:50 -0500
committerbobzel <zzzman@gmail.com>2022-12-16 15:07:50 -0500
commit403d1c4fece9efa663e0fd7161afff9f27cf670c (patch)
tree8287d487c4373655e6012d978a4af81058e6dc8d /src
parent61683e5e084f0b3a6c53bde08295a25b53ea2db3 (diff)
fixed problem with undo. regularized all linkfollowing anchor fields to start with followLink<xxx>. 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
Diffstat (limited to 'src')
-rw-r--r--src/Utils.ts29
-rw-r--r--src/client/documents/Documents.ts4
-rw-r--r--src/client/util/CurrentUserUtils.ts35
-rw-r--r--src/client/util/LinkFollower.ts78
-rw-r--r--src/client/views/DocumentButtonBar.scss17
-rw-r--r--src/client/views/DocumentButtonBar.tsx68
-rw-r--r--src/client/views/GestureOverlay.tsx1
-rw-r--r--src/client/views/PropertiesView.tsx88
-rw-r--r--src/client/views/collections/CollectionNoteTakingView.tsx4
-rw-r--r--src/client/views/collections/CollectionPileView.tsx2
-rw-r--r--src/client/views/collections/CollectionStackingView.tsx4
-rw-r--r--src/client/views/collections/CollectionTimeView.tsx2
-rw-r--r--src/client/views/collections/TabDocView.tsx5
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx6
-rw-r--r--src/client/views/collections/collectionLinear/CollectionLinearView.tsx3
-rw-r--r--src/client/views/linking/LinkPopup.scss5
-rw-r--r--src/client/views/linking/LinkPopup.tsx13
-rw-r--r--src/client/views/nodes/CollectionFreeFormDocumentView.tsx2
-rw-r--r--src/client/views/nodes/DocumentLinksButton.tsx2
-rw-r--r--src/client/views/nodes/DocumentView.tsx11
-rw-r--r--src/client/views/nodes/LabelBox.tsx140
-rw-r--r--src/client/views/nodes/WebBox.tsx8
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx5
-rw-r--r--src/client/views/nodes/trails/PresBox.tsx37
-rw-r--r--src/client/views/nodes/trails/PresEnums.ts1
-rw-r--r--src/client/views/pdf/AnchorMenu.tsx3
-rw-r--r--src/client/views/pdf/PDFViewer.tsx21
-rw-r--r--src/client/views/search/SearchBox.tsx29
-rw-r--r--src/fields/util.ts10
29 files changed, 416 insertions, 217 deletions
diff --git a/src/Utils.ts b/src/Utils.ts
index 5e0514bc6..9d3b9eb2b 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -558,9 +558,13 @@ export namespace JSONUtils {
}
}
-const easeInOutQuad = (currentTime: number, start: number, change: number, duration: number) => {
- let newCurrentTime = currentTime / (duration / 2);
+const easeFunc = (transition: 'ease' | 'linear' | undefined, currentTime: number, start: number, change: number, duration: number) => {
+ if (transition === 'linear') {
+ let newCurrentTime = currentTime / duration; // currentTime / (duration / 2);
+ return start + newCurrentTime * change;
+ }
+ let newCurrentTime = currentTime / (duration / 2);
if (newCurrentTime < 1) {
return (change / 2) * newCurrentTime * newCurrentTime + start;
}
@@ -569,23 +573,28 @@ const easeInOutQuad = (currentTime: number, start: number, change: number, durat
return (-change / 2) * (newCurrentTime * (newCurrentTime - 2) - 1) + start;
};
-export function smoothScroll(duration: number, element: HTMLElement | HTMLElement[], to: number) {
+export function smoothScroll(duration: number, element: HTMLElement | HTMLElement[], to: number, transition: 'ease' | 'linear' | undefined, stopper?: () => void) {
+ stopper?.();
const elements = element instanceof HTMLElement ? [element] : element;
const starts = elements.map(element => element.scrollTop);
const startDate = new Date().getTime();
-
+ let _stop = false;
+ const stop = () => (_stop = true);
const animateScroll = () => {
const currentDate = new Date().getTime();
const currentTime = currentDate - startDate;
- elements.map((element, i) => (element.scrollTop = easeInOutQuad(currentTime, starts[i], to - starts[i], duration)));
+ elements.map((element, i) => (element.scrollTop = easeFunc(transition, currentTime, starts[i], to - starts[i], duration)));
- if (currentTime < duration) {
- requestAnimationFrame(animateScroll);
- } else {
- elements.forEach(element => (element.scrollTop = to));
+ if (!_stop) {
+ if (currentTime < duration) {
+ requestAnimationFrame(animateScroll);
+ } else {
+ elements.forEach(element => (element.scrollTop = to));
+ }
}
};
animateScroll();
+ return stop;
}
export function smoothScrollHorizontal(duration: number, element: HTMLElement | HTMLElement[], to: number) {
@@ -596,7 +605,7 @@ export function smoothScrollHorizontal(duration: number, element: HTMLElement |
const animateScroll = () => {
const currentDate = new Date().getTime();
const currentTime = currentDate - startDate;
- elements.map((element, i) => (element.scrollLeft = easeInOutQuad(currentTime, starts[i], to - starts[i], duration)));
+ elements.map((element, i) => (element.scrollLeft = easeFunc('ease', currentTime, starts[i], to - starts[i], duration)));
if (currentTime < duration) {
requestAnimationFrame(animateScroll);
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index d13d96dd3..634b14822 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -37,7 +37,7 @@ import { FontIconBox } from '../views/nodes/button/FontIconBox';
import { ColorBox } from '../views/nodes/ColorBox';
import { ComparisonBox } from '../views/nodes/ComparisonBox';
import { DataVizBox } from '../views/nodes/DataVizBox/DataVizBox';
-import { DocFocusOptions, OpenWhereMod } from '../views/nodes/DocumentView';
+import { DocFocusOptions, OpenWhere, OpenWhereMod } from '../views/nodes/DocumentView';
import { EquationBox } from '../views/nodes/EquationBox';
import { FieldViewProps } from '../views/nodes/FieldView';
import { FilterBox } from '../views/nodes/FilterBox';
@@ -1322,7 +1322,7 @@ export namespace DocUtils {
return DocUtils.ActiveRecordings.map(audio => {
const sourceDoc = getSourceDoc();
const link = sourceDoc && DocUtils.MakeLink({ doc: sourceDoc }, { doc: audio.getAnchor() || audio.props.Document }, 'recording annotation:linked recording', 'recording timeline');
- link && (link.followLinkLocation = 'add:right');
+ link && (link.followLinkLocation = OpenWhere.addRight);
return link;
});
}
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 635980e03..5549769aa 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -22,7 +22,7 @@ import { Colors } from "../views/global/globalEnums";
import { MainView } from "../views/MainView";
import { ButtonType, NumButtonType } from "../views/nodes/button/FontIconBox";
import { OverlayView } from "../views/OverlayView";
-import { DragManager } from "./DragManager";
+import { DragManager, dropActionType } from "./DragManager";
import { MakeTemplate } from "./DropConverter";
import { LinkManager } from "./LinkManager";
import { ScriptingGlobals } from "./ScriptingGlobals";
@@ -306,7 +306,7 @@ export class CurrentUserUtils {
const creatorBtns = CurrentUserUtils.creatorBtnDescriptors(doc).map((reqdOpts) => {
const btn = dragCreatorDoc ? DocListCast(dragCreatorDoc.data).find(doc => doc.title === reqdOpts.title): undefined;
const opts:DocumentOptions = {...OmitKeys(reqdOpts, ["funcs", "scripts", "backgroundColor"]).omit,
- _nativeWidth: 50, _nativeHeight: 50, _width: 35, _height: 35, _hideContextMenu: true, _stayInCollection: true, _dropAction: "alias",
+ _nativeWidth: 50, _nativeHeight: 50, _width: 35, _height: 35, _hideContextMenu: true, _stayInCollection: true,
btnType: ButtonType.ToolButton, backgroundColor: reqdOpts.backgroundColor ?? Colors.DARK_GRAY, color: Colors.WHITE, system: true,
_removeDropProperties: new List<string>(["_stayInCollection"]),
};
@@ -316,7 +316,7 @@ export class CurrentUserUtils {
const reqdOpts:DocumentOptions = {
title: "Basic Item Creators", _showTitle: "title", _xMargin: 0, _stayInCollection: true, _hideContextMenu: true, _chromeHidden: true, system: true,
_autoHeight: true, _width: 500, _height: 300, _fitWidth: true, _columnWidth: 40, ignoreClick: true, _lockedPosition: true, _forceActive: true,
- childDocumentsActive: true
+ childDocumentsActive: true, childDropAction: 'alias'
};
const reqdScripts = { dropConverter: "convertToButtons(dragData)" };
return DocUtils.AssignScripts(DocUtils.AssignOpts(dragCreatorDoc, reqdOpts, creatorBtns) ?? Docs.Create.MasonryDocument(creatorBtns, reqdOpts), reqdScripts);
@@ -352,15 +352,15 @@ export class CurrentUserUtils {
const btnDoc = myLeftSidebarMenu ? DocListCast(myLeftSidebarMenu.data).find(doc => doc.title === title) : undefined;
const reqdBtnOpts:DocumentOptions = {
title, icon, target, btnType: ButtonType.MenuButton, system: true, dontUndo: true, dontRegisterView: true,
- _width: 60, _height: 60, _stayInCollection: true, _hideContextMenu: true, _chromeHidden: true, _dropAction: "alias",
- _removeDropProperties: new List<string>(["dropAction", "_stayInCollection"]),
+ _width: 60, _height: 60, _stayInCollection: true, _hideContextMenu: true, _chromeHidden: true,
+ _removeDropProperties: new List<string>(["_stayInCollection"]),
};
return DocUtils.AssignScripts(DocUtils.AssignOpts(btnDoc, reqdBtnOpts) ?? Docs.Create.FontIconDocument(reqdBtnOpts), scripts, funcs);
});
const reqdStackOpts:DocumentOptions ={
- title: "menuItemPanel", childDropAction: "alias", backgroundColor: Colors.DARK_GRAY, boxShadow: "rgba(0,0,0,0)", dontRegisterView: true, ignoreClick: true,
- _chromeHidden: true, _gridGap: 0, _yMargin: 0, _yPadding: 0, _xMargin: 0, _autoHeight: false, _width: 60, _columnWidth: 60, _lockedPosition: true, system: true
+ title: "menuItemPanel", childDropAction: "same", backgroundColor: Colors.DARK_GRAY, boxShadow: "rgba(0,0,0,0)", dontRegisterView: true, ignoreClick: true,
+ _chromeHidden: true, _gridGap: 0, _yMargin: 0, _yPadding: 0, _xMargin: 0, _autoHeight: false, _width: 60, _columnWidth: 60, _lockedPosition: true, system: true,
};
return DocUtils.AssignDocField(doc, field, (opts, items) => Docs.Create.StackingDocument(items??[], opts), reqdStackOpts, menuBtns, { dropConverter: "convertToButtons(dragData)" });
}
@@ -397,14 +397,14 @@ export class CurrentUserUtils {
// sets up the main document for the mobile button
static mobileButton = (opts: DocumentOptions, docs: Doc[]) => Docs.Create.MulticolumnDocument(docs, {
...opts,
- _removeDropProperties: new List<string>(["dropAction"]), _nativeWidth: 900, _nativeHeight: 250, _width: 900, _height: 250, _yMargin: 15,
+ _nativeWidth: 900, _nativeHeight: 250, _width: 900, _height: 250, _yMargin: 15,
borderRounding: "5px", boxShadow: "0 0", system: true
}) as any as Doc
// sets up the text container for the information contained within the mobile button
static mobileTextContainer = (opts: DocumentOptions, docs: Doc[]) => Docs.Create.MultirowDocument(docs, {
...opts,
- _removeDropProperties: new List<string>(["dropAction"]), _nativeWidth: 450, _nativeHeight: 250, _width: 450, _height: 250, _yMargin: 25,
+ _nativeWidth: 450, _nativeHeight: 250, _width: 450, _height: 250, _yMargin: 25,
backgroundColor: "rgba(0,0,0,0)", borderRounding: "0", boxShadow: "0 0", ignoreClick: true, system: true
}) as any as Doc
@@ -573,8 +573,8 @@ export class CurrentUserUtils {
})
static createToolButton = (opts: DocumentOptions) => Docs.Create.FontIconDocument({
- btnType: ButtonType.ToolButton, _forceActive: true, _dropAction: "alias", _hideContextMenu: true,
- _removeDropProperties: new List<string>(["_dropAction", "_hideContextMenu", "stayInCollection"]),
+ btnType: ButtonType.ToolButton, _forceActive: true, _hideContextMenu: true,
+ _removeDropProperties: new List<string>([ "_hideContextMenu", "stayInCollection"]),
_nativeWidth: 40, _nativeHeight: 40, _width: 40, _height: 40, system: true, ...opts,
})
@@ -595,12 +595,15 @@ export class CurrentUserUtils {
// { scripts: { onClick: 'nextKeyFrame(_readOnly_)'}, opts:{title: "Fwd", icon: "chevron-right", toolTip: "Next Animation Frame", btnType: ButtonType.ClickButton, width: 20,} },
];
const btns = btnDescs.map(desc => dockBtn({_width: 30, _height: 30, dontUndo: true, _stayInCollection: true, ...desc.opts}, desc.scripts));
- const dockBtnsReqdOpts = {
- title: "docked buttons", _height: 40, flexGap: 0, boxShadow: "standard",
+ const dockBtnsReqdOpts:DocumentOptions = {
+ title: "docked buttons", _height: 40, flexGap: 0, boxShadow: "standard", childDropAction: 'alias',
childDontRegisterViews: true, linearViewIsExpanded: true, linearViewExpandable: true, ignoreClick: true
};
reaction(() => UndoManager.redoStack.slice(), () => Doc.GetProto(btns.find(btn => btn.title === "redo")!).opacity = UndoManager.CanRedo() ? 1 : 0.4, { fireImmediately: true });
- reaction(() => UndoManager.undoStack.slice(), () => Doc.GetProto(btns.find(btn => btn.title === "undo")!).opacity = UndoManager.CanUndo() ? 1 : 0.4, { fireImmediately: true });
+ reaction(() => UndoManager.undoStack.slice(), () => {
+ console.log(UndoManager.undoStack)
+ Doc.GetProto(btns.find(btn => btn.title === "undo")!).opacity = UndoManager.CanUndo() ? 1 : 0.4;
+ }, { fireImmediately: true });
return DocUtils.AssignDocField(doc, field, (opts, items) => this.linearButtonList(opts, items??[]), dockBtnsReqdOpts, btns);
}
@@ -687,7 +690,7 @@ export class CurrentUserUtils {
_nativeWidth: params.width ?? 30, _width: params.width ?? 30,
_height: 30, _nativeHeight: 30,
_stayInCollection: true, _hideContextMenu: true, _lockedPosition: true,
- _dropAction: "alias", _removeDropProperties: new List<string>(["dropAction", "_stayInCollection"]),
+ _removeDropProperties: new List<string>([ "_stayInCollection"]),
};
const reqdFuncs:{[key:string]:any} = {
...params.funcs,
@@ -698,7 +701,7 @@ export class CurrentUserUtils {
/// Initializes all the default buttons for the top bar context menu
static setupContextMenuButtons(doc: Doc, field="myContextMenuBtns") {
- const reqdCtxtOpts = { title: "context menu buttons", flexGap: 0, childDontRegisterViews: true, linearViewIsExpanded: true, ignoreClick: true, linearViewExpandable: false, _height: 35 };
+ const reqdCtxtOpts:DocumentOptions = { title: "context menu buttons", flexGap: 0, childDropAction: 'alias', childDontRegisterViews: true, linearViewIsExpanded: true, ignoreClick: true, linearViewExpandable: false, _height: 35 };
const ctxtMenuBtnsDoc = DocUtils.AssignDocField(doc, field, (opts, items) => this.linearButtonList(opts, items??[]), reqdCtxtOpts, undefined);
const ctxtMenuBtns = CurrentUserUtils.contextMenuTools().map(params => {
const menuBtnDoc = DocListCast(ctxtMenuBtnsDoc?.data).find(doc => doc.title === params.title);
diff --git a/src/client/util/LinkFollower.ts b/src/client/util/LinkFollower.ts
index 0285803e8..94badbb44 100644
--- a/src/client/util/LinkFollower.ts
+++ b/src/client/util/LinkFollower.ts
@@ -1,5 +1,6 @@
import { action, runInAction } from 'mobx';
-import { Doc, DocListCast, Opt } from '../../fields/Doc';
+import { Doc, DocListCast, Opt, WidthSym } from '../../fields/Doc';
+import { listSpec } from '../../fields/Schema';
import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../fields/Types';
import { DocumentType } from '../documents/DocumentTypes';
import { DocumentDecorations } from '../views/DocumentDecorations';
@@ -98,32 +99,59 @@ export class LinkFollower {
: linkDoc.anchor1
) as Doc;
if (target) {
- const options: DocFocusOptions = {
- playAudio: BoolCast(sourceDoc.followLinkAudio),
- toggleTarget: BoolCast(sourceDoc.followLinkToggle),
- willPan: true,
- willPanZoom: BoolCast(LinkManager.getOppositeAnchor(linkDoc, target)?.followLinkZoom, false),
- zoomTime: NumCast(LinkManager.getOppositeAnchor(linkDoc, target)?.linkTransitionTime, 500),
- zoomScale: Cast(sourceDoc.linkZoomScale, 'number', null),
- effect: sourceDoc,
- originatingDoc: sourceDoc,
+ const doFollow = (canToggle?: boolean) => {
+ const options: DocFocusOptions = {
+ playAudio: BoolCast(sourceDoc.followLinkAudio),
+ toggleTarget: canToggle && BoolCast(sourceDoc.followLinkToggle),
+ willPan: true,
+ willPanZoom: BoolCast(LinkManager.getOppositeAnchor(linkDoc, target)?.followLinkZoom, false),
+ zoomTime: NumCast(LinkManager.getOppositeAnchor(linkDoc, target)?.followLinkTransitionTime, 500),
+ zoomScale: Cast(sourceDoc.followLinkZoomScale, 'number', null),
+ easeFunc: StrCast(sourceDoc.followLinkEase, 'ease') as any,
+ effect: sourceDoc,
+ originatingDoc: sourceDoc,
+ };
+ if (target.TourMap) {
+ const fieldKey = Doc.LayoutFieldKey(target);
+ const tour = DocListCast(target[fieldKey]).reverse();
+ LightboxView.SetLightboxDoc(currentContext, undefined, tour);
+ setTimeout(LightboxView.Next);
+ allFinished();
+ } else {
+ const containerAnnoDoc = Cast(target.annotationOn, Doc, null);
+ const containerDoc = containerAnnoDoc || target;
+ var containerDocContext = containerDoc?.context ? [Cast(containerDoc?.context, Doc, null)] : ([] as Doc[]);
+ while (containerDocContext.length && !DocumentManager.Instance.getDocumentView(containerDocContext[0]) && containerDocContext[0].context) {
+ containerDocContext = [Cast(containerDocContext[0].context, Doc, null), ...containerDocContext];
+ }
+ const targetContexts = LightboxView.LightboxDoc ? [containerAnnoDoc || containerDocContext[0]].filter(a => a) : containerDocContext;
+ DocumentManager.Instance.jumpToDocument(target, options, (doc, finished) => createViewFunc(doc, StrCast(linkDoc.followLinkLocation, OpenWhere.inPlace), finished), targetContexts, allFinished);
+ }
};
- if (target.TourMap) {
- const fieldKey = Doc.LayoutFieldKey(target);
- const tour = DocListCast(target[fieldKey]).reverse();
- LightboxView.SetLightboxDoc(currentContext, undefined, tour);
- setTimeout(LightboxView.Next);
- allFinished();
- } else {
- const containerAnnoDoc = Cast(target.annotationOn, Doc, null);
- const containerDoc = containerAnnoDoc || target;
- var containerDocContext = containerDoc?.context ? [Cast(containerDoc?.context, Doc, null)] : ([] as Doc[]);
- while (containerDocContext.length && !DocumentManager.Instance.getDocumentView(containerDocContext[0]) && containerDocContext[0].context) {
- containerDocContext = [Cast(containerDocContext[0].context, Doc, null), ...containerDocContext];
+ let movedTarget = false;
+ if (sourceDoc.followLinkLocation === OpenWhere.inParent) {
+ const sourceDocParent = DocCast(sourceDoc.context);
+ if (target.context instanceof Doc && target.context !== sourceDocParent) {
+ Doc.RemoveDocFromList(target.context, Doc.LayoutFieldKey(target.context), target);
+ movedTarget = true;
+ }
+ if (!DocListCast(sourceDocParent[Doc.LayoutFieldKey(sourceDocParent)]).includes(target)) {
+ Doc.AddDocToList(sourceDocParent, Doc.LayoutFieldKey(sourceDocParent), target);
+ movedTarget = true;
+ }
+ target.context = sourceDocParent;
+ const moveTo = [NumCast(sourceDoc.x) + NumCast(sourceDoc.followLinkXoffset), NumCast(sourceDoc.y) + NumCast(sourceDoc.followLinkYoffset)];
+ if (sourceDoc.followLinkXoffset !== undefined && moveTo[0] !== target.x) {
+ target.x = moveTo[0];
+ movedTarget = true;
+ }
+ if (sourceDoc.followLinkYoffset !== undefined && moveTo[1] !== target.y) {
+ target.y = moveTo[1];
+ movedTarget = true;
}
- const targetContexts = LightboxView.LightboxDoc ? [containerAnnoDoc || containerDocContext[0]].filter(a => a) : containerDocContext;
- DocumentManager.Instance.jumpToDocument(target, options, (doc, finished) => createViewFunc(doc, StrCast(linkDoc.followLinkLocation, 'inPlace'), finished), targetContexts, allFinished);
- }
+ if (movedTarget) setTimeout(doFollow);
+ else doFollow(true);
+ } else doFollow(true);
} else {
allFinished();
}
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 : (
+ <div className="documentButtonBar-icon documentButtonBar-link">
+ <div className="documentButtonBar-linkTypes">
+ <Tooltip title={<div>search for target</div>}>
+ <div className="documentButtonBar-button">
+ <button style={{ backgroundColor: 'transparent', width: 35, height: 35, display: 'flex', justifyContent: 'center', alignItems: 'center', position: 'relative' }} onPointerDown={this.toggleLinkSearch}>
+ <FontAwesomeIcon style={{ position: 'absolute', transform: 'scale(1.5)' }} icon={'search'} size="lg" />
+ <FontAwesomeIcon style={{ position: 'absolute', transform: 'scale(0.5)', transformOrigin: 'center', top: 9, left: 2 }} icon={'link'} size="lg" />
+ </button>
+ </div>
+ </Tooltip>
+ </div>
+ <div style={{ width: 25, height: 25 }}>
+ <DocumentLinksButton View={this.view0} AlwaysOn={true} InMenu={true} StartLink={true} />
+ </div>
+ </div>
+ );
+ }
+
@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 : (
- <Tooltip
- title={
- <>
- <div className="dash-tooltip">{'Open Sharing Manager'}</div>
- </>
- }>
+ <Tooltip title={<div className="dash-tooltip">{'Open Sharing Manager'}</div>}>
<div className="documentButtonBar-icon" style={{ color: 'white' }} onClick={e => SharingManager.Instance.open(this.view0, targetDoc)}>
<FontAwesomeIcon className="documentdecorations-icon" icon="users" />
</div>
@@ -347,12 +365,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
get metadataButton() {
const view0 = this.view0;
return !view0 ? null : (
- <Tooltip
- title={
- <>
- <div className="dash-tooltip">Show metadata panel</div>
- </>
- }>
+ <Tooltip title={<div className="dash-tooltip">Show metadata panel</div>}>
<div className="documentButtonBar-linkFlyout">
<Flyout
anchorPoint={anchorPoints.LEFT_TOP}
@@ -464,6 +477,12 @@ export class DocumentButtonBar extends React.Component<{ views: () => (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
<div className="documentButtonBar-button">
<DocumentLinksButton View={this.view0} AlwaysOn={true} InMenu={true} ShowCount={true} />
</div>
- <div className="documentButtonBar-button">
- <DocumentLinksButton View={this.view0} AlwaysOn={true} InMenu={true} StartLink={true} />
- </div>
+ {this._showLinkPopup ? (
+ <div style={{ position: 'absolute', zIndex: 1000 }}>
+ <LinkPopup
+ key="popup"
+ showPopup={this._showLinkPopup}
+ linkCreated={link => (link.linkDisplay = !this.props.views().lastElement()?.rootDoc.isLinkButton)}
+ linkCreateAnchor={() => this.props.views().lastElement()?.ComponentView?.getAnchor?.()}
+ linkFrom={() => this.props.views().lastElement()?.rootDoc}
+ />
+ </div>
+ ) : (
+ <div className="documentButtonBar-button">{this.linkButton}</div>
+ )}
+
{(DocumentLinksButton.StartLink || Doc.UserDoc()['documentLinksButton-fullMenu']) && DocumentLinksButton.StartLink !== doc ? (
<div className="documentButtonBar-button">
<DocumentLinksButton View={this.view0} AlwaysOn={true} InMenu={true} StartLink={false} />
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<GestureOverlayProps> {
onPointerUp: data.pointerUp ? ScriptField.MakeScript(data.pointerUp) : undefined,
onPointerDown: data.pointerDown ? ScriptField.MakeScript(data.pointerDown) : undefined,
backgroundColor: data.backgroundColor,
- _removeDropProperties: new List<string>(['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<PropertiesViewProps> {
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<PropertiesViewProps> {
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<PropertiesViewProps> {
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<PropertiesViewProps> {
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<PropertiesViewProps> {
<p>Follow by</p>
<select onChange={e => this.changeFollowBehavior(e.currentTarget.value)} value={StrCast(this.sourceAnchor?.followLinkLocation, 'default')}>
<option value="default">Default</option>
- <option value="add:left">Opening in new left pane</option>
- <option value="add:right">Opening in new right pane</option>
- <option value="replace:left">Replacing left tab</option>
- <option value="replace:right">Replacing right tab</option>
- <option value="fullScreen">Overlaying current tab</option>
- <option value="lightbox">Opening in lightbox</option>
- <option value="add">Opening in new tab</option>
- <option value="replace">Replacing current tab</option>
- <option value="inPlace">Opening in place</option>
+ <option value={OpenWhere.addLeft}>Opening in new left pane</option>
+ <option value={OpenWhere.addRight}>Opening in new right pane</option>
+ <option value={OpenWhere.replaceLeft}>Replacing left tab</option>
+ <option value={OpenWhere.replaceRight}>Replacing right tab</option>
+ <option value={OpenWhere.fullScreen}>Overlaying current tab</option>
+ <option value={OpenWhere.lightbox}>Opening in lightbox</option>
+ <option value={OpenWhere.add}>Opening in new tab</option>
+ <option value={OpenWhere.replace}>Replacing current tab</option>
+ <option value={OpenWhere.inParent}>Opening in same collection</option>
+ <option value={OpenWhere.inPlace}>Opening in place</option>
{LinkManager.currentLink?.linksToAnnotation ? <option value="openExternal">Open in external page</option> : null}
</select>
</div>
<div className="propertiesView-input inline first" style={{ display: 'grid', gridTemplateColumns: '84px calc(100% - 134px) 50px' }}>
<p>Animation</p>
- <select style={{ width: '100%', gridColumn: 2 }} onChange={e => this.changeAnimationBehavior(e.currentTarget.value)} value={StrCast(this.sourceAnchor?.linkAnimEffect, 'default')}>
+ <select style={{ width: '100%', gridColumn: 2 }} onChange={e => this.changeAnimationBehavior(e.currentTarget.value)} value={StrCast(this.sourceAnchor?.followLinkAnimEffect, 'default')}>
<option value="default">Default</option>
{[PresEffect.None, PresEffect.Zoom, PresEffect.Lightspeed, PresEffect.Fade, PresEffect.Flip, PresEffect.Rotate, PresEffect.Bounce, PresEffect.Roll].map(effect => (
<option value={effect.toString()}>{effect.toString()}</option>
@@ -1642,9 +1655,9 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
'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
)}{' '}
<div
@@ -1681,9 +1694,42 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
<FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" />
</button>
</div>
+ <div className="propertiesView-input inline">
+ <p>Ease Transitions</p>
+ <button
+ style={{ background: this.sourceAnchor?.followLinkEase === 'linear' ? '' : '#4476f7', borderRadius: 3 }}
+ onPointerDown={e => this.toggleAnchorProp(e, 'followLinkEase', this.sourceAnchor, 'ease', 'linear')}
+ onClick={e => e.stopPropagation()}
+ className="propertiesButton">
+ <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" />
+ </button>
+ </div>
+ <div className="propertiesView-input inline">
+ <p>Capture Offset to Target</p>
+ <button
+ style={{ background: this.sourceAnchor?.followLinkXoffset === undefined ? '' : '#4476f7', borderRadius: 3 }}
+ onPointerDown={e => {
+ this.toggleAnchorProp(e, 'followLinkXoffset', this.sourceAnchor, NumCast(this.destinationAnchor?.x) - NumCast(this.sourceAnchor?.x), undefined);
+ this.toggleAnchorProp(e, 'followLinkYoffset', this.sourceAnchor, NumCast(this.destinationAnchor?.y) - NumCast(this.sourceAnchor?.y), undefined);
+ }}
+ onClick={e => e.stopPropagation()}
+ className="propertiesButton">
+ <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" />
+ </button>
+ </div>
+ <div className="propertiesView-input inline">
+ <p>Center Target (no zoom)</p>
+ <button
+ style={{ background: this.sourceAnchor?.followLinkZoomScale !== 0 ? '' : '#4476f7', borderRadius: 3 }}
+ onPointerDown={e => this.toggleAnchorProp(e, 'followLinkZoomScale', this.sourceAnchor, 0, 0.5, val => !val && this.sourceAnchor && (this.sourceAnchor.followLinkZoom = true))}
+ onClick={e => e.stopPropagation()}
+ className="propertiesButton">
+ <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" />
+ </button>
+ </div>
<div className="propertiesView-input inline" style={{ display: 'grid', gridTemplateColumns: '78px calc(100% - 108px) 50px' }}>
<p>Zoom %</p>
- <div className="ribbon-property" style={{ display: !targZoom ? 'none' : 'inline-flex' }}>
+ <div className="ribbon-property" style={{ display: !targZoom || this.sourceAnchor?.followLinkZoomScale === 0 ? 'none' : 'inline-flex' }}>
<input className="presBox-input" style={{ width: '100%' }} type="number" value={zoom} />
<div className="ribbon-propertyUpDown" style={{ display: 'flex', flexDirection: 'column' }}>
<div className="ribbon-propertyUpDownItem" onClick={undoBatch(() => this.setZoom(String(zoom), 0.1))}>
@@ -1695,18 +1741,18 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
</div>
</div>
<button
- style={{ background: !targZoom ? '' : '#4476f7', borderRadius: 3, gridColumn: 3 }}
+ style={{ background: !targZoom || this.sourceAnchor?.followLinkZoomScale === 0 ? '' : '#4476f7', borderRadius: 3, gridColumn: 3 }}
onPointerDown={e => this.toggleAnchorProp(e, 'followLinkZoom', this.sourceAnchor)}
onClick={e => e.stopPropagation()}
className="propertiesButton">
<FontAwesomeIcon className="fa-icon" icon={faArrowRight as IconLookup} size="lg" />
</button>
</div>
- {!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)}
<div
className={'slider-headers'}
style={{
- display: !targZoom ? 'none' : 'grid',
+ display: !targZoom || this.sourceAnchor?.followLinkZoomScale === 0 ? 'none' : 'grid',
justifyContent: 'space-between',
width: `calc(100% - ${indent * 2}px)`,
marginLeft: indent,
diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx
index 5e389e17e..26c73438c 100644
--- a/src/client/views/collections/CollectionNoteTakingView.tsx
+++ b/src/client/views/collections/CollectionNoteTakingView.tsx
@@ -181,7 +181,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
}
scrollToBottom = () => {
- 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<Partial<collection
}
scrollToBottom = () => {
- 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<Partial<collection
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?.(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<string>(['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<TabDocViewProps> {
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<Partial<collection
const focusSpeed = options?.instant ? 0 : didMove ? options.zoomTime ?? 500 : 0;
// glr: freeform transform speed can be set by adjusting presTransition field - needs a way of knowing when presentation is not active...
if (didMove) {
- scale && (this.Document[this.scaleFieldKey] = scale);
+ options.zoomScale && scale && (this.Document[this.scaleFieldKey] = scale);
this.setPan(panX, panY, focusSpeed, true); // docs that are floating in their collection can't be panned to from their collection -- need to propagate the pan to a parent freeform somehow
}
@@ -1343,6 +1343,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
addDocTab = action((doc: Doc, where: OpenWhere) => {
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<Partial<collection
doc.x = scr?.[0];
doc.y = scr?.[1];
});
- this.props.addDocTab(childDocs as any as Doc, OpenWhere.inParent);
+ this.props.addDocTab(childDocs as any as Doc, OpenWhere.inParentFromScreen);
this.props.ContainingCollectionView?.removeDocument(this.props.Document);
};
diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
index ff263c4f3..a2330c6b2 100644
--- a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
+++ b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
@@ -9,7 +9,7 @@ import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../../fields
import { emptyFunction, returnEmptyDoclist, returnTrue, StopEvent, Utils } from '../../../../Utils';
import { CollectionViewType } from '../../../documents/DocumentTypes';
import { DocumentManager } from '../../../util/DocumentManager';
-import { DragManager } from '../../../util/DragManager';
+import { DragManager, dropActionType } from '../../../util/DragManager';
import { Transform } from '../../../util/Transform';
import { Colors, Shadows } from '../../global/globalEnums';
import { DocumentLinksButton } from '../../nodes/DocumentLinksButton';
@@ -190,6 +190,7 @@ export class CollectionLinearView extends CollectionSubView() {
moveDocument={this.props.moveDocument}
addDocTab={this.props.addDocTab}
pinToPres={emptyFunction}
+ dropAction={StrCast(this.layoutDoc.childDropAction) as dropActionType}
rootSelected={this.props.isSelected}
removeDocument={this.props.removeDocument}
ScreenToLocalTransform={docXf}
diff --git a/src/client/views/linking/LinkPopup.scss b/src/client/views/linking/LinkPopup.scss
index 60c9ebfcd..b20ad9476 100644
--- a/src/client/views/linking/LinkPopup.scss
+++ b/src/client/views/linking/LinkPopup.scss
@@ -1,7 +1,7 @@
.linkPopup-container {
background: white;
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.19), 0 6px 6px rgba(0, 0, 0, 0.23);
- top: 35px;
+ top: 0;
height: 200px;
width: 200px;
position: absolute;
@@ -38,8 +38,7 @@
}
}
-
.searchBox-container {
background: pink;
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/linking/LinkPopup.tsx b/src/client/views/linking/LinkPopup.tsx
index 0bcb68f82..7bdace2b6 100644
--- a/src/client/views/linking/LinkPopup.tsx
+++ b/src/client/views/linking/LinkPopup.tsx
@@ -11,10 +11,13 @@ import { SearchBox } from '../search/SearchBox';
import { DefaultStyleProvider } from '../StyleProvider';
import './LinkPopup.scss';
import React = require('react');
+import { OpenWhere } from '../nodes/DocumentView';
interface LinkPopupProps {
showPopup: boolean;
linkFrom?: () => 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<LinkPopupProps> {
// 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<HTMLInputElement>) => {
- this.linkURL = e.target.value;
- };
+ onLinkChange = (e: React.ChangeEvent<HTMLInputElement>) => (this.linkURL = e.target.value);
getPWidth = () => 500;
getPHeight = () => 500;
@@ -67,7 +66,9 @@ export class LinkPopup extends React.Component<LinkPopupProps> {
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<CollectionFreeF
topDoc.x = spt[0];
topDoc.y = spt[1];
this.props.removeDocument?.(topDoc);
- this.props.addDocTab(topDoc, OpenWhere.inParent);
+ this.props.addDocTab(topDoc, OpenWhere.inParentFromScreen);
} else {
const spt = this.screenToLocalTransform().inverse().transformPoint(0, 0);
const fpt = screenXf.transformPoint(spt[0], spt[1]);
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
index 99fa62fa7..6f3152981 100644
--- a/src/client/views/nodes/DocumentLinksButton.tsx
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -305,7 +305,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
top: 0,
pointerEvents: 'none',
}}>
- {!DocumentLinksButton.LinkEditorDocView ? this.linkButtonInner : <Tooltip title={<div className="dash-tooltip">{title}</div>}>{this.linkButtonInner}</Tooltip>}
+ <Tooltip title={<div className="dash-tooltip">{title}</div>}>{this.linkButtonInner}</Tooltip>
</div>
);
}
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<ViewAdjustment>;
export type DocFocusFunc = (doc: Doc, options: DocFocusOptions) => void;
@@ -737,7 +742,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
document.removeEventListener('pointerup', this.onPointerUp);
this._cursorTimer && clearTimeout(this._cursorTimer);
this._cursorPress = false;
- this.startDragging(this._downX, this._downY, ((e.ctrlKey || e.altKey) && 'alias') || ((this.props.dropAction || this.Document.dropAction || undefined) as dropActionType));
+ this.startDragging(this._downX, this._downY, ((e.ctrlKey || e.altKey) && 'alias') || ((this.Document.dropAction || this.props.dropAction || undefined) as dropActionType));
}
}
e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document' however it does mark the event as cancelBubble=true which we test for in the move event handlers
@@ -854,7 +859,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
const portal = Docs.Create.FreeformDocument([], { _width: NumCast(this.layoutDoc._width) + 10, _height: NumCast(this.layoutDoc._height), _fitWidth: true, title: StrCast(this.props.Document.title) + ' [Portal]' });
DocUtils.MakeLink({ doc: this.props.Document }, { doc: portal }, 'portal to:portal from');
}
- this.Document.followLinkLocation = 'inPlace';
+ this.Document.followLinkLocation = OpenWhere.inPlace;
this.Document.followLinkZoom = true;
this.Document._isLinkButton = true;
};
@@ -1446,7 +1451,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
duration: Cast(presEffectDoc?.presTransition, 'number', null),
};
//prettier-ignore
- switch (StrCast(presEffectDoc?.presEffect, StrCast(presEffectDoc?.linkAnimEffect))) {
+ switch (StrCast(presEffectDoc?.presEffect, StrCast(presEffectDoc?.followLinkAnimEffect))) {
default:
case PresEffect.None: return renderDoc;
case PresEffect.Zoom: return <Zoom {...effectProps}>{renderDoc}</Zoom>;
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<FieldViewProps & LabelBoxProps>() {
+ public static LayoutString(fieldKey: string) {
+ return FieldView.LayoutString(LabelBox, fieldKey);
+ }
public static LayoutStringWithTitle(fieldStr: string, label?: string) {
- return !label ? LabelBox.LayoutString(fieldStr) : `<LabelBox fieldKey={'${fieldStr}'} label={'${label}'} {...props} />`; //e.g., "<ImageBox {...props} fieldKey={"data} />"
+ return !label ? LabelBox.LayoutString(fieldStr) : `<LabelBox fieldKey={'${fieldStr}'} label={'${label}'} {...props} />`; //e.g., "<ImageBox {...props} fieldKey={"data} />"
}
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<Doc>(docDragData.droppedDocuments.map((d, i) =>
- d.onDragStart ? docDragData.draggedDocuments[i] : d));
+ this.paramsDoc[(e.target as any).textContent] = new List<Doc>(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 (
- <div className="labelBox-outerDiv"
- onMouseLeave={action(() => this._mouseOver = false)}
- onMouseOver={action(() => this._mouseOver = true)}
- ref={this.createDropTarget} onContextMenu={this.specificContextMenu}
+ <div
+ className="labelBox-outerDiv"
+ onMouseLeave={action(() => (this._mouseOver = false))}
+ onMouseOver={action(() => (this._mouseOver = true))}
+ ref={this.createDropTarget}
+ onContextMenu={this.specificContextMenu}
style={{ boxShadow: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BoxShadow) }}>
- <div className="labelBox-mainButton" style={{
- backgroundColor: this.hoverColor,
- fontSize: StrCast(this.layoutDoc._fontSize),
- fontFamily: StrCast(this.layoutDoc._fontFamily) || "inherit",
- letterSpacing: StrCast(this.layoutDoc.letterSpacing),
- textTransform: StrCast(this.layoutDoc.textTransform) as any,
- paddingLeft: NumCast(this.rootDoc._xPadding),
- paddingRight: NumCast(this.rootDoc._xPadding),
- paddingTop: NumCast(this.rootDoc._yPadding),
- paddingBottom: NumCast(this.rootDoc._yPadding),
- width: this.props.PanelWidth(),
- height: this.props.PanelHeight(),
- whiteSpace: boxParams.singleLine ? "pre" : "pre-wrap"
- }} >
- <span style={{ width: boxParams.singleLine ? "" : "100%" }} ref={action((r: any) => this.fitTextToBox(r))}>
- {label.startsWith("#") ? (null) : label.replace(/([^a-zA-Z])/g, "$1\u200b")}
+ <div
+ className="labelBox-mainButton"
+ style={{
+ backgroundColor: this.hoverColor,
+ fontSize: StrCast(this.layoutDoc._fontSize),
+ fontFamily: StrCast(this.layoutDoc._fontFamily) || 'inherit',
+ letterSpacing: StrCast(this.layoutDoc.letterSpacing),
+ textTransform: StrCast(this.layoutDoc.textTransform) as any,
+ paddingLeft: NumCast(this.rootDoc._xPadding),
+ paddingRight: NumCast(this.rootDoc._xPadding),
+ paddingTop: NumCast(this.rootDoc._yPadding),
+ paddingBottom: NumCast(this.rootDoc._yPadding),
+ width: this.props.PanelWidth(),
+ height: this.props.PanelHeight(),
+ whiteSpace: boxParams.singleLine ? 'pre' : 'pre-wrap',
+ }}>
+ <span style={{ width: boxParams.singleLine ? '' : '100%' }} ref={action((r: any) => this.fitTextToBox(r))}>
+ {label.startsWith('#') ? null : label.replace(/([^a-zA-Z])/g, '$1\u200b')}
</span>
</div>
- <div className="labelBox-fieldKeyParams" >
- {!missingParams?.length ? (null) : missingParams.map(m => <div key={m} className="labelBox-missingParam">{m}</div>)}
+ <div className="labelBox-fieldKeyParams">
+ {!missingParams?.length
+ ? null
+ : missingParams.map(m => (
+ <div key={m} className="labelBox-missingParam">
+ {m}
+ </div>
+ ))}
</div>
</div>
);
}
-} \ 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<ViewBoxAnnotatableProps
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;
- this.goTo(scrollTop, duration);
+ this.goTo(scrollTop, duration, 'ease');
},
{ fireImmediately: true }
);
@@ -295,7 +295,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
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.getScrollHeight()));
if (scrollTo !== undefined && this._initialScroll === undefined) {
const focusSpeed = options.instant ? 0 : options.zoomTime ?? 500;
- this.goTo(scrollTo, focusSpeed);
+ this.goTo(scrollTo, focusSpeed, options.easeFunc);
return focusSpeed;
} else if (!this._webPageHasBeenRendered || !this.getScrollHeight() || this._initialScroll !== undefined) {
this._initialScroll = scrollTo;
@@ -497,11 +497,11 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
);
};
- goTo = (scrollTop: number, duration: number) => {
+ 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<FieldViewProps
const durationSecStr = viewTrans.match(/([0-9.]*)s/);
const duration = durationMiliStr ? Number(durationMiliStr[1]) : durationSecStr ? Number(durationSecStr[1]) * 1000 : 0;
if (duration) {
- smoothScroll(duration, this._scrollRef.current, Math.abs(pos || 0));
+ this._scrollStopper = smoothScroll(duration, this._scrollRef.current, Math.abs(pos || 0), 'ease', this._scrollStopper);
} else {
this._scrollRef.current.scrollTo({ top: pos });
}
@@ -1284,6 +1284,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
});
}
_didScroll = false;
+ _scrollStopper: undefined | (() => 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<FieldViewProps
const shift = Math.min(topOff ?? Number.MAX_VALUE, botOff ?? Number.MAX_VALUE);
const scrollPos = scrollRef.scrollTop + shift * self.props.ScreenToLocalTransform().Scale;
if (this._focusSpeed !== undefined) {
- scrollPos && smoothScroll(this._focusSpeed, scrollRef, scrollPos);
+ scrollPos && (this._scrollStopper = smoothScroll(this._focusSpeed, scrollRef, scrollPos, 'ease', this._scrollStopper));
} else {
scrollRef.scrollTo({ top: scrollPos });
}
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index e0f64e7aa..39328ae4a 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -40,6 +40,7 @@ const { Howl } = require('howler');
export interface PinProps {
audioRange?: boolean;
activeFrame?: number;
+ currentFrame?: number;
hidePresBox?: boolean;
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)
@@ -276,14 +277,15 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
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<FieldViewProps>() {
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<FieldViewProps>() {
});
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<FieldViewProps>() {
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<FieldViewProps>() {
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<FieldViewProps>() {
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<FieldViewProps>() {
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 (
<div
className={`presBox-ribbon ${this._transitionTools && this.layoutDoc.presStatus === PresStatus.Edit ? 'active' : ''}`}
@@ -1247,6 +1256,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
<FontAwesomeIcon className="presBox-dropdownIcon" style={{ gridColumn: 2, color: this._openMovementDropdown ? Colors.MEDIUM_BLUE : 'black' }} icon={'angle-down'} />
<div className={'presBox-dropdownOptions'} id={'presBoxMovementDropdown'} onPointerDown={StopEvent} style={{ display: this._openMovementDropdown ? 'grid' : 'none' }}>
{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<FieldViewProps>() {
</div>
</div>
</div>
- {PresBox.inputter('0.1', '0.1', '10', transitionSpeed, true, this.setTransitionTime)}
+ {PresBox.inputter('0.1', '0.1', '100', transitionSpeed, true, this.setTransitionTime)}
<div className={'slider-headers'}>
<div className="slider-text">Fast</div>
<div className="slider-text">Medium</div>
@@ -1317,6 +1327,11 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
Lightbox
</div>
</Tooltip>
+ <Tooltip title={<div className="dash-tooltip">{'Transition movement style'}</div>}>
+ <div className="ribbon-toggle" onClick={() => this.updateEaseFunc(activeItem)}>
+ {`${StrCast(activeItem.presEaseFunc, 'ease')}`}
+ </div>
+ </Tooltip>
</div>
{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<AntimodeMenuProps> {
</button>
</Tooltip>
),
- //NOTE: link popup is currently in progress
<Tooltip key="link" title={<div className="dash-tooltip">{'Find document to link to selected text'}</div>}>
<button className="antimodeMenu-button link" onPointerDown={this.toggleLinkPopup} style={{}}>
<FontAwesomeIcon style={{ position: 'absolute', transform: 'scale(1.5)' }} icon={'search'} size="lg" />
<FontAwesomeIcon style={{ position: 'absolute', transform: 'scale(0.5)', transformOrigin: 'top left', top: 12, left: 12 }} icon={'link'} size="lg" />
</button>
</Tooltip>,
- <LinkPopup key="popup" showPopup={this._showLinkPopup} linkFrom={this.onMakeAnchor} />,
+ <LinkPopup key="popup" showPopup={this._showLinkPopup} linkCreateAnchor={this.onMakeAnchor} />,
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<IViewerProps> {
private _lastSearch = false;
private _viewerIsSetup = false;
private _ignoreScroll = false;
- private _initialScroll: Opt<number>;
+ private _initialScroll: { loc: Opt<number>; easeFunc: 'linear' | 'ease' | undefined } | undefined;
private _forcedScroll = true;
selectionText = () => this._selectionText;
@@ -164,6 +164,8 @@ export class PDFViewer extends React.Component<IViewerProps> {
}
};
+ _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<IViewerProps> {
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<IViewerProps> {
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<IViewerProps> {
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<IViewerProps> {
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<IViewerProps> {
);
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<IViewerProps> {
@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<IViewerProps> {
};
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<SearchBoxProps>() {
// 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<SearchBoxProps>() {
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<SearchBoxProps>() {
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<SearchBoxProps>() {
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}>
<div className="searchBox-result-title">{title as string}</div>
<div className="searchBox-result-type">{formattedType}</div>
@@ -482,7 +496,10 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
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"
diff --git a/src/fields/util.ts b/src/fields/util.ts
index f222e4555..7f4892bd6 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -449,7 +449,7 @@ export function updateFunction(target: any, prop: any, value: any, receiver: any
diff?.op === '$addToSet'
? {
redo: () => {
- receiver[prop].push(...diff.items.map((item: any) => (item.value ? item.value() : item)));
+ receiver[prop].push(...diff.items.map((item: any) => item.value ?? item));
lastValue = ObjectField.MakeCopy(receiver[prop]);
},
undo: action(() => {
@@ -459,7 +459,7 @@ export function updateFunction(target: any, prop: any, value: any, receiver: any
const ind = receiver[prop].findIndex((ele: any) => ele instanceof SchemaHeaderField && ele.heading === item.heading);
ind !== -1 && receiver[prop].splice(ind, 1);
} else {
- const ind = receiver[prop].indexOf(item.value ? item.value() : item);
+ const ind = receiver[prop].indexOf(item.value ?? item);
ind !== -1 && receiver[prop].splice(ind, 1);
}
});
@@ -471,7 +471,7 @@ export function updateFunction(target: any, prop: any, value: any, receiver: any
? {
redo: action(() => {
diff.items.forEach((item: any) => {
- const ind = item instanceof SchemaHeaderField ? receiver[prop].findIndex((ele: any) => ele instanceof SchemaHeaderField && ele.heading === item.heading) : receiver[prop].indexOf(item.value ? item.value() : item);
+ const ind = item instanceof SchemaHeaderField ? receiver[prop].findIndex((ele: any) => ele instanceof SchemaHeaderField && ele.heading === item.heading) : receiver[prop].indexOf(item.value ?? item);
ind !== -1 && receiver[prop].splice(ind, 1);
});
lastValue = ObjectField.MakeCopy(receiver[prop]);
@@ -483,8 +483,8 @@ export function updateFunction(target: any, prop: any, value: any, receiver: any
const ind = (prevValue as List<any>).findIndex((ele: any) => ele instanceof SchemaHeaderField && ele.heading === item.heading);
ind !== -1 && receiver[prop].findIndex((ele: any) => ele instanceof SchemaHeaderField && ele.heading === item.heading) === -1 && receiver[prop].splice(ind, 0, item);
} else {
- const ind = (prevValue as List<any>).indexOf(item.value ? item.value() : item);
- ind !== -1 && receiver[prop].indexOf(item.value ? item.value() : item) === -1 && receiver[prop].splice(ind, 0, item);
+ const ind = (prevValue as List<any>).indexOf(item.value ?? item);
+ ind !== -1 && receiver[prop].indexOf(item.value ?? item) === -1 && receiver[prop].splice(ind, 0, item);
}
});
lastValue = ObjectField.MakeCopy(receiver[prop]);