aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbobzel <zzzman@gmail.com>2022-12-01 10:13:03 -0500
committerbobzel <zzzman@gmail.com>2022-12-01 10:13:03 -0500
commit66184a172006de4d4bf72d9da33858e04d298181 (patch)
tree9c9fc08d92102410383f7780b4e249616301f8a8
parent9d88adb19c2caf715b56c5ed40a500b9ef1491aa (diff)
refactored process of following links / jumping to docs and added following options for zoomTime, etc instead of setting temporary fields on docs.
-rw-r--r--src/client/documents/Documents.ts8
-rw-r--r--src/client/util/DocumentManager.ts98
-rw-r--r--src/client/util/Import & Export/DirectoryImportBox.tsx334
-rw-r--r--src/client/util/LinkFollower.ts84
-rw-r--r--src/client/util/SharingManager.tsx2
-rw-r--r--src/client/views/DocComponent.tsx2
-rw-r--r--src/client/views/InkingStroke.tsx6
-rw-r--r--src/client/views/MainView.tsx2
-rw-r--r--src/client/views/MarqueeAnnotator.tsx2
-rw-r--r--src/client/views/PropertiesView.tsx28
-rw-r--r--src/client/views/collections/CollectionNoteTakingView.tsx5
-rw-r--r--src/client/views/collections/CollectionStackingView.tsx5
-rw-r--r--src/client/views/collections/TabDocView.tsx4
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx8
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx2
-rw-r--r--src/client/views/collections/collectionLinear/CollectionLinearView.tsx2
-rw-r--r--src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx2
-rw-r--r--src/client/views/nodes/DocumentView.tsx26
-rw-r--r--src/client/views/nodes/ImageBox.tsx8
-rw-r--r--src/client/views/nodes/PDFBox.tsx13
-rw-r--r--src/client/views/nodes/VideoBox.tsx6
-rw-r--r--src/client/views/nodes/WebBox.tsx10
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx12
-rw-r--r--src/client/views/nodes/trails/PresBox.tsx24
-rw-r--r--src/client/views/pdf/Annotation.tsx4
-rw-r--r--src/client/views/pdf/PDFViewer.tsx8
-rw-r--r--src/client/views/search/SearchBox.tsx2
27 files changed, 354 insertions, 353 deletions
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index eed839520..d13d96dd3 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -157,7 +157,7 @@ export class DocumentOptions {
_contentBounds?: List<number>; // the (forced) bounds of the document to display. format is: [left, top, right, bottom]
_lockedPosition?: boolean; // lock the x,y coordinates of the document so that it can't be dragged
_lockedTransform?: boolean; // lock the panx,pany and scale parameters of the document so that it be panned/zoomed
- _isPushpin?: boolean; // whether document, when clicked, toggles display of its link target
+ _followLinkToggle?: boolean; // whether document, when clicked, toggles display of its link target
_showTitle?: string; // field name to display in header (:hover is an optional suffix)
_showCaption?: string; // which field to display in the caption area. leave empty to have no caption
_scrollTop?: number; // scroll location for pdfs
@@ -272,7 +272,7 @@ export class DocumentOptions {
clipWidth?: number; // percent transition from before to after in comparisonBox
dockingConfig?: string;
annotationOn?: Doc;
- isPushpin?: boolean;
+ followLinkToggle?: boolean;
isGroup?: boolean; // whether a collection should use a grouping UI behavior
_removeDropProperties?: List<string>; // list of properties that should be removed from a document when it is dropped. e.g., a creator button may be forceActive to allow it be dragged, but the forceActive property can be removed from the dropped document
noteType?: string;
@@ -1703,7 +1703,7 @@ export namespace DocUtils {
}
export function LeavePushpin(doc: Doc, annotationField: string) {
- if (doc.isPushpin) return undefined;
+ if (doc.followLinkToggle) return undefined;
const context = Cast(doc.context, Doc, null) ?? Cast(doc.annotationOn, Doc, null);
const hasContextAnchor = DocListCast(doc.links).some(l => (l.anchor2 === doc && Cast(l.anchor1, Doc, null)?.annotationOn === context) || (l.anchor1 === doc && Cast(l.anchor2, Doc, null)?.annotationOn === context));
if (context && !hasContextAnchor && (context.type === DocumentType.VID || context.type === DocumentType.WEB || context.type === DocumentType.PDF || context.type === DocumentType.IMG)) {
@@ -1711,7 +1711,7 @@ export namespace DocUtils {
title: 'pushpin',
label: '',
annotationOn: Cast(doc.annotationOn, Doc, null),
- isPushpin: true,
+ followLinkToggle: true,
icon: 'map-pin',
x: Cast(doc.x, 'number', null),
y: Cast(doc.y, 'number', null),
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 4f02a8202..1b63b615b 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -5,7 +5,7 @@ import { Cast, DocCast } from '../../fields/Types';
import { returnFalse } from '../../Utils';
import { DocumentType } from '../documents/DocumentTypes';
import { LightboxView } from '../views/LightboxView';
-import { DocumentView, OpenWhereMod, ViewAdjustment } from '../views/nodes/DocumentView';
+import { DocFocusOptions, DocumentView, OpenWhereMod, ViewAdjustment } from '../views/nodes/DocumentView';
import { LinkAnchorBox } from '../views/nodes/LinkAnchorBox';
import { CollectionDockingView } from '../views/collections/CollectionDockingView';
import { CollectionFreeFormView } from '../views/collections/collectionFreeForm';
@@ -171,19 +171,13 @@ export class DocumentManager {
};
public jumpToDocument = (
targetDoc: Doc, // document to display
- willZoom: boolean, // whether to zoom doc to take up most of screen
+ options: DocFocusOptions, // options for how to navigate to target
createViewFunc = DocumentManager.addView, // how to create a view of the doc if it doesn't exist
docContext: Doc[], // context to load that should contain the target
- linkDoc?: Doc, // link that's being followed
- closeContextIfNotFound: boolean = false, // after opening a context where the document should be, this determines whether the context should be closed if the Doc isn't actually there
- originatingDoc: Opt<Doc> = undefined, // doc that initiated the display of the target odoc
- finished?: () => void,
- originalTarget?: Doc,
- noSelect?: boolean,
- presZoomScale?: number
+ finished?: () => void
): void => {
- originalTarget = originalTarget ?? targetDoc;
- const docView = this.getFirstDocumentView(targetDoc, originatingDoc);
+ const originalTarget = options.originalTarget ?? targetDoc;
+ const docView = this.getFirstDocumentView(targetDoc, options.originatingDoc);
const annotatedDoc = Cast(targetDoc.annotationOn, Doc, null);
const resolvedTarget = targetDoc.type === DocumentType.MARKER ? annotatedDoc ?? docView?.rootDoc ?? targetDoc : docView?.rootDoc ?? targetDoc; // if target is a marker, then focus toggling should apply to the document it's on since the marker itself doesn't have a hidden field
var wasHidden = resolvedTarget.hidden;
@@ -195,14 +189,14 @@ export class DocumentManager {
}
const focusAndFinish = (didFocus: boolean) => {
const finalTargetDoc = resolvedTarget;
- if (originatingDoc?.isPushpin) {
+ if (options.toggleTarget) {
if (!didFocus && !wasHidden) {
// don't toggle the hidden state if the doc was already un-hidden as part of this document traversal
finalTargetDoc.hidden = !finalTargetDoc.hidden;
}
} else {
finalTargetDoc.hidden && (finalTargetDoc.hidden = undefined);
- !noSelect && docView?.select(false);
+ !options.noSelect && docView?.select(false);
}
finished?.();
};
@@ -216,9 +210,8 @@ export class DocumentManager {
if (annoContainerView.props.Document.layoutKey === 'layout_icon') {
annoContainerView.iconify(() =>
annoContainerView.focus(targetDoc, {
+ ...options,
originalTarget,
- willZoom,
- scale: presZoomScale,
afterFocus: (didFocus: boolean) =>
new Promise<ViewAdjustment>(res => {
focusAndFinish(true);
@@ -232,13 +225,12 @@ export class DocumentManager {
}
}
if (focusView) {
- !noSelect && Doc.linkFollowHighlight(focusView.rootDoc, undefined, targetDoc); //TODO:glr make this a setting in PresBox
- if (originatingDoc?.followLinkAudio) DocumentManager.playAudioAnno(focusView.rootDoc);
+ !options.noSelect && Doc.linkFollowHighlight(focusView.rootDoc, undefined, targetDoc); //TODO:glr make this a setting in PresBox
+ if (options.playAudio) DocumentManager.playAudioAnno(focusView.rootDoc);
const doFocus = (forceDidFocus: boolean) =>
- focusView.focus(originalTarget ?? targetDoc, {
+ focusView.focus(originalTarget, {
+ ...options,
originalTarget,
- willZoom,
- scale: presZoomScale,
afterFocus: (didFocus: boolean) =>
new Promise<ViewAdjustment>(res => {
focusAndFinish(forceDidFocus || didFocus);
@@ -262,13 +254,12 @@ export class DocumentManager {
targetDocContextView.rootDoc.hidden = false; // make sure context isn't hidden
targetDocContext._viewTransition = 'transform 500ms';
targetDocContextView.props.focus(targetDocContextView.rootDoc, {
- willZoom,
+ ...options,
+ // originalTarget, // needed?
afterFocus: async () => {
targetDocContext._viewTransition = undefined;
if (targetDocContext.layoutKey === 'layout_icon') {
- targetDocContextView.iconify(() =>
- this.jumpToDocument(resolvedTarget ?? targetDoc, willZoom, createViewFunc, docContext, linkDoc, closeContextIfNotFound, originatingDoc, finished, originalTarget, noSelect, presZoomScale)
- );
+ targetDocContextView.iconify(() => this.jumpToDocument(resolvedTarget ?? targetDoc, { ...options /* originalTarget - needed?*/ }, createViewFunc, docContext, finished));
}
return ViewAdjustment.doNothing;
},
@@ -281,56 +272,35 @@ export class DocumentManager {
finished?.();
} else {
// no timecode means we need to find the context view and focus on our target
- const findView = (delay: number) => {
- const retryDocView = this.getFirstDocumentView(resolvedTarget); // test again for the target view snce we presumably created the context above by focusing on it
- if (retryDocView) {
- // we found the target in the context.
- Doc.linkFollowHighlight(retryDocView.rootDoc);
- retryDocView.focus(targetDoc, {
- willZoom,
- afterFocus: (didFocus: boolean) =>
- new Promise<ViewAdjustment>(res => {
- !noSelect && focusAndFinish(true);
- res(ViewAdjustment.doNothing);
- }),
- }); // focus on the target in the context
- } else if (delay > 1000) {
- // we didn't find the target, so it must have moved out of the context. Go back to just creating it.
- if (closeContextIfNotFound) targetDocContextView.props.removeDocument?.(targetDocContextView.rootDoc);
- if (targetDoc.layout) {
- // there will no layout for a TEXTANCHOR type document
- createViewFunc(Doc.BrushDoc(targetDoc), finished); // create a new view of the target
- }
- } else {
- setTimeout(() => findView(delay + 200), 200);
- }
- };
- setTimeout(() => findView(0), 0);
+ const retryDocView = this.getFirstDocumentView(resolvedTarget); // test again for the target view snce we presumably created the context above by focusing on it
+ if (retryDocView) {
+ // we found the target in the context.
+ Doc.linkFollowHighlight(retryDocView.rootDoc);
+ retryDocView.focus(targetDoc, {
+ ...options,
+ // originalTarget -- needed?
+ afterFocus: (didFocus: boolean) =>
+ new Promise<ViewAdjustment>(res => {
+ !options.noSelect && focusAndFinish(true);
+ res(ViewAdjustment.doNothing);
+ }),
+ }); // focus on the target in the context
+ } else if (targetDoc.layout) {
+ // there will no layout for a TEXTANCHOR type document
+ createViewFunc(Doc.BrushDoc(targetDoc), finished); // create a new view of the target
+ }
}
} else {
if (docContext.length && docContext[0]?.layoutKey === 'layout_icon') {
const docContextView = this.getFirstDocumentView(docContext[0]);
if (docContextView) {
- return docContextView.iconify(() =>
- this.jumpToDocument(targetDoc, willZoom, createViewFunc, docContext.slice(1, docContext.length), linkDoc, closeContextIfNotFound, originatingDoc, finished, originalTarget, noSelect, presZoomScale)
- );
+ return docContextView.iconify(() => this.jumpToDocument(targetDoc, { ...options, originalTarget }, createViewFunc, docContext.slice(1, docContext.length), finished));
}
}
// there's no context view so we need to create one first and try again when that finishes
createViewFunc(
targetDocContext, // after creating the context, this calls the finish function that will retry looking for the target
- () =>
- this.jumpToDocument(
- targetDoc,
- willZoom,
- (doc: Doc, finished?: () => void) => doc !== targetDocContext && createViewFunc(doc, finished),
- docContext,
- linkDoc,
- true /* if target not found, get rid of context just created */,
- originatingDoc,
- finished,
- originalTarget
- )
+ () => this.jumpToDocument(targetDoc, { ...options, originalTarget }, (doc: Doc, finished?: () => void) => doc !== targetDocContext && createViewFunc(doc, finished), docContext, finished)
);
}
}
diff --git a/src/client/util/Import & Export/DirectoryImportBox.tsx b/src/client/util/Import & Export/DirectoryImportBox.tsx
index 37571ae01..916eee4b7 100644
--- a/src/client/util/Import & Export/DirectoryImportBox.tsx
+++ b/src/client/util/Import & Export/DirectoryImportBox.tsx
@@ -1,27 +1,27 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { BatchedArray } from "array-batcher";
-import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx";
-import { observer } from "mobx-react";
-import { extname } from "path";
-import Measure, { ContentRect } from "react-measure";
-import { Doc, DocListCast, DocListCastAsync, Opt } from "../../../fields/Doc";
-import { Id } from "../../../fields/FieldSymbols";
-import { List } from "../../../fields/List";
-import { listSpec } from "../../../fields/Schema";
-import { SchemaHeaderField } from "../../../fields/SchemaHeaderField";
-import { BoolCast, Cast, NumCast } from "../../../fields/Types";
-import { AcceptableMedia, Upload } from "../../../server/SharedMediaTypes";
-import { Utils } from "../../../Utils";
-import { GooglePhotos } from "../../apis/google_docs/GooglePhotosClientUtils";
-import { Docs, DocumentOptions, DocUtils } from "../../documents/Documents";
-import { Networking } from "../../Network";
-import { FieldView, FieldViewProps } from "../../views/nodes/FieldView";
-import { DocumentManager } from "../DocumentManager";
-import "./DirectoryImportBox.scss";
-import ImportMetadataEntry, { keyPlaceholder, valuePlaceholder } from "./ImportMetadataEntry";
-import React = require("react");
+import { BatchedArray } from 'array-batcher';
+import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
+import { observer } from 'mobx-react';
+import { extname } from 'path';
+import Measure, { ContentRect } from 'react-measure';
+import { Doc, DocListCast, DocListCastAsync, Opt } from '../../../fields/Doc';
+import { Id } from '../../../fields/FieldSymbols';
+import { List } from '../../../fields/List';
+import { listSpec } from '../../../fields/Schema';
+import { SchemaHeaderField } from '../../../fields/SchemaHeaderField';
+import { BoolCast, Cast, NumCast } from '../../../fields/Types';
+import { AcceptableMedia, Upload } from '../../../server/SharedMediaTypes';
+import { Utils } from '../../../Utils';
+import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils';
+import { Docs, DocumentOptions, DocUtils } from '../../documents/Documents';
+import { Networking } from '../../Network';
+import { FieldView, FieldViewProps } from '../../views/nodes/FieldView';
+import { DocumentManager } from '../DocumentManager';
+import './DirectoryImportBox.scss';
+import ImportMetadataEntry, { keyPlaceholder, valuePlaceholder } from './ImportMetadataEntry';
+import React = require('react');
-const unsupported = ["text/html", "text/plain"];
+const unsupported = ['text/html', 'text/plain'];
@observer
export class DirectoryImportBox extends React.Component<FieldViewProps> {
@@ -29,7 +29,7 @@ export class DirectoryImportBox extends React.Component<FieldViewProps> {
@observable private top = 0;
@observable private left = 0;
private dimensions = 50;
- @observable private phase = "";
+ @observable private phase = '';
private disposer: Opt<IReactionDisposer>;
@observable private entries: ImportMetadataEntry[] = [];
@@ -40,7 +40,9 @@ export class DirectoryImportBox extends React.Component<FieldViewProps> {
@observable private uploading = false;
@observable private removeHover = false;
- public static LayoutString(fieldKey: string) { return FieldView.LayoutString(DirectoryImportBox, fieldKey); }
+ public static LayoutString(fieldKey: string) {
+ return FieldView.LayoutString(DirectoryImportBox, fieldKey);
+ }
constructor(props: FieldViewProps) {
super(props);
@@ -71,7 +73,7 @@ export class DirectoryImportBox extends React.Component<FieldViewProps> {
handleSelection = async (e: React.ChangeEvent<HTMLInputElement>) => {
runInAction(() => {
this.uploading = true;
- this.phase = "Initializing download...";
+ this.phase = 'Initializing download...';
});
const docs: Doc[] = [];
@@ -79,7 +81,7 @@ export class DirectoryImportBox extends React.Component<FieldViewProps> {
const files = e.target.files;
if (!files || files.length === 0) return;
- const directory = (files.item(0) as any).webkitRelativePath.split("/", 1)[0];
+ const directory = (files.item(0) as any).webkitRelativePath.split('/', 1)[0];
const validated: File[] = [];
for (let i = 0; i < files.length; i++) {
@@ -100,7 +102,7 @@ export class DirectoryImportBox extends React.Component<FieldViewProps> {
const sizes: number[] = [];
const modifiedDates: number[] = [];
- runInAction(() => this.phase = `Internal: uploading ${this.quota - this.completed} files to Dash...`);
+ runInAction(() => (this.phase = `Internal: uploading ${this.quota - this.completed} files to Dash...`));
const batched = BatchedArray.from(validated, { batchSize: 15 });
const uploads = await batched.batchedMapAsync<Upload.FileResponse<Upload.ImageInformation>>(async (batch, collector) => {
@@ -109,23 +111,28 @@ export class DirectoryImportBox extends React.Component<FieldViewProps> {
modifiedDates.push(file.lastModified);
});
collector.push(...(await Networking.UploadFilesToServer<Upload.ImageInformation>(batch)));
- runInAction(() => this.completed += batch.length);
+ runInAction(() => (this.completed += batch.length));
});
- await Promise.all(uploads.map(async response => {
- const { source: { type }, result } = response;
- if (result instanceof Error) {
- return;
- }
- const { accessPaths, exifData } = result;
- const path = Utils.prepend(accessPaths.agnostic.client);
- const document = type && await DocUtils.DocumentFromType(type, path, { _width: 300 });
- const { data, error } = exifData;
- if (document) {
- Doc.GetProto(document).exif = error || Doc.Get.FromJson({ data });
- docs.push(document);
- }
- }));
+ await Promise.all(
+ uploads.map(async response => {
+ const {
+ source: { type },
+ result,
+ } = response;
+ if (result instanceof Error) {
+ return;
+ }
+ const { accessPaths, exifData } = result;
+ const path = Utils.prepend(accessPaths.agnostic.client);
+ const document = type && (await DocUtils.DocumentFromType(type, path, { _width: 300 }));
+ const { data, error } = exifData;
+ if (document) {
+ Doc.GetProto(document).exif = error || Doc.Get.FromJson({ data });
+ docs.push(document);
+ }
+ })
+ );
for (let i = 0; i < docs.length; i++) {
const doc = docs[i];
@@ -146,7 +153,7 @@ export class DirectoryImportBox extends React.Component<FieldViewProps> {
_height: 500,
_chromeHidden: true,
x: NumCast(doc.x),
- y: NumCast(doc.y) + offset
+ y: NumCast(doc.y) + offset,
};
const parent = this.props.ContainingCollectionView;
if (parent) {
@@ -154,14 +161,14 @@ export class DirectoryImportBox extends React.Component<FieldViewProps> {
if (docs.length < 50) {
importContainer = Docs.Create.MasonryDocument(docs, options);
} else {
- const headers = [new SchemaHeaderField("title"), new SchemaHeaderField("size")];
+ const headers = [new SchemaHeaderField('title'), new SchemaHeaderField('size')];
importContainer = Docs.Create.SchemaDocument(headers, docs, options);
}
- runInAction(() => this.phase = 'External: uploading files to Google Photos...');
+ runInAction(() => (this.phase = 'External: uploading files to Google Photos...'));
await GooglePhotos.Export.CollectionToAlbum({ collection: importContainer });
- Doc.AddDocToList(Doc.GetProto(parent.props.Document), "data", importContainer);
+ Doc.AddDocToList(Doc.GetProto(parent.props.Document), 'data', importContainer);
!this.persistent && this.props.removeDocument && this.props.removeDocument(doc);
- DocumentManager.Instance.jumpToDocument(importContainer, true, undefined, []);
+ DocumentManager.Instance.jumpToDocument(importContainer, { willZoom: true }, undefined, []);
}
runInAction(() => {
@@ -169,14 +176,14 @@ export class DirectoryImportBox extends React.Component<FieldViewProps> {
this.quota = 1;
this.completed = 0;
});
- }
+ };
componentDidMount() {
- this.selector.current!.setAttribute("directory", "");
- this.selector.current!.setAttribute("webkitdirectory", "");
+ this.selector.current!.setAttribute('directory', '');
+ this.selector.current!.setAttribute('webkitdirectory', '');
this.disposer = reaction(
() => this.completed,
- completed => runInAction(() => this.phase = `Internal: uploading ${this.quota - completed} files to Dash...`)
+ completed => runInAction(() => (this.phase = `Internal: uploading ${this.quota - completed} files to Dash...`))
);
}
@@ -193,7 +200,7 @@ export class DirectoryImportBox extends React.Component<FieldViewProps> {
const offset = this.dimensions / 2;
this.left = bounds.width / 2 - offset;
this.top = bounds.height / 2 - offset;
- }
+ };
@action
addMetadataEntry = async () => {
@@ -201,8 +208,8 @@ export class DirectoryImportBox extends React.Component<FieldViewProps> {
entryDoc.checked = false;
entryDoc.key = keyPlaceholder;
entryDoc.value = valuePlaceholder;
- Doc.AddDocToList(this.props.Document, "data", entryDoc);
- }
+ Doc.AddDocToList(this.props.Document, 'data', entryDoc);
+ };
@action
remove = async (entry: ImportMetadataEntry) => {
@@ -217,7 +224,7 @@ export class DirectoryImportBox extends React.Component<FieldViewProps> {
}
}
}
- }
+ };
render() {
const dimensions = 50;
@@ -228,193 +235,204 @@ export class DirectoryImportBox extends React.Component<FieldViewProps> {
const uploading = this.uploading;
const showRemoveLabel = this.removeHover;
const persistent = this.persistent;
- let percent = `${completed / quota * 100}`;
- percent = percent.split(".")[0];
- percent = percent.startsWith("100") ? "99" : percent;
+ let percent = `${(completed / quota) * 100}`;
+ percent = percent.split('.')[0];
+ percent = percent.startsWith('100') ? '99' : percent;
const marginOffset = (percent.length === 1 ? 5 : 0) - 1.6;
- const message = <span className={"phase"}>{this.phase}</span>;
- const centerPiece = this.phase.includes("Google Photos") ?
- <img src={"/assets/google_photos.png"} style={{
- transition: "0.4s opacity ease",
- width: 30,
- height: 30,
- opacity: uploading ? 1 : 0,
- pointerEvents: "none",
- position: "absolute",
- left: 12,
- top: this.top + 10,
- fontSize: 18,
- color: "white",
- marginLeft: this.left + marginOffset
- }} />
- : <div
+ const message = <span className={'phase'}>{this.phase}</span>;
+ const centerPiece = this.phase.includes('Google Photos') ? (
+ <img
+ src={'/assets/google_photos.png'}
style={{
- transition: "0.4s opacity ease",
+ transition: '0.4s opacity ease',
+ width: 30,
+ height: 30,
opacity: uploading ? 1 : 0,
- pointerEvents: "none",
- position: "absolute",
+ pointerEvents: 'none',
+ position: 'absolute',
+ left: 12,
+ top: this.top + 10,
+ fontSize: 18,
+ color: 'white',
+ marginLeft: this.left + marginOffset,
+ }}
+ />
+ ) : (
+ <div
+ style={{
+ transition: '0.4s opacity ease',
+ opacity: uploading ? 1 : 0,
+ pointerEvents: 'none',
+ position: 'absolute',
left: 10,
top: this.top + 12.3,
fontSize: 18,
- color: "white",
- marginLeft: this.left + marginOffset
- }}>{percent}%</div>;
+ color: 'white',
+ marginLeft: this.left + marginOffset,
+ }}>
+ {percent}%
+ </div>
+ );
return (
<Measure offset onResize={this.preserveCentering}>
- {({ measureRef }) =>
- <div ref={measureRef} style={{ width: "100%", height: "100%", pointerEvents: "all" }} >
+ {({ measureRef }) => (
+ <div ref={measureRef} style={{ width: '100%', height: '100%', pointerEvents: 'all' }}>
{message}
<input
- id={"selector"}
+ id={'selector'}
ref={this.selector}
onChange={this.handleSelection}
type="file"
style={{
- position: "absolute",
- display: "none"
- }} />
+ position: 'absolute',
+ display: 'none',
+ }}
+ />
<label
- htmlFor={"selector"}
+ htmlFor={'selector'}
style={{
opacity: isEditing ? 0 : 1,
- pointerEvents: isEditing ? "none" : "all",
- transition: "0.4s ease opacity"
- }}
- >
- <div style={{
- width: dimensions,
- height: dimensions,
- borderRadius: "50%",
- background: "black",
- position: "absolute",
- left: this.left,
- top: this.top
- }} />
- <div style={{
- position: "absolute",
- left: this.left + 8,
- top: this.top + 10,
- opacity: uploading ? 0 : 1,
- transition: "0.4s opacity ease"
+ pointerEvents: isEditing ? 'none' : 'all',
+ transition: '0.4s ease opacity',
}}>
- <FontAwesomeIcon icon={"cloud-upload-alt"} color="#FFFFFF" size={"2x"} />
+ <div
+ style={{
+ width: dimensions,
+ height: dimensions,
+ borderRadius: '50%',
+ background: 'black',
+ position: 'absolute',
+ left: this.left,
+ top: this.top,
+ }}
+ />
+ <div
+ style={{
+ position: 'absolute',
+ left: this.left + 8,
+ top: this.top + 10,
+ opacity: uploading ? 0 : 1,
+ transition: '0.4s opacity ease',
+ }}>
+ <FontAwesomeIcon icon={'cloud-upload-alt'} color="#FFFFFF" size={'2x'} />
</div>
<img
style={{
width: 80,
height: 80,
- transition: "0.4s opacity ease",
+ transition: '0.4s opacity ease',
opacity: uploading ? 0.7 : 0,
- position: "absolute",
+ position: 'absolute',
top: this.top - 15,
- left: this.left - 15
+ left: this.left - 15,
}}
- src={"/assets/loading.gif"}></img>
+ src={'/assets/loading.gif'}></img>
</label>
<input
- type={"checkbox"}
- onChange={e => runInAction(() => this.persistent = e.target.checked)}
+ type={'checkbox'}
+ onChange={e => runInAction(() => (this.persistent = e.target.checked))}
style={{
margin: 0,
- position: "absolute",
+ position: 'absolute',
left: 10,
bottom: 10,
opacity: isEditing || uploading ? 0 : 1,
- transition: "0.4s opacity ease",
- pointerEvents: isEditing || uploading ? "none" : "all"
+ transition: '0.4s opacity ease',
+ pointerEvents: isEditing || uploading ? 'none' : 'all',
}}
checked={this.persistent}
- onPointerEnter={action(() => this.removeHover = true)}
- onPointerLeave={action(() => this.removeHover = false)}
+ onPointerEnter={action(() => (this.removeHover = true))}
+ onPointerLeave={action(() => (this.removeHover = false))}
/>
<p
style={{
- position: "absolute",
+ position: 'absolute',
left: 27,
bottom: 8.4,
fontSize: 12,
opacity: showRemoveLabel ? 1 : 0,
- transition: "0.4s opacity ease"
- }}>Template will be <span style={{ textDecoration: "underline", textDecorationColor: persistent ? "green" : "red", color: persistent ? "green" : "red" }}>{persistent ? "kept" : "removed"}</span> after upload</p>
+ transition: '0.4s opacity ease',
+ }}>
+ Template will be <span style={{ textDecoration: 'underline', textDecorationColor: persistent ? 'green' : 'red', color: persistent ? 'green' : 'red' }}>{persistent ? 'kept' : 'removed'}</span> after upload
+ </p>
{centerPiece}
<div
style={{
- position: "absolute",
+ position: 'absolute',
top: 10,
right: 10,
- borderRadius: "50%",
+ borderRadius: '50%',
width: 25,
height: 25,
- background: "black",
- pointerEvents: uploading ? "none" : "all",
+ background: 'black',
+ pointerEvents: uploading ? 'none' : 'all',
opacity: uploading ? 0 : 1,
- transition: "0.4s opacity ease"
+ transition: '0.4s opacity ease',
}}
- title={isEditing ? "Back to Upload" : "Add Metadata"}
- onClick={action(() => this.editingMetadata = !this.editingMetadata)}
+ title={isEditing ? 'Back to Upload' : 'Add Metadata'}
+ onClick={action(() => (this.editingMetadata = !this.editingMetadata))}
/>
<FontAwesomeIcon
style={{
- pointerEvents: "none",
- position: "absolute",
+ pointerEvents: 'none',
+ position: 'absolute',
right: isEditing ? 14 : 15,
top: isEditing ? 15.4 : 16,
opacity: uploading ? 0 : 1,
- transition: "0.4s opacity ease"
+ transition: '0.4s opacity ease',
}}
- icon={isEditing ? "cloud-upload-alt" : "tag"}
+ icon={isEditing ? 'cloud-upload-alt' : 'tag'}
color="#FFFFFF"
- size={"1x"}
+ size={'1x'}
/>
<div
style={{
- transition: "0.4s ease opacity",
- width: "100%",
- height: "100%",
- pointerEvents: isEditing ? "all" : "none",
+ transition: '0.4s ease opacity',
+ width: '100%',
+ height: '100%',
+ pointerEvents: isEditing ? 'all' : 'none',
opacity: isEditing ? 1 : 0,
- overflowY: "scroll"
- }}
- >
+ overflowY: 'scroll',
+ }}>
<div
style={{
- borderRadius: "50%",
+ borderRadius: '50%',
width: 25,
height: 25,
marginLeft: 10,
- position: "absolute",
+ position: 'absolute',
right: 41,
- top: 10
+ top: 10,
}}
- title={"Add Metadata Entry"}
- onClick={this.addMetadataEntry}
- >
+ title={'Add Metadata Entry'}
+ onClick={this.addMetadataEntry}>
<FontAwesomeIcon
style={{
- pointerEvents: "none",
+ pointerEvents: 'none',
marginLeft: 6.4,
- marginTop: 5.2
+ marginTop: 5.2,
}}
- icon={"plus"}
- size={"1x"}
+ icon={'plus'}
+ size={'1x'}
/>
</div>
- <p style={{ paddingLeft: 10, paddingTop: 8, paddingBottom: 7 }} >Add metadata to your import...</p>
- <hr style={{ margin: "6px 10px 12px 10px" }} />
- {entries.map(doc =>
+ <p style={{ paddingLeft: 10, paddingTop: 8, paddingBottom: 7 }}>Add metadata to your import...</p>
+ <hr style={{ margin: '6px 10px 12px 10px' }} />
+ {entries.map(doc => (
<ImportMetadataEntry
Document={doc}
key={doc[Id]}
remove={this.remove}
- ref={(el) => { if (el) this.entries.push(el); }}
+ ref={el => {
+ if (el) this.entries.push(el);
+ }}
next={this.addMetadataEntry}
/>
- )}
+ ))}
</div>
</div>
- }
+ )}
</Measure>
);
}
-
-} \ No newline at end of file
+}
diff --git a/src/client/util/LinkFollower.ts b/src/client/util/LinkFollower.ts
index 282116f1b..4f742817a 100644
--- a/src/client/util/LinkFollower.ts
+++ b/src/client/util/LinkFollower.ts
@@ -1,10 +1,11 @@
import { action, runInAction } from 'mobx';
import { Doc, DocListCast, Opt } from '../../fields/Doc';
-import { BoolCast, Cast, DocCast, StrCast } from '../../fields/Types';
+import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../fields/Types';
import { DocumentType } from '../documents/DocumentTypes';
import { DocumentDecorations } from '../views/DocumentDecorations';
import { LightboxView } from '../views/LightboxView';
-import { DocumentViewSharedProps, OpenWhere, ViewAdjustment } from '../views/nodes/DocumentView';
+import { DocFocusOptions, DocumentViewSharedProps, OpenWhere, ViewAdjustment } from '../views/nodes/DocumentView';
+import { PresEffect, PresEffectDirection } from '../views/nodes/trails';
import { DocumentManager } from './DocumentManager';
import { LinkManager } from './LinkManager';
import { UndoManager } from './UndoManager';
@@ -56,7 +57,7 @@ export class LinkFollower {
createTabForTarget(false);
} else {
// first focus & zoom onto this (the clicked document). Then execute the function to focus on the target
- docViewProps.focus(sourceDoc, { willZoom: BoolCast(sourceDoc.followLinkZoom, true), scale: 1, afterFocus: createTabForTarget });
+ docViewProps.focus(sourceDoc, { willZoom: BoolCast(sourceDoc.followLinkZoom, true), zoomScale: 1, afterFocus: createTabForTarget });
}
};
runInAction(() => (DocumentDecorations.Instance.overrideBounds = true)); // turn off decoration bounds while following links since animations may occur, and DocDecorations is based on screenToLocal which is not always an observable value
@@ -81,52 +82,45 @@ export class LinkFollower {
const backLinkWithoutTargetView = secondDocs.find(d => DocumentManager.Instance.getDocumentViews((d.anchor1 as Doc).type === DocumentType.MARKER ? DocCast((d.anchor1 as Doc).annotationOn) : (d.anchor1 as Doc)).length === 0);
const linkWithoutTargetDoc = traverseBacklink === undefined ? fwdLinkWithoutTargetView || backLinkWithoutTargetView : traverseBacklink ? backLinkWithoutTargetView : fwdLinkWithoutTargetView;
const linkDocList = linkWithoutTargetDoc && !sourceDoc.followAllLinks ? [linkWithoutTargetDoc] : traverseBacklink === undefined ? firstDocs.concat(secondDocs) : traverseBacklink ? secondDocs : firstDocs;
- const followLinks = sourceDoc.isPushpin || sourceDoc.followAllLinks ? linkDocList : linkDocList.slice(0, 1);
+ const followLinks = sourceDoc.followLinkToggle || sourceDoc.followAllLinks ? linkDocList : linkDocList.slice(0, 1);
var count = 0;
const allFinished = () => ++count === followLinks.length && finished?.();
followLinks.forEach(async linkDoc => {
- if (linkDoc) {
- const target = (
- sourceDoc === linkDoc.anchor1
- ? linkDoc.anchor2
- : sourceDoc === linkDoc.anchor2
- ? linkDoc.anchor1
- : Doc.AreProtosEqual(sourceDoc, linkDoc.anchor1 as Doc) || Doc.AreProtosEqual((linkDoc.anchor1 as Doc).annotationOn as Doc, sourceDoc)
- ? linkDoc.anchor2
- : linkDoc.anchor1
- ) as Doc;
- if (target) {
- const zoom = BoolCast(LinkManager.getOppositeAnchor(linkDoc, target)?.followLinkZoom, false);
- if (target.TourMap) {
- const fieldKey = Doc.LayoutFieldKey(target);
- const tour = DocListCast(target[fieldKey]).reverse();
- 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,
- zoom,
- (doc, finished) => createViewFunc(doc, StrCast(linkDoc.followLinkLocation, 'inPlace'), finished),
- targetContexts,
- linkDoc,
- undefined,
- sourceDoc,
- allFinished,
- undefined,
- undefined,
- Cast(sourceDoc.presZoom, 'number', null)
- );
- }
- } else {
+ const target = (
+ sourceDoc === linkDoc.anchor1
+ ? linkDoc.anchor2
+ : sourceDoc === linkDoc.anchor2
+ ? linkDoc.anchor1
+ : Doc.AreProtosEqual(sourceDoc, linkDoc.anchor1 as Doc) || Doc.AreProtosEqual((linkDoc.anchor1 as Doc).annotationOn as Doc, sourceDoc)
+ ? linkDoc.anchor2
+ : linkDoc.anchor1
+ ) as Doc;
+ if (target) {
+ const options: DocFocusOptions = {
+ playAudio: BoolCast(sourceDoc.followLinkAudio),
+ toggleTarget: BoolCast(sourceDoc.followLinkToggle),
+ willZoom: BoolCast(LinkManager.getOppositeAnchor(linkDoc, target)?.followLinkZoom, false),
+ zoomTime: NumCast(LinkManager.getOppositeAnchor(linkDoc, target)?.linkTransitionTime, 500),
+ zoomScale: Cast(sourceDoc.linkZoomScale, 'number', null),
+ effect: StrCast(LinkManager.getOppositeAnchor(linkDoc, target)?.linkEffect) as PresEffect,
+ effectDirection: StrCast(LinkManager.getOppositeAnchor(linkDoc, target)?.linkEffectDirection) as PresEffectDirection,
+ 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, 'inPlace'), finished), targetContexts, allFinished);
}
} else {
allFinished();
diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx
index 4b0310e76..4cd45fc9c 100644
--- a/src/client/util/SharingManager.tsx
+++ b/src/client/util/SharingManager.tsx
@@ -392,7 +392,7 @@ export class SharingManager extends React.Component<{}> {
onClick={() => {
let context: Opt<CollectionView>;
if (this.targetDoc && this.targetDocView && docs.length === 1 && (context = this.targetDocView.props.ContainingCollectionView)) {
- DocumentManager.Instance.jumpToDocument(this.targetDoc, true, undefined, [context.props.Document]);
+ DocumentManager.Instance.jumpToDocument(this.targetDoc, { willZoom: true }, undefined, [context.props.Document]);
}
}}
onPointerEnter={action(() => {
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index 043a83d16..c9c09b63b 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -147,7 +147,7 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps>()
setTimeout(() =>
docs.map(doc => {
// this allows 'addDocument' to see the annotationOn field in order to create a pushin
- Doc.SetInPlace(doc, 'isPushpin', undefined, true);
+ Doc.SetInPlace(doc, 'followLinkToggle', undefined, true);
doc.annotationOn === this.props.Document && Doc.SetInPlace(doc, 'annotationOn', undefined, true);
})
);
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index aa2818e0d..36ea3ef18 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -41,7 +41,7 @@ import { InkControlPtHandles, InkEndPtHandles } from './InkControlPtHandles';
import './InkStroke.scss';
import { InkStrokeProperties } from './InkStrokeProperties';
import { InkTangentHandles } from './InkTangentHandles';
-import { DocComponentView } from './nodes/DocumentView';
+import { DocComponentView, DocFocusOptions } from './nodes/DocumentView';
import { FieldView, FieldViewProps } from './nodes/FieldView';
import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
import { StyleProp } from './StyleProvider';
@@ -83,8 +83,8 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
return this._subContentView?.getAnchor?.() || this.rootDoc;
};
- scrollFocus = (textAnchor: Doc, smooth: boolean) => {
- return this._subContentView?.scrollFocus?.(textAnchor, smooth);
+ scrollFocus = (textAnchor: Doc, options: DocFocusOptions) => {
+ return this._subContentView?.scrollFocus?.(textAnchor, options);
};
/**
* @returns the center of the ink stroke in the ink document's coordinate space (not screen space, and not the ink data coordinate space);
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 165bb69bb..965cd560f 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -206,7 +206,7 @@ export class MainView extends React.Component {
document.addEventListener('dash', (e: any) => {
// event used by chrome plugin to tell Dash which document to focus on
const id = FormattedTextBox.GetDocFromUrl(e.detail);
- DocServer.GetRefField(id).then(doc => (doc instanceof Doc ? DocumentManager.Instance.jumpToDocument(doc, false, undefined, []) : null));
+ DocServer.GetRefField(id).then(doc => (doc instanceof Doc ? DocumentManager.Instance.jumpToDocument(doc, { willZoom: false }, undefined, []) : null));
});
document.addEventListener('linkAnnotationToDash', Hypothesis.linkListener);
this.initEventListeners();
diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx
index 1162cde50..07371c9d5 100644
--- a/src/client/views/MarqueeAnnotator.tsx
+++ b/src/client/views/MarqueeAnnotator.tsx
@@ -92,7 +92,7 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(this.props.docView, sourceAnchorCreator, targetCreator), e.pageX, e.pageY, {
dragComplete: e => {
if (!e.aborted && e.annoDragData && e.annoDragData.linkSourceDoc && e.annoDragData.dropDocument && e.linkDocument) {
- e.annoDragData.linkSourceDoc.isPushpin = e.annoDragData.dropDocument.annotationOn === this.props.rootDoc;
+ e.annoDragData.linkSourceDoc.followLinkToggle = e.annoDragData.dropDocument.annotationOn === this.props.rootDoc;
e.annoDragData.linkSourceDoc.followLinkZoom = false;
}
},
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index 3e199919e..f7cc32cff 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -1412,14 +1412,14 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
changeFollowBehavior = action((follow: string) => this.selectedDoc && (this.selectedDoc.followLinkLocation = follow));
@undoBatch
- changeAnimationBehavior = action((behavior: string) => this.destinationAnchor && (this.destinationAnchor.presEffect = behavior);
+ changeAnimationBehavior = action((behavior: string) => this.sourceAnchor && (this.sourceAnchor.linkAnimEffect = behavior));
@undoBatch
- changeEffectDirection = action((effect: PresEffectDirection) => this.destinationAnchor && (this.destinationAnchor.presEffectDirection = effect);
+ changeEffectDirection = action((effect: PresEffectDirection) => this.sourceAnchor && (this.sourceAnchor.linkAnimDirection = effect));
animationDirection = (direction: PresEffectDirection, icon: string, gridColumn: number, gridRow: number, opts: object) => {
- const lanch = this.destinationAnchor;
- const color = lanch?.presEffectDirection === direction || (direction === PresEffectDirection.Center && !lanch?.presEffectDirection) ? Colors.MEDIUM_BLUE : '';
+ const lanch = this.sourceAnchor;
+ const color = lanch?.linkAnimDirection === direction || (direction === PresEffectDirection.Center && !lanch?.linkAnimDirection) ? Colors.MEDIUM_BLUE : '';
return (
<Tooltip title={<div className="dash-tooltip">{direction}</div>}>
<div
@@ -1514,7 +1514,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.presZoom = scale);
+ this.sourceAnchor && (this.sourceAnchor.linkZoomScale = scale);
};
/**
@@ -1530,7 +1530,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
render() {
const isNovice = Doc.noviceMode;
- const zoom = Number((NumCast(this.sourceAnchor?.presZoom, 1) * 100).toPrecision(3));
+ const zoom = Number((NumCast(this.sourceAnchor?.linkZoomScale, 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!));
@@ -1621,7 +1621,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
</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.destinationAnchor?.presEffect, 'default')}>
+ <select style={{ width: '100%', gridColumn: 2 }} onChange={e => this.changeAnimationBehavior(e.currentTarget.value)} value={StrCast(this.sourceAnchor?.linkAnimEffect, '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>
@@ -1639,9 +1639,9 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
'0.1',
'0.1',
'10',
- NumCast(this.destinationAnchor?.presTransition) / 1000,
+ NumCast(this.sourceAnchor?.linkTransitionTime) / 1000,
true,
- (val: string) => PresBox.SetTransitionTime(val, (timeInMS: number) => this.destinationAnchor && (this.destinationAnchor.presTransition = timeInMS)),
+ (val: string) => PresBox.SetTransitionTime(val, (timeInMS: number) => this.sourceAnchor && (this.sourceAnchor.linkTransitionTime = timeInMS)),
indent
)}{' '}
<div
@@ -1668,6 +1668,16 @@ 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>Toggle Target (Show/Hide)</p>
+ <button
+ style={{ background: !this.sourceAnchor?.followLinkToggle ? '' : '#4476f7', borderRadius: 3 }}
+ onPointerDown={e => this.toggleAnchorProp(e, 'followLinkToggle', this.sourceAnchor)}
+ 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' }}>
diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx
index 29670a1a7..5e389e17e 100644
--- a/src/client/views/collections/CollectionNoteTakingView.tsx
+++ b/src/client/views/collections/CollectionNoteTakingView.tsx
@@ -193,13 +193,12 @@ 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 = NumCast(doc.focusSpeed, 500)), this._mainCont!, localTop[1] + this._mainCont!.scrollTop);
+ smoothScroll((focusSpeed = options.zoomTime ?? 500), this._mainCont!, localTop[1] + this._mainCont!.scrollTop);
}
}
const endFocus = async (moved: boolean) => (options?.afterFocus ? options?.afterFocus(moved) : ViewAdjustment.doNothing);
this.props.focus(this.rootDoc, {
- willZoom: options?.willZoom,
- scale: options?.scale,
+ ...options,
afterFocus: (didFocus: boolean) => new Promise<ViewAdjustment>(res => setTimeout(async () => res(await endFocus(didFocus)), focusSpeed)),
});
};
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index ac6391365..08aebc62d 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -255,13 +255,12 @@ 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 = NumCast(doc.focusSpeed, 500)), this._mainCont!, localTop[1] + this._mainCont!.scrollTop);
+ smoothScroll((focusSpeed = options.zoomTime ?? 500), this._mainCont!, localTop[1] + this._mainCont!.scrollTop);
}
}
const endFocus = async (moved: boolean) => options?.afterFocus?.(moved) ?? ViewAdjustment.doNothing;
this.props.focus(this.rootDoc, {
- willZoom: options?.willZoom,
- scale: options?.scale,
+ ...options,
afterFocus: (didFocus: boolean) => new Promise<ViewAdjustment>(res => setTimeout(async () => res(await endFocus(didFocus)), focusSpeed)),
});
};
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index 7696777fd..13984171c 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -297,7 +297,7 @@ export class TabDocView extends React.Component<TabDocViewProps> {
const docs = Cast(Doc.MyOverlayDocs.data, listSpec(Doc), []);
if (docs.includes(curPres)) docs.splice(docs.indexOf(curPres), 1);
CollectionDockingView.AddSplit(curPres, OpenWhereMod.right);
- setTimeout(() => DocumentManager.Instance.jumpToDocument(docList.lastElement(), false, undefined, []), 100); // keeps the pinned doc in view since the sidebar shifts things
+ setTimeout(() => DocumentManager.Instance.jumpToDocument(docList.lastElement(), { willZoom: false }, undefined, []), 100); // keeps the pinned doc in view since the sidebar shifts things
}
setTimeout(batch.end, 500); // need to wait until dockingview (goldenlayout) updates all its structurs
}
@@ -376,7 +376,7 @@ export class TabDocView extends React.Component<TabDocViewProps> {
focusFunc = (doc: Doc, options: DocFocusOptions) => {
const shrinkwrap = options?.originalTarget === this._document && this.view?.ComponentView?.shrinkWrap;
if (options?.willZoom !== false && shrinkwrap && this._document) {
- const focusSpeed = NumCast(this._document.focusSpeed, 500);
+ const focusSpeed = options.zoomTime ?? 500;
shrinkwrap();
this._document._viewTransition = `transform ${focusSpeed}ms`;
setTimeout(
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 56a5c3dcc..5fb3c1ac6 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1164,7 +1164,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const savedState = { panX: NumCast(this.Document._panX), panY: NumCast(this.Document._panY), scale: options?.willZoom ? this.Document[this.scaleFieldKey] : undefined };
const newState = HistoryUtil.getState();
const cantTransform = (this.rootDoc._isGroup || this.layoutDoc._lockedTransform) && !LightboxView.LightboxDoc;
- const { panX, panY, scale } = cantTransform ? savedState : this.calculatePanIntoView(doc, xfToCollection, options?.willZoom ? options?.scale || 0.75 : undefined);
+ const { panX, panY, scale } = cantTransform ? savedState : this.calculatePanIntoView(doc, xfToCollection, options?.willZoom ? options?.zoomScale || 0.75 : undefined);
if (!cantTransform) {
// only pan and zoom to focus on a document if the document is not an annotation in an annotation overlay collection
newState.initializers![this.Document[Id]] = { panX, panY };
@@ -1172,7 +1172,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
}
// focus on the document in the collection
const didMove = !cantTransform && !doc.z && (panX !== savedState.panX || panY !== savedState.panY || scale !== savedState.scale);
- const focusSpeed = options?.instant ? 0 : didMove ? NumCast(doc.focusSpeed, 500) : 0;
+ 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);
@@ -1538,8 +1538,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
};
@action
- scrollFocus = (anchor: Doc, smooth: boolean) => {
- const focusSpeed = !smooth ? 0 : NumCast(anchor.presTransition);
+ scrollFocus = (anchor: Doc, options: DocFocusOptions) => {
+ const focusSpeed = options.instant ? 0 : options.zoomTime ?? 500;
return PresBox.restoreTargetDocView(
this.rootDoc, //
{ pinDocLayout: BoolCast(anchor.presPinDocLayout) },
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 3fd6ca803..9df3e195f 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -541,7 +541,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
d.y = NumCast(d.y) - this.Bounds.top;
return d;
});
- const summary = Docs.Create.TextDocument('', { backgroundColor: '#e2ad32', x: this.Bounds.left, y: this.Bounds.top, isPushpin: true, _width: 200, _height: 200, _fitContentsToBox: true, _showSidebar: true, title: 'overview' });
+ const summary = Docs.Create.TextDocument('', { backgroundColor: '#e2ad32', x: this.Bounds.left, y: this.Bounds.top, followLinkToggle: true, _width: 200, _height: 200, _fitContentsToBox: true, _showSidebar: true, title: 'overview' });
const portal = Docs.Create.FreeformDocument(selected, { x: this.Bounds.left + 200, y: this.Bounds.top, isGroup: true, backgroundColor: 'transparent' });
DocUtils.MakeLink({ doc: summary }, { doc: portal }, 'summary of:summarized by', '');
diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
index 9778fc4fe..924e07daa 100644
--- a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
+++ b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
@@ -150,7 +150,7 @@ export class CollectionLinearView extends CollectionSubView() {
<span className="bottomPopup-text">
Currently playing:
{CollectionStackedTimeline.CurrentlyPlaying.map((clip, i) => (
- <span className="audio-title" onPointerDown={() => DocumentManager.Instance.jumpToDocument(clip, true, undefined, [])}>
+ <span className="audio-title" onPointerDown={() => DocumentManager.Instance.jumpToDocument(clip, { willZoom: true }, undefined, [])}>
{clip.title + (i === CollectionStackedTimeline.CurrentlyPlaying.length - 1 ? '' : ',')}
</span>
))}
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx
index a22999f52..c0dfeedfa 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx
@@ -214,7 +214,7 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
const aliasdoc = await SearchUtil.GetAliasesOfDocument(this._rowDataDoc);
const targetContext = aliasdoc.length <= 0 ? undefined : Cast(aliasdoc[0].context, Doc, null);
// Jump to the this document
- DocumentManager.Instance.jumpToDocument(this._rowDoc, false, emptyFunction, targetContext ? [targetContext] : [], undefined, undefined, undefined, () => this.props.setPreviewDoc(this._rowDoc));
+ DocumentManager.Instance.jumpToDocument(this._rowDoc, { willZoom: false }, emptyFunction, targetContext ? [targetContext] : [], () => this.props.setPreviewDoc(this._rowDoc));
}
};
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 48e32c071..fb20887cb 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -53,6 +53,7 @@ import { RadialMenu } from './RadialMenu';
import { ScriptingBox } from './ScriptingBox';
import { PinProps, PresBox } from './trails/PresBox';
import React = require('react');
+import { PresEffect, PresEffectDirection } from './trails';
const { Howl } = require('howler');
interface Window {
@@ -95,10 +96,17 @@ export const ViewSpecPrefix = 'viewSpec'; // field prefix for anchor fields that
export interface DocFocusOptions {
originalTarget?: Doc; // set in JumpToDocument, used by TabDocView to determine whether to fit contents to tab
willZoom?: boolean; // determines whether to zoom in on target document
- scale?: number; // percent of containing frame to zoom into document
+ zoomScale?: number; // percent of containing frame to zoom into document
+ zoomTime?: number;
afterFocus?: DocAfterFocusFunc; // function to call after focusing on a document
docTransform?: Transform; // when a document can't be panned and zoomed within its own container (say a group), then we need to continue to move up the render hierarchy to find something that can pan and zoom. when this happens the docTransform must accumulate all the transforms of each level of the hierarchy
instant?: boolean; // whether focus should happen instantly (as opposed to smooth zoom)
+ effect?: PresEffect; // animation effect for focus
+ effectDirection?: PresEffectDirection;
+ noSelect?: boolean; // whether target should be selected after focusing
+ 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
}
export type DocAfterFocusFunc = (notFocused: boolean) => Promise<ViewAdjustment>;
export type DocFocusFunc = (doc: Doc, options: DocFocusOptions) => void;
@@ -106,7 +114,7 @@ export type StyleProviderFunc = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, p
export interface DocComponentView {
updateIcon?: () => void; // updates the icon representation of the document
getAnchor?: () => Doc; // returns an Anchor Doc that represents the current state of the doc's componentview (e.g., the current playhead location of a an audio/video box)
- scrollFocus?: (doc: Doc, smooth: boolean) => Opt<number>; // returns the duration of the focus
+ scrollFocus?: (doc: Doc, options: DocFocusOptions) => Opt<number>; // returns the duration of the focus
brushView?: (view: { width: number; height: number; panX: number; panY: number }) => void;
setViewSpec?: (anchor: Doc, preview: boolean) => void; // sets viewing information for a componentview, typically when following a link. 'preview' tells the view to use the values without writing to the document
reverseNativeScaling?: () => boolean; // DocumentView's setup screenToLocal based on the doc having a nativeWidth/Height. However, some content views (e.g., FreeFormView w/ fitContentsToBox set) may ignore the native dimensions so this flags the DocumentView to not do Nativre scaling.
@@ -566,7 +574,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
.forEach(spec => (this.layoutDoc[spec.replace(ViewSpecPrefix, '')] = (field => (field instanceof ObjectField ? ObjectField.MakeCopy(field) : field))(anchor[spec])));
// after a render the general viewSpec should have created the right _componentView, so after a timeout, call the componentview to update its specific view specs
setTimeout(() => this._componentView?.setViewSpec?.(anchor, LinkDocPreview.LinkInfo ? true : false));
- const focusSpeed = this._componentView?.scrollFocus?.(anchor, options?.instant !== true && !LinkDocPreview.LinkInfo);
+ const focusSpeed = this._componentView?.scrollFocus?.(anchor, { ...options, instant: options?.instant || LinkDocPreview.LinkInfo ? true : false });
const endFocus = focusSpeed === undefined ? options?.afterFocus : async (moved: boolean) => options?.afterFocus?.(true) ?? ViewAdjustment.doNothing;
this.props.focus(options?.docTransform ? anchor : this.rootDoc, {
...options,
@@ -741,8 +749,8 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
toggleFollowLink = (location: Opt<string>, zoom?: boolean, setPushpin?: boolean): void => {
this.Document.ignoreClick = false;
if (setPushpin) {
- this.Document.isPushpin = !this.Document.isPushpin;
- this.Document._isLinkButton = this.Document.isPushpin || this.Document._isLinkButton;
+ this.Document.followLinkToggle = !this.Document.followLinkToggle;
+ this.Document._isLinkButton = this.Document.followLinkToggle || this.Document._isLinkButton;
} else {
this.Document._isLinkButton = !this.Document._isLinkButton;
}
@@ -759,14 +767,14 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
toggleTargetOnClick = (): void => {
this.Document.ignoreClick = false;
this.Document._isLinkButton = true;
- this.Document.isPushpin = true;
+ this.Document.followLinkToggle = true;
};
@undoBatch
@action
followLinkOnClick = (location: Opt<string>, zoom: boolean): void => {
this.Document.ignoreClick = false;
this.Document._isLinkButton = true;
- this.Document.isPushpin = false;
+ this.Document.followLinkToggle = false;
this.Document.followLinkZoom = zoom;
this.Document.followLinkLocation = location;
};
@@ -775,7 +783,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
selectOnClick = (): void => {
this.Document.ignoreClick = false;
this.Document._isLinkButton = false;
- this.Document.isPushpin = false;
+ this.Document.followLinkToggle = false;
this.Document.onClick = this.layoutDoc.onClick = undefined;
};
@undoBatch
@@ -934,7 +942,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
onClicks.push({ description: this.Document.isLinkButton ? 'Remove Follow Behavior' : 'Follow Link in Place', event: () => this.toggleFollowLink('inPlace', false, false), icon: 'link' });
!this.Document.isLinkButton && onClicks.push({ description: 'Follow Link on Right', event: () => this.toggleFollowLink('add:right', false, false), icon: 'link' });
onClicks.push({ description: this.Document.isLinkButton || this.onClickHandler ? 'Remove Click Behavior' : 'Follow Link', event: () => this.toggleFollowLink(undefined, false, false), icon: 'link' });
- onClicks.push({ description: (this.Document.isPushpin ? 'Remove' : 'Make') + ' Pushpin', event: () => this.toggleFollowLink(undefined, false, true), icon: 'map-pin' });
+ onClicks.push({ description: (this.Document.followLinkToggle ? 'Remove' : 'Make') + ' Pushpin', event: () => this.toggleFollowLink(undefined, false, true), icon: 'map-pin' });
onClicks.push({ description: 'Edit onClick Script', event: () => UndoManager.RunInBatch(() => DocUtils.makeCustomViewClicked(this.props.Document, undefined, 'onClick'), 'edit onClick'), icon: 'terminal' });
!existingOnClick && cm.addItem({ description: 'OnClick...', addDivider: true, noexpand: true, subitems: onClicks, icon: 'mouse-pointer' });
} else if (DocListCast(this.Document.links).length) {
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 1d79febdf..416107859 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -31,7 +31,7 @@ import { FieldView, FieldViewProps } from './FieldView';
import './ImageBox.scss';
import React = require('react');
import { PresBox } from './trails';
-import { DocumentViewProps } from './DocumentView';
+import { DocFocusOptions, DocumentViewProps } from './DocumentView';
export const pageSchema = createSchema({
googlePhotosUrl: 'string',
@@ -62,8 +62,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
};
@action
- scrollFocus = (anchor: Doc, smooth: boolean) => {
- const focusSpeed = !smooth ? 0 : NumCast(anchor.presTransition);
+ scrollFocus = (anchor: Doc, options: DocFocusOptions) => {
+ const focusSpeed = options.instant ? 0 : options.zoomTime ?? 500;
return PresBox.restoreTargetDocView(
this.rootDoc, //
{ pinDocLayout: BoolCast(anchor.presPinDocLayout) },
@@ -168,7 +168,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
const cropping = Doc.MakeCopy(region, true);
Doc.GetProto(region).lockedPosition = true;
Doc.GetProto(region).title = 'region:' + this.rootDoc.title;
- Doc.GetProto(region).isPushpin = true;
+ Doc.GetProto(region).followLinkToggle = true;
this.addDocument(region);
const anchx = NumCast(cropping.x);
const anchy = NumCast(cropping.y);
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 39e323247..b19c4a9e2 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -28,6 +28,7 @@ import './PDFBox.scss';
import { VideoBox } from './VideoBox';
import React = require('react');
import { PresBox } from './trails';
+import { DocFocusOptions } from './DocumentView';
@observer
export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps>() {
@@ -94,7 +95,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
const cropping = Doc.MakeCopy(region, true);
Doc.GetProto(region).lockedPosition = true;
Doc.GetProto(region).title = 'region:' + this.rootDoc.title;
- Doc.GetProto(region).isPushpin = true;
+ Doc.GetProto(region).followLinkToggle = true;
this.addDocument(region);
const docViewContent = this.props.docViewPath().lastElement().ContentDiv!;
@@ -200,16 +201,16 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
brushView = (view: { width: number; height: number; panX: number; panY: number }) => {
this._pdfViewer?.brushView(view);
};
- scrollFocus = (doc: Doc, smooth: boolean) => {
+ scrollFocus = (doc: Doc, options: DocFocusOptions) => {
let didToggle = false;
if (DocListCast(this.props.Document[this.fieldKey + '-sidebar']).includes(doc) && !this.SidebarShown) {
- this.toggleSidebar(!smooth);
+ this.toggleSidebar(!options.instant);
didToggle = true;
}
if (this._sidebarRef?.current?.makeDocUnfiltered(doc)) return 1;
this._initialScrollTarget = doc;
- PresBox.restoreTargetDocView(this.rootDoc, {}, doc, NumCast(doc.presTransition, NumCast(doc.focusSpeed, 500)), { pannable: doc.presPinData ? true : false });
- return this._pdfViewer?.scrollFocus(doc, NumCast(doc.presPinViewScroll, NumCast(doc.y)), smooth) ?? (didToggle ? 1 : undefined);
+ PresBox.restoreTargetDocView(this.rootDoc, {}, doc, options.zoomTime ?? 500, { pannable: doc.presPinData ? true : false });
+ return this._pdfViewer?.scrollFocus(doc, NumCast(doc.presPinViewScroll, NumCast(doc.y)), options) ?? (didToggle ? 1 : undefined);
};
getAnchor = () => {
const docAnchor = () => {
@@ -277,7 +278,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
setPdfViewer = (pdfViewer: PDFViewer) => {
this._pdfViewer = pdfViewer;
if (this._initialScrollTarget) {
- this.scrollFocus(this._initialScrollTarget, false);
+ this.scrollFocus(this._initialScrollTarget, { instant: true });
this._initialScrollTarget = undefined;
}
};
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 82d5b00f9..607cd6187 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -31,7 +31,7 @@ import { FieldView, FieldViewProps } from './FieldView';
import { RecordingBox } from './RecordingBox';
import './VideoBox.scss';
import { ObjectField } from '../../../fields/ObjectField';
-import { OpenWhere } from './DocumentView';
+import { DocFocusOptions, OpenWhere } from './DocumentView';
const path = require('path');
/**
@@ -950,7 +950,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
);
};
- scrollFocus = (doc: Doc, smooth: boolean) => {
+ scrollFocus = (doc: Doc, options: DocFocusOptions) => {
if (doc !== this.rootDoc) {
const showTime = Cast(doc._timecodeToShow, 'number', null);
showTime !== undefined && setTimeout(() => this.Seek(showTime), 100);
@@ -1006,7 +1006,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
Doc.GetProto(region).backgroundColor = 'transparent';
Doc.GetProto(region).lockedPosition = true;
Doc.GetProto(region).title = 'region:' + this.rootDoc.title;
- Doc.GetProto(region).isPushpin = true;
+ Doc.GetProto(region).followLinkToggle = true;
region._timecodeToHide = NumCast(region._timecodeToShow) + 0.0001;
this.addDocument(region);
const anchx = NumCast(cropping.x);
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 5ce6a0eb1..64b186489 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -29,7 +29,7 @@ import { AnchorMenu } from '../pdf/AnchorMenu';
import { Annotation } from '../pdf/Annotation';
import { SidebarAnnos } from '../SidebarAnnos';
import { StyleProp } from '../StyleProvider';
-import { DocumentViewProps } from './DocumentView';
+import { DocFocusOptions, DocumentViewProps } from './DocumentView';
import { FieldView, FieldViewProps } from './FieldView';
import { LinkDocPreview } from './LinkDocPreview';
import { VideoBox } from './VideoBox';
@@ -284,17 +284,17 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
setBrushViewer = (func?: (view: { width: number; height: number; panX: number; panY: number }) => void) => (this._setBrushViewer = func);
brushView = (view: { width: number; height: number; panX: number; panY: number }) => this._setBrushViewer?.(view);
- scrollFocus = (doc: Doc, smooth: boolean) => {
- if (StrCast(doc.webUrl) !== this._url) this.submitURL(StrCast(doc.webUrl), !smooth);
+ scrollFocus = (doc: Doc, options: DocFocusOptions) => {
+ if (StrCast(doc.webUrl) !== this._url) this.submitURL(StrCast(doc.webUrl), options.instant);
if (DocListCast(this.props.Document[this.fieldKey + '-sidebar']).includes(doc) && !this.SidebarShown) {
- this.toggleSidebar(!smooth);
+ this.toggleSidebar(options.instant);
}
if (this._sidebarRef?.current?.makeDocUnfiltered(doc)) return 1;
if (doc !== this.rootDoc && this._outerRef.current) {
const windowHeight = this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1);
const scrollTo = Utils.scrollIntoView(NumCast(doc.y), doc[HeightSym](), NumCast(this.layoutDoc._scrollTop), windowHeight, windowHeight * 0.1, Math.max(NumCast(doc.y) + doc[HeightSym](), this.getScrollHeight()));
if (scrollTo !== undefined && this._initialScroll === undefined) {
- const focusSpeed = smooth ? NumCast(doc.focusSpeed, 500) : 0;
+ const focusSpeed = options.instant ? 0 : options.zoomTime ?? 500;
this.goTo(scrollTo, focusSpeed);
return focusSpeed;
} else if (!this._webPageHasBeenRendered || !this.getScrollHeight() || this._initialScroll !== undefined) {
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index ce4639b76..9e91f6c46 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -45,7 +45,7 @@ import { LightboxView } from '../../LightboxView';
import { AnchorMenu } from '../../pdf/AnchorMenu';
import { SidebarAnnos } from '../../SidebarAnnos';
import { StyleProp } from '../../StyleProvider';
-import { DocumentViewInternal, OpenWhere } from '../DocumentView';
+import { DocFocusOptions, DocumentViewInternal, OpenWhere } from '../DocumentView';
import { FieldView, FieldViewProps } from '../FieldView';
import { LinkDocPreview } from '../LinkDocPreview';
import { DashDocCommentView } from './DashDocCommentView';
@@ -689,9 +689,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
pinToPres = (anchor: Doc) => this.props.pinToPres(anchor, {});
@undoBatch
- makePushpin = (anchor: Doc) => (anchor.isPushpin = !anchor.isPushpin);
+ makePushpin = (anchor: Doc) => (anchor.followLinkToggle = !anchor.followLinkToggle);
- isPushpin = (anchor: Doc) => BoolCast(anchor.isPushpin);
+ isPushpin = (anchor: Doc) => BoolCast(anchor.followLinkToggle);
specificContextMenu = (e: React.MouseEvent): void => {
const cm = ContextMenu.Instance;
@@ -922,10 +922,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
return anchorDoc ?? this.rootDoc;
}
- scrollFocus = (textAnchor: Doc, smooth: boolean) => {
+ scrollFocus = (textAnchor: Doc, options: DocFocusOptions) => {
let didToggle = false;
if (DocListCast(this.Document[this.fieldKey + '-sidebar']).includes(textAnchor) && !this.SidebarShown) {
- this.toggleSidebar(!smooth);
+ this.toggleSidebar(options.instant);
didToggle = true;
}
const textAnchorId = textAnchor[Id];
@@ -966,7 +966,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
const content = (ret.frag as any)?.content;
if ((ret.frag.size > 2 || (content?.length && content[0].type === this._editorView.state.schema.nodes.audiotag)) && ret.start >= 0) {
- smooth && (this._focusSpeed = 500);
+ !options.instant && (this._focusSpeed = 500);
let selection = TextSelection.near(editor.state.doc.resolve(ret.start)); // default to near the start
if (ret.frag.firstChild) {
selection = TextSelection.between(editor.state.doc.resolve(ret.start), editor.state.doc.resolve(ret.start + ret.frag.firstChild.nodeSize)); // bcz: looks better to not have the target selected
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index c04b79a1e..4073677f3 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -15,7 +15,7 @@ import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../../fields/Ty
import { AudioField } from '../../../../fields/URLField';
import { emptyFunction, returnFalse, returnOne, setupMoveUpEvents, StopEvent } from '../../../../Utils';
import { DocServer } from '../../../DocServer';
-import { Docs } from '../../../documents/Documents';
+import { Docs, DocumentOptions } from '../../../documents/Documents';
import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes';
import { DocumentManager } from '../../../util/DocumentManager';
import { ScriptingGlobals } from '../../../util/ScriptingGlobals';
@@ -35,7 +35,7 @@ import { ScriptingBox } from '../ScriptingBox';
import './PresBox.scss';
import { PresEffect, PresEffectDirection, PresMovement, PresStatus } from './PresEnums';
import { map } from 'bluebird';
-import { OpenWhere, OpenWhereMod } from '../DocumentView';
+import { DocFocusOptions, OpenWhere, OpenWhereMod } from '../DocumentView';
const { Howl } = require('howler');
export interface PinProps {
@@ -313,6 +313,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
this.rootDoc._itemIndex = index;
const activeItem: Doc = this.activeItem;
const targetDoc: Doc = this.targetDoc;
+ let focusSpeed = 500;
if (activeItem.presActiveFrame !== undefined) {
const transTime = NumCast(activeItem.presTransition, 500);
const context = DocCast(DocCast(activeItem.presentationTargetDoc).context);
@@ -334,11 +335,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
if (this.layoutDoc.presStatus !== PresStatus.Edit && (targetDoc.type === DocumentType.AUDIO || targetDoc.type === DocumentType.VID) && activeItem.mediaStart === 'auto') {
this.startTempMedia(targetDoc, activeItem);
}
- if (targetDoc) {
- // Doc.linkFollowHighlight(targetDoc.annotationOn instanceof Doc ? [targetDoc, targetDoc.annotationOn] : targetDoc);
- targetDoc && runInAction(() => (targetDoc.focusSpeed = activeItem.presMovement === PresMovement.Jump ? 0 : NumCast(activeItem.presTransition, 500)));
- setTimeout(() => (targetDoc.focusSpeed = undefined), NumCast(targetDoc.focusSpeed) + 10);
- }
if (targetDoc?.lastFrame !== undefined) {
targetDoc._currentFrame = 0;
}
@@ -583,8 +579,14 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
LightboxView.SetLightboxDoc(targetDoc); // openInTab(targetDoc);
} else if (targetDoc && activeItem.presMovement !== PresMovement.None) {
LightboxView.SetLightboxDoc(undefined);
- const zooming = activeItem.presMovement !== PresMovement.Pan;
- DocumentManager.Instance.jumpToDocument(targetDoc, zooming, openInTab, srcContext ? [srcContext] : [], undefined, undefined, activeItem, finished, undefined, true, NumCast(activeItem.presZoom, 1));
+ const options: DocFocusOptions = {
+ willZoom: activeItem.presMovement !== PresMovement.Pan,
+ zoomScale: NumCast(activeItem.presZoom, 1),
+ zoomTime: activeItem.presMovement === PresMovement.Jump ? 0 : NumCast(activeItem.presTransition, 500),
+ noSelect: true,
+ originatingDoc: activeItem,
+ };
+ DocumentManager.Instance.jumpToDocument(targetDoc, options, openInTab, srcContext ? [srcContext] : [], finished);
} else if (activeItem.presMovement === PresMovement.None && targetDoc.type === DocumentType.SCRIPTING) {
(DocumentManager.Instance.getFirstDocumentView(targetDoc)?.ComponentView as ScriptingBox)?.onRun?.();
}
@@ -1199,7 +1201,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
_batch: UndoManager.Batch | undefined = undefined;
- public static inputter = (min: string, step: string, max: string, value: number, active: boolean, change: (val: string) => void, hmargin?:number) => {
+ public static inputter = (min: string, step: string, max: string, value: number, active: boolean, change: (val: string) => void, hmargin?: number) => {
let batch: any;
return (
<input
@@ -1208,7 +1210,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
min={min}
max={max}
value={value}
- style={{marginLeft: hmargin, marginRight:hmargin, width: `calc(100% - ${2*(hmargin??0)}px)`}}
+ style={{ marginLeft: hmargin, marginRight: hmargin, width: `calc(100% - ${2 * (hmargin ?? 0)}px)` }}
className={`toolbar-slider ${active ? '' : 'none'}`}
onPointerDown={e => {
batch = UndoManager.StartBatch('pres slider');
diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx
index 133b882f6..7069ff399 100644
--- a/src/client/views/pdf/Annotation.tsx
+++ b/src/client/views/pdf/Annotation.tsx
@@ -55,9 +55,9 @@ class RegionAnnotation extends React.Component<IRegionAnnotationProps> {
pinToPres = () => this.props.pinToPres(this.annoTextRegion, {});
@undoBatch
- makePushpin = () => (this.annoTextRegion.isPushpin = !this.annoTextRegion.isPushpin);
+ makePushpin = () => (this.annoTextRegion.followLinkToggle = !this.annoTextRegion.followLinkToggle);
- isPushpin = () => BoolCast(this.annoTextRegion.isPushpin);
+ isPushpin = () => BoolCast(this.annoTextRegion.followLinkToggle);
@action
onPointerDown = (e: React.PointerEvent) => {
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 5a5c63c3d..0703ca9b4 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -16,7 +16,7 @@ import { SnappingManager } from '../../util/SnappingManager';
import { MarqueeOptionsMenu } from '../collections/collectionFreeForm';
import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
import { MarqueeAnnotator } from '../MarqueeAnnotator';
-import { DocumentViewProps } from '../nodes/DocumentView';
+import { DocFocusOptions, DocumentViewProps } from '../nodes/DocumentView';
import { FieldViewProps } from '../nodes/FieldView';
import { LinkDocPreview } from '../nodes/LinkDocPreview';
import { StyleProp } from '../StyleProvider';
@@ -166,7 +166,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
// 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, smooth: boolean) => {
+ scrollFocus = (doc: Doc, scrollTop: number, options: DocFocusOptions) => {
const mainCont = this._mainCont.current;
let focusSpeed: Opt<number>;
if (doc !== this.props.rootDoc && mainCont) {
@@ -174,7 +174,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
const scrollTo = doc.unrendered ? scrollTop : Utils.scrollIntoView(scrollTop, doc[HeightSym](), NumCast(this.props.layoutDoc._scrollTop), windowHeight, 0.1 * windowHeight, NumCast(this.props.Document.scrollHeight));
if (scrollTo !== undefined && scrollTo !== this.props.layoutDoc._scrollTop) {
if (!this._pdfViewer) this._initialScroll = scrollTo;
- else if (smooth) smoothScroll((focusSpeed = NumCast(doc.focusSpeed, 500)), mainCont, scrollTo);
+ else if (!options.instant) smoothScroll((focusSpeed = options.zoomTime??500), mainCont, scrollTo);
else this._mainCont.current?.scrollTo({ top: Math.abs(scrollTo || 0) });
}
} else {
@@ -295,7 +295,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
@action
scrollToAnnotation = (scrollToAnnotation: Doc) => {
if (scrollToAnnotation) {
- this.scrollFocus(scrollToAnnotation, NumCast(scrollToAnnotation.y), true);
+ this.scrollFocus(scrollToAnnotation, NumCast(scrollToAnnotation.y), {zoomTime: 500});
Doc.linkFollowHighlight(scrollToAnnotation);
}
};
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index c5177de90..9c656e093 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -407,7 +407,7 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
* or opening it in a new tab.
*/
selectElement = async (doc: Doc, finishFunc: () => void) => {
- await DocumentManager.Instance.jumpToDocument(doc, true, undefined, [], undefined, undefined, undefined, finishFunc);
+ await DocumentManager.Instance.jumpToDocument(doc, { willZoom: true }, undefined, [], finishFunc);
};
/**