aboutsummaryrefslogtreecommitdiff
path: root/src/client/util
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/util')
-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
4 files changed, 250 insertions, 268 deletions
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(() => {