aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--package-lock.json21
-rw-r--r--package.json1
-rw-r--r--src/Utils.ts7
-rw-r--r--src/client/documents/DocumentTypes.ts4
-rw-r--r--src/client/documents/Documents.ts7
-rw-r--r--src/client/util/DocumentManager.ts157
-rw-r--r--src/client/util/LinkManager.ts61
-rw-r--r--src/client/views/AntimodeMenu.scss2
-rw-r--r--src/client/views/DocumentDecorations.scss1
-rw-r--r--src/client/views/DocumentDecorations.tsx20
-rw-r--r--src/client/views/GestureOverlay.tsx41
-rw-r--r--src/client/views/GlobalKeyHandler.ts10
-rw-r--r--src/client/views/LightboxView.scss26
-rw-r--r--src/client/views/LightboxView.tsx105
-rw-r--r--src/client/views/MainView.tsx45
-rw-r--r--src/client/views/MarqueeAnnotator.tsx3
-rw-r--r--src/client/views/OverlayView.tsx4
-rw-r--r--src/client/views/Palette.tsx4
-rw-r--r--src/client/views/PreviewCursor.scss1
-rw-r--r--src/client/views/PropertiesView.tsx2
-rw-r--r--src/client/views/StyleProvider.tsx4
-rw-r--r--src/client/views/TemplateMenu.tsx6
-rw-r--r--src/client/views/collections/CollectionLinearView.tsx4
-rw-r--r--src/client/views/collections/CollectionMenu.scss15
-rw-r--r--src/client/views/collections/CollectionMenu.tsx179
-rw-r--r--src/client/views/collections/CollectionSchemaView.tsx4
-rw-r--r--src/client/views/collections/CollectionStackingView.tsx53
-rw-r--r--src/client/views/collections/CollectionTreeView.tsx2
-rw-r--r--src/client/views/collections/CollectionView.tsx41
-rw-r--r--src/client/views/collections/SchemaTable.tsx6
-rw-r--r--src/client/views/collections/TabDocView.tsx16
-rw-r--r--src/client/views/collections/TreeView.tsx10
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx45
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx29
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx137
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx6
-rw-r--r--src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx4
-rw-r--r--src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx4
-rw-r--r--src/client/views/linking/LinkEditor.tsx84
-rw-r--r--src/client/views/linking/LinkMenu.scss40
-rw-r--r--src/client/views/linking/LinkMenu.tsx75
-rw-r--r--src/client/views/linking/LinkMenuGroup.tsx10
-rw-r--r--src/client/views/linking/LinkMenuItem.tsx113
-rw-r--r--src/client/views/nodes/AudioBox.tsx4
-rw-r--r--src/client/views/nodes/CollectionFreeFormDocumentView.tsx3
-rw-r--r--src/client/views/nodes/DocHolderBox.tsx4
-rw-r--r--src/client/views/nodes/DocumentContentsView.tsx23
-rw-r--r--src/client/views/nodes/DocumentLinksButton.tsx24
-rw-r--r--src/client/views/nodes/DocumentView.tsx97
-rw-r--r--src/client/views/nodes/FilterBox.tsx4
-rw-r--r--src/client/views/nodes/ImageBox.tsx5
-rw-r--r--src/client/views/nodes/KeyValuePair.tsx6
-rw-r--r--src/client/views/nodes/LinkAnchorBox.tsx11
-rw-r--r--src/client/views/nodes/LinkDocPreview.scss72
-rw-r--r--src/client/views/nodes/LinkDocPreview.tsx247
-rw-r--r--src/client/views/nodes/PDFBox.tsx6
-rw-r--r--src/client/views/nodes/PresBox.tsx2
-rw-r--r--src/client/views/nodes/VideoBox.scss5
-rw-r--r--src/client/views/nodes/VideoBox.tsx10
-rw-r--r--src/client/views/nodes/WebBox.scss2
-rw-r--r--src/client/views/nodes/WebBox.tsx262
-rw-r--r--src/client/views/nodes/formattedText/DashDocView.tsx4
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.scss2
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx292
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBoxComment.scss84
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx327
-rw-r--r--src/client/views/nodes/formattedText/RichTextMenu.tsx24
-rw-r--r--src/client/views/nodes/formattedText/RichTextSchema.tsx1
-rw-r--r--src/client/views/nodes/formattedText/TooltipTextMenu.scss2
-rw-r--r--src/client/views/nodes/formattedText/marks_rts.ts21
-rw-r--r--src/client/views/nodes/formattedText/nodes_rts.ts4
-rw-r--r--src/client/views/nodes/formattedText/prosemirrorPatches.js18
-rw-r--r--src/client/views/pdf/PDFViewer.tsx129
-rw-r--r--src/client/views/presentationview/PresElementBox.tsx4
-rw-r--r--src/client/views/search/SearchBox.scss2
-rw-r--r--src/fields/Doc.ts7
-rw-r--r--src/fields/documentSchemas.ts7
-rw-r--r--src/fields/util.ts4
-rw-r--r--src/mobile/AudioUpload.tsx4
-rw-r--r--src/mobile/MobileInterface.tsx2
80 files changed, 1522 insertions, 1612 deletions
diff --git a/package-lock.json b/package-lock.json
index ed4f697b6..0b5a2f240 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -6358,8 +6358,7 @@
"follow-redirects": {
"version": "1.12.1",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.12.1.tgz",
- "integrity": "sha512-tmRv0AVuR7ZyouUHLeNSiO6pqulF7dYa3s19c6t+wz9LD69/uSzdMxJ2S91nTI9U3rt/IldxpzMOFejp6f0hjg==",
- "dev": true
+ "integrity": "sha512-tmRv0AVuR7ZyouUHLeNSiO6pqulF7dYa3s19c6t+wz9LD69/uSzdMxJ2S91nTI9U3rt/IldxpzMOFejp6f0hjg=="
},
"for-each-property": {
"version": "0.0.4",
@@ -16843,6 +16842,24 @@
"punycode": "^2.1.0"
}
},
+ "translate-google-api": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/translate-google-api/-/translate-google-api-1.0.4.tgz",
+ "integrity": "sha512-KVXmo4+64/H1vIbnzf2zNiJ2JLeEB3jrEnNRP2EFNAGNqna/5bmw/Cps3pCHu0n3BzTOoWh9u6wFvrRYdzQ6Iw==",
+ "requires": {
+ "axios": "^0.20.0"
+ },
+ "dependencies": {
+ "axios": {
+ "version": "0.20.0",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-0.20.0.tgz",
+ "integrity": "sha512-ANA4rr2BDcmmAQLOKft2fufrtuvlqR+cXNNinUmvfeSNCOF98PZL+7M/v1zIdGo7OLjEA9J2gXJL+j4zGsl0bA==",
+ "requires": {
+ "follow-redirects": "^1.10.0"
+ }
+ }
+ }
+ },
"traverse-chain": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/traverse-chain/-/traverse-chain-0.1.0.tgz",
diff --git a/package.json b/package.json
index c7d674cf0..d15c97e88 100644
--- a/package.json
+++ b/package.json
@@ -251,6 +251,7 @@
"standard-http-error": "^2.0.1",
"styled-components": "^4.4.1",
"textarea-caret": "^3.1.0",
+ "translate-google-api": "^1.0.4",
"typescript-collections": "^1.3.3",
"typescript-language-server": "^0.4.0",
"url-loader": "^1.1.2",
diff --git a/src/Utils.ts b/src/Utils.ts
index c7074c3da..b6a59118a 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -191,6 +191,13 @@ export namespace Utils {
return { h: h, s: s, l: l };
}
+ export function scrollIntoView(targetY: number, targetHgt: number, scrollTop: number, contextHgt: number) {
+ if (scrollTop + contextHgt < targetY + targetHgt * 1.1) {
+ return Math.ceil(targetY + targetHgt * 1.1 - contextHgt);
+ } else if (scrollTop > targetY - targetHgt * .1) {
+ return Math.max(0, Math.floor(targetY - targetHgt * .1));
+ }
+ }
export function clamp(n: number, lower: number, upper: number) {
return Math.max(lower, Math.min(upper, n));
diff --git a/src/client/documents/DocumentTypes.ts b/src/client/documents/DocumentTypes.ts
index 37a148e55..080657fd8 100644
--- a/src/client/documents/DocumentTypes.ts
+++ b/src/client/documents/DocumentTypes.ts
@@ -38,5 +38,7 @@ export enum DocumentType {
LINKDB = "linkdb", // database of links ??? why do we have this
SCRIPTDB = "scriptdb", // database of scripts
- GROUPDB = "groupdb" // database of groups
+ GROUPDB = "groupdb", // database of groups
+
+ TEXTANCHOR = "textanchor" // selection of text in a text box
} \ No newline at end of file
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 1a4aae17e..de4a8f43c 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -373,6 +373,9 @@ export namespace Docs {
}],
[DocumentType.GROUP, {
layout: { view: EmptyBox, dataField: defaultDataKey }
+ }],
+ [DocumentType.TEXTANCHOR, {
+ layout: { view: EmptyBox, dataField: defaultDataKey }
}]
]);
@@ -785,6 +788,10 @@ export namespace Docs {
return InstanceFromProto(Prototypes.get(DocumentType.DOCHOLDER), document, { title: document ? document.title + "" : "container", targetDropAction: "move", ...options });
}
+ export function TextanchorDocument(options: DocumentOptions = {}) {
+ return InstanceFromProto(Prototypes.get(DocumentType.TEXTANCHOR), undefined, { targetDropAction: "move", ...options });
+ }
+
export function FreeformDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) {
return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", ...options, _viewType: CollectionViewType.Freeform }, id);
}
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 41c7a1409..816f7f6be 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -1,13 +1,13 @@
-import { action, observable } from 'mobx';
+import { action, observable, runInAction } from 'mobx';
import { Doc, DocListCast, DocListCastAsync, Opt } from '../../fields/Doc';
import { Id } from '../../fields/FieldSymbols';
-import { Cast, NumCast } from '../../fields/Types';
+import { Cast, NumCast, StrCast } from '../../fields/Types';
import { returnFalse } from '../../Utils';
import { DocumentType } from '../documents/DocumentTypes';
import { CollectionDockingView } from '../views/collections/CollectionDockingView';
import { CollectionView } from '../views/collections/CollectionView';
+import { LightboxView } from '../views/LightboxView';
import { DocumentView } from '../views/nodes/DocumentView';
-import { LinkManager } from './LinkManager';
import { Scripting } from './Scripting';
export type CreateViewFunc = (doc: Doc, followLinkLocation: string, finished?: () => void) => void;
@@ -15,32 +15,28 @@ export type CreateViewFunc = (doc: Doc, followLinkLocation: string, finished?: (
export class DocumentManager {
//global holds all of the nodes (regardless of which collection they're in)
- @observable
- public DocumentViews: DocumentView[] = [];
- @observable LinkedDocumentViews: { a: DocumentView, b: DocumentView, l: Doc }[] = [];
+ @observable public DocumentViews: DocumentView[] = [];
+ @observable public LinkedDocumentViews: { a: DocumentView, b: DocumentView, l: Doc }[] = [];
- // singleton instance
private static _instance: DocumentManager;
-
- // create one and only one instance of NodeManager
- public static get Instance(): DocumentManager {
- return this._instance || (this._instance = new this());
- }
+ public static get Instance(): DocumentManager { return this._instance || (this._instance = new this()); }
//private constructor so no other class can create a nodemanager
- private constructor() {
- }
+ private constructor() { }
@action
public AddView = (view: DocumentView) => {
- const linksList = DocListCast(view.props.Document.links);
- linksList.forEach(link => {
- const linkToDoc = link && LinkManager.getOppositeAnchor(link, view.props.Document);
- linkToDoc && DocumentManager.Instance.DocumentViews.filter(dv => Doc.AreProtosEqual(dv.props.Document, linkToDoc)).forEach(dv => {
- if (dv.props.Document.type !== DocumentType.LINK || dv.props.LayoutTemplateString !== view.props.LayoutTemplateString) {
- this.LinkedDocumentViews.push({ a: dv, b: view, l: link });
- }
- });
+ DocListCast(view.rootDoc.links).forEach(link => {
+ const whichOtherAnchor = view.props.LayoutTemplateString?.includes("anchor2") ? "anchor1" : "anchor2";
+ const otherDoc = link && (link[whichOtherAnchor] as Doc);
+ const otherDocAnno = otherDoc?.type === DocumentType.TEXTANCHOR ? otherDoc.annotationOn as Doc : undefined;
+ otherDoc && DocumentManager.Instance.DocumentViews.
+ filter(dv => Doc.AreProtosEqual(dv.rootDoc, otherDoc) || Doc.AreProtosEqual(dv.rootDoc, otherDocAnno)).
+ forEach(otherView => {
+ if (otherView.rootDoc.type !== DocumentType.LINK || otherView.props.LayoutTemplateString !== view.props.LayoutTemplateString) {
+ this.LinkedDocumentViews.push({ a: whichOtherAnchor === "anchor1" ? otherView : view, b: whichOtherAnchor === "anchor1" ? view : otherView, l: link });
+ }
+ });
});
this.DocumentViews.push(view);
}
@@ -55,13 +51,13 @@ export class DocumentManager {
public getDocumentViewsById(id: string) {
const toReturn: DocumentView[] = [];
DocumentManager.Instance.DocumentViews.map(view => {
- if (view.props.Document[Id] === id) {
+ if (view.rootDoc[Id] === id) {
toReturn.push(view);
}
});
if (toReturn.length === 0) {
DocumentManager.Instance.DocumentViews.map(view => {
- const doc = view.props.Document.proto;
+ const doc = view.rootDoc.proto;
if (doc && doc[Id] && doc[Id] === id) {
toReturn.push(view);
}
@@ -81,14 +77,14 @@ export class DocumentManager {
for (const pass of passes) {
DocumentManager.Instance.DocumentViews.map(view => {
- if (view.props.Document[Id] === id && (!pass || view.props.ContainingCollectionView === preferredCollection)) {
+ if (view.rootDoc[Id] === id && (!pass || view.props.ContainingCollectionView === preferredCollection)) {
toReturn = view;
return;
}
});
if (!toReturn) {
DocumentManager.Instance.DocumentViews.map(view => {
- const doc = view.props.Document.proto;
+ const doc = view.rootDoc.proto;
if (doc && doc[Id] === id && (!pass || view.props.ContainingCollectionView === preferredCollection)) {
toReturn = view;
}
@@ -105,21 +101,28 @@ export class DocumentManager {
return this.getDocumentViewById(toFind[Id], preferredCollection);
}
+ public getLightboxDocumentView = (toFind: Doc, originatingDoc: Opt<Doc> = undefined): DocumentView | undefined => {
+ const docViews = DocumentManager.Instance.DocumentViews;
+ const views: DocumentView[] = [];
+ docViews.map(view => LightboxView.IsLightboxDocView(view.docViewPath) && view.rootDoc === toFind && views.push(view));
+ return views?.find(view => view.ContentDiv?.getBoundingClientRect().width && view.props.focus !== returnFalse) || views?.find(view => view.props.focus !== returnFalse) || (views.length ? views[0] : undefined);
+ }
public getFirstDocumentView = (toFind: Doc, originatingDoc: Opt<Doc> = undefined): DocumentView | undefined => {
- const views = this.getDocumentViews(toFind).filter(view => view.props.Document !== originatingDoc);
+ const views = this.getDocumentViews(toFind).filter(view => view.rootDoc !== originatingDoc);
return views?.find(view => view.ContentDiv?.getBoundingClientRect().width && view.props.focus !== returnFalse) || views?.find(view => view.props.focus !== returnFalse) || (views.length ? views[0] : undefined);
}
public getDocumentViews(toFind: Doc): DocumentView[] {
const toReturn: DocumentView[] = [];
- const docViews = DocumentManager.Instance.DocumentViews;
+ const docViews = DocumentManager.Instance.DocumentViews.filter(view => !LightboxView.IsLightboxDocView(view.docViewPath));
+ const lightViews = DocumentManager.Instance.DocumentViews.filter(view => LightboxView.IsLightboxDocView(view.docViewPath));
// heuristic to return the "best" documents first:
+ // choose a document in the lightbox first
// choose an exact match over an alias match
- // choose documents that have a PanelWidth() over those that don't (the treeview documents have no panelWidth)
- docViews.map(view => view.props.PanelWidth() > 1 && view.props.Document === toFind && toReturn.push(view));
- docViews.map(view => view.props.PanelWidth() <= 1 && view.props.Document === toFind && toReturn.push(view));
- docViews.map(view => view.props.PanelWidth() > 1 && view.props.Document !== toFind && Doc.AreProtosEqual(view.props.Document, toFind) && toReturn.push(view));
- docViews.map(view => view.props.PanelWidth() <= 1 && view.props.Document !== toFind && Doc.AreProtosEqual(view.props.Document, toFind) && toReturn.push(view));
+ lightViews.map(view => view.rootDoc === toFind && toReturn.push(view));
+ lightViews.map(view => view.rootDoc !== toFind && Doc.AreProtosEqual(view.rootDoc, toFind) && toReturn.push(view));
+ docViews.map(view => view.rootDoc === toFind && toReturn.push(view));
+ docViews.map(view => view.rootDoc !== toFind && Doc.AreProtosEqual(view.rootDoc, toFind) && toReturn.push(view));
return toReturn;
}
@@ -139,56 +142,51 @@ export class DocumentManager {
originatingDoc: Opt<Doc> = undefined, // doc that initiated the display of the target odoc
finished?: () => void,
): Promise<void> => {
- const getFirstDocView = DocumentManager.Instance.getFirstDocumentView;
- const focusAndFinish = () => { finished?.(); return false; };
+ const getFirstDocView = LightboxView.LightboxDoc ? DocumentManager.Instance.getLightboxDocumentView : DocumentManager.Instance.getFirstDocumentView;
+ const docView = getFirstDocView(targetDoc, originatingDoc);
const highlight = () => {
const finalDocView = getFirstDocView(targetDoc);
- if (finalDocView) {
- finalDocView.layoutDoc.scrollToLinkID = linkDoc?.[Id];
- Doc.linkFollowHighlight(finalDocView.props.Document);
+ finalDocView && Doc.linkFollowHighlight(finalDocView.rootDoc);
+ };
+ const focusAndFinish = (didFocus: boolean) => {
+ if (originatingDoc?.isPushpin) {
+ if (!didFocus || targetDoc.hidden) {
+ targetDoc.hidden = !targetDoc.hidden;
+ }
+ } else {
+ targetDoc.hidden && (targetDoc.hidden = undefined);
+ docView?.select(false);
}
+ highlight();
+ finished?.();
+ return false;
};
- const docView = getFirstDocView(targetDoc, originatingDoc);
- let annotatedDoc = await Cast(targetDoc.annotationOn, Doc);
- if (annotatedDoc && annotatedDoc !== originatingDoc?.context && !targetDoc?.isPushpin) {
+ let annotatedDoc = Cast(targetDoc.annotationOn, Doc, null);
+ const contextDocs = docContext ? await DocListCastAsync(docContext.data) : undefined;
+ const contextDoc = contextDocs?.find(doc => Doc.AreProtosEqual(doc, targetDoc) || Doc.AreProtosEqual(doc, annotatedDoc)) ? docContext : undefined;
+ const targetDocContext = contextDoc || annotatedDoc;
+ const targetDocContextView = targetDocContext && getFirstDocView(targetDocContext);
+ if (!docView && annotatedDoc && annotatedDoc !== originatingDoc?.context && targetDoc.type === DocumentType.TEXTANCHOR) {
const first = getFirstDocView(annotatedDoc);
if (first) {
- annotatedDoc = first.props.Document;
- first.props.focus(annotatedDoc, false);
+ annotatedDoc = first.rootDoc;
+ first.focus(targetDoc, false);
}
- }
- if (docView) { // we have a docView already and aren't forced to create a new one ... just focus on the document. TODO move into view if necessary otherwise just highlight?
- const sameContext = annotatedDoc && annotatedDoc === originatingDoc?.context;
- if (originatingDoc?.isPushpin) {
- docView.props.focus(docView.props.Document, willZoom, undefined, (didFocus: boolean) => {
- if (!didFocus || docView.props.Document.hidden) {
- docView.props.Document.hidden = !docView.props.Document.hidden;
- }
- return focusAndFinish();
- }, sameContext, false);// don't want to focus the container if the source and target are in the same container, so pass 'sameContext' for dontCenter parameter
- //finished?.();
- }
- else {
- docView.select(false);
- docView.props.Document.hidden && (docView.props.Document.hidden = undefined);
- // @ts-ignore
- docView.props.focus(docView.props.Document, willZoom, undefined, focusAndFinish, sameContext, false);
- }
- highlight();
+ } else if (docView && (targetDocContextView || !targetDocContext)) { // we have a docView already and aren't forced to create a new one ... just focus on the document. TODO move into view if necessary otherwise just highlight?
+ docView.props.focus(docView.rootDoc, willZoom, undefined, (didFocus: boolean) =>
+ new Promise<boolean>(res => {
+ focusAndFinish(didFocus);
+ res();
+ })
+ );
} else {
- const contextDocs = docContext ? await DocListCastAsync(docContext.data) : undefined;
- const contextDoc = contextDocs?.find(doc => Doc.AreProtosEqual(doc, targetDoc)) ? docContext : undefined;
- const targetDocContext = annotatedDoc || contextDoc;
-
if (!targetDocContext) { // we don't have a view and there's no context specified ... create a new view of the target using the dockFunc or default
createViewFunc(Doc.BrushDoc(targetDoc), finished); // bcz: should we use this?: Doc.MakeAlias(targetDoc)));
highlight();
} else { // otherwise try to get a view of the context of the target
- const targetDocContextView = getFirstDocView(targetDocContext);
- targetDocContext._scrollY = targetDocContext._scrollPreviewY = NumCast(targetDocContext._scrollTop, 0); // this will force PDFs to activate and load their annotations / allow scrolling
if (targetDocContextView) { // we found a context view and aren't forced to create a new one ... focus on the context first..
targetDocContext._viewTransition = "transform 500ms";
- targetDocContextView.props.focus(targetDocContextView.props.Document, willZoom);
+ targetDocContextView.props.focus(targetDocContextView.rootDoc, willZoom);
// now find the target document within the context
if (targetDoc._timecodeToShow) { // if the target has a timecode, it should show up once the (presumed) video context scrubs to the display timecode;
@@ -198,12 +196,17 @@ export class DocumentManager {
const findView = (delay: number) => {
const retryDocView = getFirstDocView(targetDoc); // 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
- retryDocView.props.focus(targetDoc, willZoom, undefined, focusAndFinish); // focus on the target in the context
+ retryDocView.props.focus(targetDoc, willZoom, undefined, (didFocus: boolean) =>
+ new Promise<boolean>(res => {
+ focusAndFinish(didFocus);
+ res();
+ })
+ ); // focus on the target in the context
highlight();
} else if (delay > 1500) {
// 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.props.Document);
- if (targetDoc.layout) {
+ if (closeContextIfNotFound) targetDocContextView.props.removeDocument?.(targetDocContextView.rootDoc);
+ if (targetDoc.layout) { // there will no layout for a TEXTANCHOR type document
Doc.SetInPlace(targetDoc, "annotationOn", undefined, false);
createViewFunc(Doc.BrushDoc(targetDoc), finished); // create a new view of the target
}
@@ -214,8 +217,14 @@ export class DocumentManager {
findView(0);
}
} else { // 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, createViewFunc, docContext, linkDoc, true /* if we don't find the target, we want to get rid of the context just created */, undefined, finished));
+ const finishFunc = () => this.jumpToDocument(targetDoc, willZoom, createViewFunc, docContext, linkDoc, true /* if we don't find the target, we want to get rid of the context just created */, undefined, finished);
+ if (LightboxView.LightboxDoc) {
+ runInAction(() => LightboxView.LightboxDoc = targetDocContext);
+ setTimeout(() => finishFunc, 250);
+ } else {
+ createViewFunc(targetDocContext, // after creating the context, this calls the finish function that will retry looking for the target
+ finishFunc);
+ }
}
}
}
diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index bf927c5b8..58ccfe645 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -1,9 +1,9 @@
+import { runInAction } from "mobx";
import { computedFn } from "mobx-utils";
import { Doc, DocListCast, Opt } from "../../fields/Doc";
import { BoolCast, Cast, StrCast } from "../../fields/Types";
-import { DocFocusFunc, DocumentViewSharedProps } from "../views/nodes/DocumentView";
-import { FormattedTextBoxComment } from "../views/nodes/formattedText/FormattedTextBoxComment";
-import { LinkDocPreview } from "../views/nodes/LinkDocPreview";
+import { LightboxView } from "../views/LightboxView";
+import { DocumentViewSharedProps } from "../views/nodes/DocumentView";
import { CreateViewFunc, DocumentManager } from "./DocumentManager";
import { SharingManager } from "./SharingManager";
import { UndoManager } from "./UndoManager";
@@ -100,35 +100,37 @@ export class LinkManager {
// follows a link - if the target is on screen, it highlights/pans to it.
// if the target isn't onscreen, then it will open up the target in a tab, on the right, or in place
// depending on the followLinkLocation property of the source (or the link itself as a fallback);
- public static FollowLink = async (linkDoc: Opt<Doc>, sourceDoc: Doc, docViewProps: DocumentViewSharedProps, altKey: boolean) => {
+ public static FollowLink = (linkDoc: Opt<Doc>, sourceDoc: Doc, docViewProps: DocumentViewSharedProps, altKey: boolean) => {
const batch = UndoManager.StartBatch("follow link click");
// open up target if it's not already in view ...
- const createViewFunc = (doc: Doc, followLoc: string, finished: Opt<() => void>) => {
- const targetFocusAfterDocFocus = () => {
+ const createViewFunc = (doc: Doc, followLoc: string, finished?: Opt<() => void>) => {
+ const createTabForTarget = (didFocus: boolean) => new Promise<boolean>(res => {
const where = StrCast(sourceDoc.followLinkLocation) || followLoc;
- const hackToCallFinishAfterFocus = () => {
- finished && setTimeout(finished, 0); // finished() needs to be called right after hackToCallFinishAfterFocus(), but there's no callback for that so we use the hacky timeout.
- return false; // we must return false here so that the zoom to the document is not reversed. If it weren't for needing to call finished(), we wouldn't need this function at all since not having it is equivalent to returning false
- };
- const addTab = docViewProps.addDocTab(doc, where);
- addTab && setTimeout(() => {
+ docViewProps.addDocTab(doc, where);
+ setTimeout(() => {
const targDocView = DocumentManager.Instance.getFirstDocumentView(doc);
- targDocView?.props.focus(doc, BoolCast(sourceDoc.followLinkZoom, false), undefined, hackToCallFinishAfterFocus);
- }); // add the target and focus on it.
- return where !== "inPlace" || addTab; // return true to reset the initial focus&zoom (return false for 'inPlace' since resetting the initial focus&zoom will negate the zoom into the target)
- };
+ if (targDocView) {
+ targDocView.props.focus(doc, BoolCast(sourceDoc.followLinkZoom, false), undefined, (didFocus: boolean) => {
+ finished?.();
+ res(true);
+ return new Promise<boolean>(res2 => res2());
+ });
+ } else {
+ res(where !== "inPlace"); // return true to reset the initial focus&zoom (return false for 'inPlace' since resetting the initial focus&zoom will negate the zoom into the target)
+ }
+ });
+ });
+
if (!sourceDoc.followLinkZoom) {
- targetFocusAfterDocFocus();
+ createTabForTarget(false);
} else {
// first focus & zoom onto this (the clicked document). Then execute the function to focus on the target
- docViewProps.focus(sourceDoc, BoolCast(sourceDoc.followLinkZoom, true), 1, targetFocusAfterDocFocus);
+ docViewProps.focus(sourceDoc, BoolCast(sourceDoc.followLinkZoom, true), 1, createTabForTarget);
}
};
- await LinkManager.traverseLink(linkDoc, sourceDoc, createViewFunc, BoolCast(sourceDoc.followLinkZoom, false), docViewProps.ContainingCollectionDoc, batch.end, altKey ? true : undefined);
+ LinkManager.traverseLink(linkDoc, sourceDoc, createViewFunc, BoolCast(sourceDoc.followLinkZoom, false), docViewProps.ContainingCollectionDoc, batch.end, altKey ? true : undefined);
}
- public static async traverseLink(link: Opt<Doc>, doc: Doc, createViewFunc: CreateViewFunc, zoom = false, currentContext?: Doc, finished?: () => void, traverseBacklink?: boolean) {
- LinkDocPreview.TargetDoc = undefined;
- FormattedTextBoxComment.linkDoc = undefined;
+ public static traverseLink(link: Opt<Doc>, doc: Doc, createViewFunc: CreateViewFunc, zoom = false, currentContext?: Doc, finished?: () => void, traverseBacklink?: boolean) {
const linkDocs = link ? [link] : DocListCast(doc.links);
const firstDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor1 as Doc, doc) || Doc.AreProtosEqual((linkDoc.anchor1 as Doc).annotationOn as Doc, doc)); // link docs where 'doc' is anchor1
const secondDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor2 as Doc, doc) || Doc.AreProtosEqual((linkDoc.anchor2 as Doc).annotationOn as Doc, doc)); // link docs where 'doc' is anchor2
@@ -145,11 +147,16 @@ export class LinkManager {
doc === linkDoc.anchor2 ? Cast(linkDoc.anchor1_timecode, "number") :
(Doc.AreProtosEqual(doc, linkDoc.anchor1 as Doc) || Doc.AreProtosEqual((linkDoc.anchor1 as Doc).annotationOn as Doc, doc) ? Cast(linkDoc.anchor2_timecode, "number") : Cast(linkDoc.anchor1_timecode, "number")));
if (target) {
- const containerDoc = (await Cast(target.annotationOn, Doc)) || target;
- containerDoc._currentTimecode = targetTimecode;
- const targetContext = await target?.context as Doc;
- const targetNavContext = !Doc.AreProtosEqual(targetContext, currentContext) ? targetContext : undefined;
- DocumentManager.Instance.jumpToDocument(target, zoom, (doc, finished) => createViewFunc(doc, StrCast(linkDoc.followLinkLocation, "add:right"), finished), targetNavContext, linkDoc, undefined, doc, finished);
+ if (LightboxView.LightboxDoc && !DocumentManager.Instance.getLightboxDocumentView(doc)) {
+ runInAction(() => LightboxView.LightboxDoc = (target.annotationOn as Doc) ?? target);
+ finished?.();
+ } else {
+ const containerDoc = Cast(target.annotationOn, Doc, null) || target;
+ targetTimecode !== undefined && (containerDoc._currentTimecode = targetTimecode);
+ const targetContext = Cast(containerDoc?.context, Doc, null);
+ const targetNavContext = !Doc.AreProtosEqual(targetContext, currentContext) ? targetContext : undefined;
+ DocumentManager.Instance.jumpToDocument(target, zoom, (doc, finished) => createViewFunc(doc, StrCast(linkDoc.followLinkLocation, "add:right"), finished), targetNavContext, linkDoc, undefined, doc, finished);
+ }
} else {
finished?.();
}
diff --git a/src/client/views/AntimodeMenu.scss b/src/client/views/AntimodeMenu.scss
index be21cec12..6eeb83ba9 100644
--- a/src/client/views/AntimodeMenu.scss
+++ b/src/client/views/AntimodeMenu.scss
@@ -3,7 +3,7 @@
.antimodeMenu-cont {
position: absolute;
- z-index: 10000;
+ z-index: 10001;
height: $antimodemenu-height;
background: #323232;
box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.25);
diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss
index 22e120167..de8a3c909 100644
--- a/src/client/views/DocumentDecorations.scss
+++ b/src/client/views/DocumentDecorations.scss
@@ -4,6 +4,7 @@ $linkGap : 3px;
.documentDecorations {
position: absolute;
+ z-index: 2000;
}
.documentDecorations-container {
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 8d8f4cd3b..77b43db9b 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -23,6 +23,7 @@ import { DocumentButtonBar } from './DocumentButtonBar';
import './DocumentDecorations.scss';
import { KeyManager } from './GlobalKeyHandler';
import { InkStrokeProperties } from './InkStrokeProperties';
+import { LightboxView } from './LightboxView';
import { DocumentView } from "./nodes/DocumentView";
import React = require("react");
import e = require('express');
@@ -171,8 +172,13 @@ export class DocumentDecorations extends React.Component<{ boundsLeft: number, b
alias.x = -alias[WidthSym]() / 2;
alias.y = -alias[HeightSym]() / 2;
CollectionDockingView.AddSplit(Docs.Create.FreeformDocument([alias], { title: "Tab for " + alias.title }), "right");
- } else { // open same document in new tab
+ } else if (e.altKey) { // open same document in new tab
CollectionDockingView.ToggleSplit(Cast(selectedDocs[0].props.Document._fullScreenView, Doc, null) || selectedDocs[0].props.Document, "right");
+ } else {
+ runInAction(() => {
+ LightboxView.LightboxDoc = selectedDocs[0].props.Document;
+ LightboxView.LightboxFuture = selectedDocs.slice(1).map(view => view.props.Document);
+ });
}
}
}
@@ -569,10 +575,12 @@ export class DocumentDecorations extends React.Component<{ boundsLeft: number, b
<FontAwesomeIcon className="documentdecorations-times" icon={"times"} size="lg" />
</div></Tooltip>);
- const openIcon = !canOpen ? (null) : <Tooltip key="open" title={<div className="dash-tooltip">Open in Tab (ctrl: as alias, shift: in new collection)</div>} placement="top"><div className="documentDecorations-openInTab" onContextMenu={e => { e.preventDefault(); e.stopPropagation(); }} onPointerDown={this.onMaximizeDown}>
- {SelectionManager.Views().length === 1 ? <FontAwesomeIcon icon="external-link-alt" className="documentView-minimizedIcon" /> : "..."}
- </div>
- </Tooltip>;
+ const openIcon = !canOpen ? (null) :
+ <Tooltip key="open" title={<div className="dash-tooltip">Open in Tab (ctrl: as alias, shift: in new collection)</div>} placement="top">
+ <div className="documentDecorations-openInTab" onContextMenu={e => { e.preventDefault(); e.stopPropagation(); }} onPointerDown={this.onMaximizeDown}>
+ <FontAwesomeIcon icon="external-link-alt" className="documentView-minimizedIcon" />
+ </div>
+ </Tooltip>;
const titleArea = this._edtingTitle ?
<input ref={this._keyinput} className="documentDecorations-title" type="text" name="dynbox" autoComplete="on" value={this._accumulatedTitle}
@@ -601,7 +609,7 @@ export class DocumentDecorations extends React.Component<{ boundsLeft: number, b
left: bounds.x - this._resizeBorderWidth / 2,
top: bounds.y - this._resizeBorderWidth / 2,
pointerEvents: KeyManager.Instance.ShiftPressed || this.Interacting ? "none" : "all",
- zIndex: SelectionManager.Views().length > 1 ? 900 : 0,
+ display: SelectionManager.Views().length <= 1 ? "none" : undefined
}} onPointerDown={this.onBackgroundDown} onContextMenu={e => { e.preventDefault(); e.stopPropagation(); }} >
</div>
{bounds.r - bounds.x < 15 && bounds.b - bounds.y < 15 ? (null) : <>
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 524462401..d7e7e055f 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -8,7 +8,7 @@ import { Cast, FieldValue, NumCast } from "../../fields/Types";
import MobileInkOverlay from "../../mobile/MobileInkOverlay";
import { GestureUtils } from "../../pen-gestures/GestureUtils";
import { MobileInkOverlayContent } from "../../server/Message";
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnTrue, setupMoveUpEvents } from "../../Utils";
+import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnTrue, setupMoveUpEvents, emptyPath } from "../../Utils";
import { CognitiveServices } from "../cognitive_services/CognitiveServices";
import { DocUtils } from "../documents/Documents";
import { CurrentUserUtils } from "../util/CurrentUserUtils";
@@ -32,10 +32,6 @@ export class GestureOverlay extends Touchable {
@observable public InkShape: string = "";
@observable public SavedColor?: string;
@observable public SavedWidth?: string;
- @observable public SavedFill?: string;
- @observable public SavedArrowStart: string = "none";
- @observable public SavedArrowEnd: string = "none";
- @observable public SavedDash: String = "0";
@observable public Tool: ToolglassTools = ToolglassTools.None;
@observable private _thumbX?: number;
@@ -520,7 +516,7 @@ export class GestureOverlay extends Touchable {
}
handleLineGesture = (): boolean => {
- let actionPerformed = false;
+ const actionPerformed = false;
const B = this.svgBounds;
// get the two targets at the ends of the line
@@ -529,21 +525,6 @@ export class GestureOverlay extends Touchable {
const target1 = document.elementFromPoint(ep1.X, ep1.Y);
const target2 = document.elementFromPoint(ep2.X, ep2.Y);
- // callback function to be called by each target
- const callback = (doc: Doc) => {
- if (!this._d1) {
- this._d1 = doc;
- }
- // we don't want to create a link of both endpoints are the same document (doing so makes drawing an l very hard)
- else if (this._d1 !== doc && !LinkManager.Instance.doesLinkExist(this._d1, doc)) {
- // we don't want to create a link between ink strokes (doing so makes drawing a t very hard)
- if (this._d1.type !== "ink" && doc.type !== "ink") {
- DocUtils.MakeLink({ doc: this._d1 }, { doc: doc }, "gestural link", "");
- actionPerformed = true;
- }
- }
- };
-
const ge = new CustomEvent<GestureUtils.GestureEvent>("dashOnGesture",
{
bubbles: true,
@@ -607,7 +588,7 @@ export class GestureOverlay extends Touchable {
this.makePolygon(this.InkShape, false);
this.dispatchGesture(GestureUtils.Gestures.Stroke);
this._points = [];
- if (!CollectionFreeFormViewChrome.Instance._keepMode) {
+ if (!CollectionFreeFormViewChrome.Instance._keepPrimitiveMode) {
this.InkShape = "";
}
}
@@ -653,15 +634,7 @@ export class GestureOverlay extends Touchable {
} else {
this._points = [];
}
- //get out of ink mode after each stroke=
- if (CollectionFreeFormViewChrome.Instance && !CollectionFreeFormViewChrome.Instance?._keepMode) {
- Doc.SetSelectedTool(InkTool.None);
- CollectionFreeFormViewChrome.Instance._selected = CollectionFreeFormViewChrome.Instance._shapesNum;
- SetActiveArrowStart("none");
- GestureOverlay.Instance.SavedArrowStart = ActiveArrowStart();
- SetActiveArrowEnd("none");
- GestureOverlay.Instance.SavedArrowEnd = ActiveArrowEnd();
- }
+ CollectionFreeFormViewChrome.Instance.primCreated();
}
makePolygon = (shape: string, gesture: boolean) => {
@@ -898,6 +871,8 @@ export class GestureOverlay extends Touchable {
PanelHeight={this.return300}
renderDepth={0}
styleProvider={returnEmptyString}
+ layerProvider={undefined}
+ docViewPath={returnEmptyDoclist}
focus={emptyFunction}
parentActive={returnTrue}
whenActiveChanged={emptyFunction}
@@ -969,13 +944,9 @@ Scripting.addGlobal(function setPen(width: any, color: any, fill: any, arrowStar
SetActiveInkColor(color);
GestureOverlay.Instance.SavedWidth = ActiveInkWidth();
SetActiveInkWidth(width);
- GestureOverlay.Instance.SavedFill = ActiveFillColor();
SetActiveFillColor(fill);
- GestureOverlay.Instance.SavedArrowStart = ActiveArrowStart();
SetActiveArrowStart(arrowStart);
- GestureOverlay.Instance.SavedArrowEnd = ActiveArrowEnd();
SetActiveArrowStart(arrowEnd);
- GestureOverlay.Instance.SavedDash = ActiveDash();
SetActiveDash(dash);
});
});
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index 3f34c0f20..3c855fb0e 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -19,12 +19,13 @@ import { SnappingManager } from "../util/SnappingManager";
import { undoBatch, UndoManager } from "../util/UndoManager";
import { CollectionDockingView } from "./collections/CollectionDockingView";
import { CollectionFreeFormViewChrome } from "./collections/CollectionMenu";
+import { CollectionStackedTimeline } from "./collections/CollectionStackedTimeline";
import { ContextMenu } from "./ContextMenu";
import { DocumentDecorations } from "./DocumentDecorations";
import { InkStrokeProperties } from "./InkStrokeProperties";
+import { LightboxView } from "./LightboxView";
import { MainView } from "./MainView";
import { DocumentLinksButton } from "./nodes/DocumentLinksButton";
-import { CollectionStackedTimeline } from "./collections/CollectionStackedTimeline";
import { AnchorMenu } from "./pdf/AnchorMenu";
import { SearchBox } from "./search/SearchBox";
import { random } from "lodash";
@@ -131,13 +132,16 @@ export class KeyManager {
} else {
doDeselect = !ContextMenu.Instance.closeMenu();
}
- doDeselect && SelectionManager.DeselectAll();
+ if (doDeselect) {
+ SelectionManager.DeselectAll();
+ LightboxView.LightboxDoc = undefined;
+ }
DictationManager.Controls.stop();
GoogleAuthenticationManager.Instance.cancel();
SharingManager.Instance.close();
if (!GroupManager.Instance.isOpen) SettingsManager.Instance.close();
GroupManager.Instance.close();
- CollectionFreeFormViewChrome.Instance?.clearKeep();
+ CollectionFreeFormViewChrome.Instance?.clearKeepPrimitiveMode();
window.getSelection()?.empty();
document.body.focus();
break;
diff --git a/src/client/views/LightboxView.scss b/src/client/views/LightboxView.scss
new file mode 100644
index 000000000..35f11b699
--- /dev/null
+++ b/src/client/views/LightboxView.scss
@@ -0,0 +1,26 @@
+.lightboxView-frame {
+ position: absolute;
+ top: 0; left: 0;
+ width: 100%;
+ height: 100%;
+ background: #000000bb;
+ z-index: 1000;
+ .lightboxView-contents {
+ position: absolute;
+ }
+ .lightboxView-navBtn-frame {
+ position: absolute;
+ .lightboxView-navBtn {
+ margin: auto;
+ position: relative;
+ background: transparent;
+ border-radius: 8;
+ color:white;
+ opacity: 0.7;
+ width: 30;
+ &:hover {
+ opacity: 1;
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx
new file mode 100644
index 000000000..4e9491ec6
--- /dev/null
+++ b/src/client/views/LightboxView.tsx
@@ -0,0 +1,105 @@
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { action, observable, computed } from 'mobx';
+import { observer } from 'mobx-react';
+import "normalize.css";
+import * as React from 'react';
+import { Doc, Opt } from '../../fields/Doc';
+import { emptyFunction, emptyPath, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from '../../Utils';
+import { Transform } from '../util/Transform';
+import "./LightboxView.scss";
+import { DocumentView } from './nodes/DocumentView';
+import { DefaultStyleProvider } from './StyleProvider';
+
+interface LightboxViewProps {
+ PanelWidth: number;
+ PanelHeight: number;
+ maxBorder: number[];
+}
+
+@observer
+export class LightboxView extends React.Component<LightboxViewProps> {
+ @observable public static LightboxDoc: Opt<Doc>;
+ public static IsLightboxDocView(path: DocumentView[]) { return path.includes(LightboxView.LightboxDocView.current!); }
+ public static LightboxHistory: (Opt<Doc>)[] = [];
+ public static LightboxFuture: (Opt<Doc>)[] = [];
+ public static LightboxDocView = React.createRef<DocumentView>();
+ @computed get leftBorder() { return Math.min(this.props.PanelWidth / 4, this.props.maxBorder[0]); }
+ @computed get topBorder() { return Math.min(this.props.PanelHeight / 4, this.props.maxBorder[1]); }
+ lightboxWidth = () => this.props.PanelWidth - this.leftBorder * 2;
+ lightboxHeight = () => this.props.PanelHeight - this.topBorder * 2;
+ lightboxScreenToLocal = () => new Transform(-this.leftBorder, -this.topBorder, 1);
+ navBtn = (left: Opt<number>, icon: string, display: () => string, click: (e: React.MouseEvent) => void) => {
+ return <div className="lightboxView-navBtn-frame" style={{
+ display: display(),
+ left,
+ width: Math.min(this.props.PanelWidth / 4, this.props.maxBorder[0])
+ }}>
+ <div className="lightboxView-navBtn" style={{ top: this.props.PanelHeight / 2 - 12.50 }}
+ onClick={click}>
+ <FontAwesomeIcon icon={icon as any} size="3x" />
+ </div>
+ </div>;
+ }
+
+ render() {
+ if (LightboxView.LightboxHistory.lastElement() !== LightboxView.LightboxDoc) LightboxView.LightboxHistory.push(LightboxView.LightboxDoc);
+ let downx = 0, downy = 0;
+ return !LightboxView.LightboxDoc ? (null) :
+ <div className="lightboxView-frame"
+ onPointerDown={e => { downx = e.clientX; downy = e.clientY; }}
+ onClick={action(e => {
+ if (Math.abs(downx - e.clientX) < 4 && Math.abs(downy - e.clientY) < 4) {
+ LightboxView.LightboxHistory = [];
+ LightboxView.LightboxFuture = [];
+ LightboxView.LightboxDoc = undefined;
+ }
+ })} >
+ <div className="lightboxView-contents" style={{
+ left: this.leftBorder,
+ top: this.topBorder,
+ width: this.lightboxWidth(),
+ height: this.lightboxHeight()
+ }}>
+ <DocumentView ref={LightboxView.LightboxDocView}
+ Document={LightboxView.LightboxDoc}
+ DataDoc={undefined}
+ addDocument={undefined}
+ addDocTab={returnFalse}
+ pinToPres={emptyFunction}
+ rootSelected={returnTrue}
+ docViewPath={returnEmptyDoclist}
+ removeDocument={undefined}
+ styleProvider={DefaultStyleProvider}
+ layerProvider={returnTrue}
+ ScreenToLocalTransform={this.lightboxScreenToLocal}
+ PanelWidth={this.lightboxWidth}
+ PanelHeight={this.lightboxHeight}
+ focus={emptyFunction}
+ parentActive={returnTrue}
+ whenActiveChanged={emptyFunction}
+ bringToFront={emptyFunction}
+ docFilters={returnEmptyFilter}
+ docRangeFilters={returnEmptyFilter}
+ searchFilterDocs={returnEmptyDoclist}
+ ContainingCollectionView={undefined}
+ ContainingCollectionDoc={undefined}
+ renderDepth={0} />
+ </div>
+ {this.navBtn(undefined, "chevron-left",
+ () => LightboxView.LightboxDoc && LightboxView.LightboxHistory.length ? "" : "none",
+ action(e => {
+ e.stopPropagation();
+ const popped = LightboxView.LightboxHistory.pop();
+ if (LightboxView.LightboxHistory.lastElement() !== LightboxView.LightboxFuture.lastElement()) LightboxView.LightboxFuture.push(popped);
+ LightboxView.LightboxDoc = LightboxView.LightboxHistory.lastElement();
+ }))}
+ {this.navBtn(this.props.PanelWidth - Math.min(this.props.PanelWidth / 4, this.props.maxBorder[0]), "chevron-right",
+ () => LightboxView.LightboxDoc && LightboxView.LightboxFuture.length ? "" : "none",
+ action(e => {
+ e.stopPropagation();
+ LightboxView.LightboxDoc = LightboxView.LightboxFuture.pop();
+ }))}
+
+ </div>;
+ }
+} \ No newline at end of file
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index a0161b81e..729f2aa89 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -13,7 +13,7 @@ import { List } from '../../fields/List';
import { PrefetchProxy } from '../../fields/Proxy';
import { BoolCast, PromiseValue, StrCast } from '../../fields/Types';
import { TraceMobx } from '../../fields/util';
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, setupMoveUpEvents, simulateMouseClick, Utils } from '../../Utils';
+import { emptyFunction, emptyPath, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, setupMoveUpEvents, simulateMouseClick, Utils } from '../../Utils';
import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager';
import { DocServer } from '../DocServer';
import { Docs } from '../documents/Documents';
@@ -42,6 +42,7 @@ import { GestureOverlay } from './GestureOverlay';
import { MENU_PANEL_WIDTH, SEARCH_PANEL_HEIGHT } from './globalCssVariables.scss';
import { KeyManager } from './GlobalKeyHandler';
import { InkStrokeProperties } from './InkStrokeProperties';
+import { LightboxView } from './LightboxView';
import { LinkMenu } from './linking/LinkMenu';
import "./MainView.scss";
import "./collections/TreeView.scss";
@@ -67,8 +68,9 @@ const _global = (window /* browser */ || global /* node */) as any;
export class MainView extends React.Component {
public static Instance: MainView;
private _docBtnRef = React.createRef<HTMLDivElement>();
- private _mainViewRef = React.createRef<HTMLDivElement>();
@observable public LastButton: Opt<Doc>;
+ @observable private _windowWidth: number = 0;
+ @observable private _windowHeight: number = 0;
@observable private _panelWidth: number = 0;
@observable private _panelHeight: number = 0;
@observable private _panelContent: string = "none";
@@ -100,7 +102,7 @@ export class MainView extends React.Component {
}
new InkStrokeProperties();
this._sidebarContent.proto = undefined;
- DocServer.setPlaygroundFields(["x", "y", "dataTransition", "_delayAutoHeight", "_autoHeight", "_showSidebar", "_sidebarWidthPercent", "_width", "_height", "_viewTransition", "_panX", "_panY", "_viewScale", "_scrollY", "_scrollTop", "hidden", "_curPage", "_viewType", "_chromeStatus"]); // can play with these fields on someone else's
+ DocServer.setPlaygroundFields(["x", "y", "dataTransition", "_delayAutoHeight", "_autoHeight", "_showSidebar", "_sidebarWidthPercent", "_width", "_height", "_viewTransition", "_panX", "_panY", "_viewScale", "_scrollTop", "hidden", "_curPage", "_viewType", "_chromeStatus"]); // can play with these fields on someone else's
DocServer.GetRefField("rtfProto").then(proto => (proto instanceof Doc) && reaction(() => StrCast(proto.BROADCAST_MESSAGE), msg => msg && alert(msg)));
@@ -177,8 +179,8 @@ export class MainView extends React.Component {
const targClass = targets[0].className.toString();
if (SearchBox.Instance._searchbarOpen || SearchBox.Instance.open) {
const check = targets.some((thing) =>
- (thing.className === "collectionSchemaView-searchContainer" || (thing as any)?.dataset.icon === "filter" ||
- thing.className === "collectionSchema-header-menuOptions"));
+ (thing.className === "collectionSchemaView-searchContainer" || (thing as any)?.dataset.icon === "filter" ||
+ thing.className === "collectionSchema-header-menuOptions"));
!check && SearchBox.Instance.resetSearch(true);
}
!targClass.includes("contextMenu") && ContextMenu.Instance.closeMenu();
@@ -249,6 +251,9 @@ export class MainView extends React.Component {
addDocument={undefined}
addDocTab={this.addDocTabFunc}
pinToPres={emptyFunction}
+ docViewPath={returnEmptyDoclist}
+ layerProvider={undefined}
+ styleProvider={undefined}
rootSelected={returnTrue}
removeDocument={undefined}
ScreenToLocalTransform={Transform.Identity}
@@ -337,6 +342,9 @@ export class MainView extends React.Component {
addDocument={undefined}
addDocTab={this.addDocTabFunc}
pinToPres={emptyFunction}
+ docViewPath={returnEmptyDoclist}
+ layerProvider={undefined}
+ styleProvider={this._sidebarContent.proto === Doc.UserDoc().myDashboards ? this.DashboardStyleProvider : DefaultStyleProvider}
rootSelected={returnTrue}
removeDocument={returnFalse}
ScreenToLocalTransform={this.mainContainerXf}
@@ -345,7 +353,6 @@ export class MainView extends React.Component {
renderDepth={0}
scriptContext={CollectionDockingView.Instance.props.Document}
focus={emptyFunction}
- styleProvider={this._sidebarContent.title === "My Dashboards" ? this.DashboardStyleProvider : DefaultStyleProvider}
parentActive={returnTrue}
whenActiveChanged={emptyFunction}
bringToFront={emptyFunction}
@@ -374,8 +381,10 @@ export class MainView extends React.Component {
PanelWidth={this.menuPanelWidth}
PanelHeight={this.getContentsHeight}
renderDepth={0}
+ docViewPath={returnEmptyDoclist}
focus={emptyFunction}
styleProvider={DefaultStyleProvider}
+ layerProvider={undefined}
parentActive={returnTrue}
whenActiveChanged={emptyFunction}
bringToFront={emptyFunction}
@@ -477,11 +486,13 @@ export class MainView extends React.Component {
dropAction={"alias"}
parentActive={returnFalse}
styleProvider={DefaultStyleProvider}
+ layerProvider={undefined}
rootSelected={returnTrue}
bringToFront={emptyFunction}
select={emptyFunction}
active={returnFalse}
isSelected={returnFalse}
+ docViewPath={returnEmptyDoclist}
moveDocument={this.moveButtonDoc}
CollectionView={undefined}
addDocument={this.addButtonDoc}
@@ -548,12 +559,14 @@ export class MainView extends React.Component {
pinToPres={emptyFunction}
rootSelected={returnTrue}
styleProvider={DefaultStyleProvider}
+ layerProvider={undefined}
removeDocument={undefined}
ScreenToLocalTransform={Transform.Identity}
PanelWidth={this.getPWidth}
PanelHeight={this.getPHeight}
renderDepth={0}
focus={emptyFunction}
+ docViewPath={returnEmptyDoclist}
parentActive={returnFalse}
whenActiveChanged={emptyFunction}
bringToFront={emptyFunction}
@@ -574,6 +587,8 @@ export class MainView extends React.Component {
ContainingCollectionDoc={undefined}
Document={DocumentLinksButton.invisibleWebDoc}
dropAction={"move"}
+ layerProvider={undefined}
+ styleProvider={undefined}
isSelected={returnFalse}
select={returnFalse}
rootSelected={returnFalse}
@@ -586,6 +601,7 @@ export class MainView extends React.Component {
active={returnFalse}
whenActiveChanged={returnFalse}
focus={returnFalse}
+ docViewPath={returnEmptyDoclist}
PanelWidth={() => 500}
PanelHeight={() => 800}
docFilters={returnEmptyFilter}
@@ -596,7 +612,11 @@ export class MainView extends React.Component {
}
render() {
- return (<div className={"mainView-container" + (this.darkScheme ? "-dark" : "")} onScroll={() => ((ele) => ele.scrollTop = ele.scrollLeft = 0)(document.getElementById("root")!)} ref={this._mainViewRef}>
+ return (<div className={"mainView-container" + (this.darkScheme ? "-dark" : "")}
+ onScroll={() => ((ele) => ele.scrollTop = ele.scrollLeft = 0)(document.getElementById("root")!)}
+ ref={r => {
+ r && new _global.ResizeObserver(action(() => { this._windowWidth = r.getBoundingClientRect().width; this._windowHeight = r.getBoundingClientRect().height; })).observe(r);
+ }}>
{this.inkResources}
<DictationOverlay />
<SharingManager />
@@ -607,9 +627,8 @@ export class MainView extends React.Component {
{this.search}
<CollectionMenu />
{LinkDescriptionPopup.descriptionPopup ? <LinkDescriptionPopup /> : null}
- {DocumentLinksButton.EditLink ? <LinkMenu docView={DocumentLinksButton.EditLink} docprops={DocumentLinksButton.EditLink.props} changeFlyout={emptyFunction} /> : (null)}
- {LinkDocPreview.LinkInfo ? <LinkDocPreview location={LinkDocPreview.LinkInfo.Location} docprops={LinkDocPreview.LinkInfo.docprops}
- linkDoc={LinkDocPreview.LinkInfo.linkDoc} linkSrc={LinkDocPreview.LinkInfo.linkSrc} href={LinkDocPreview.LinkInfo.href} /> : (null)}
+ {DocumentLinksButton.LinkEditorDocView ? <LinkMenu docView={DocumentLinksButton.LinkEditorDocView} changeFlyout={emptyFunction} /> : (null)}
+ {LinkDocPreview.LinkInfo ? <LinkDocPreview {...LinkDocPreview.LinkInfo} /> : (null)}
<GestureOverlay >
{this.mainContent}
</GestureOverlay>
@@ -623,7 +642,8 @@ export class MainView extends React.Component {
<TimelineMenu />
{this.snapLines}
<div className="mainView-webRef" ref={this.makeWebRef} />
- </div >);
+ <LightboxView PanelWidth={this._windowWidth} PanelHeight={this._windowHeight} maxBorder={[200, 50]} />
+ </div>);
}
makeWebRef = (ele: HTMLDivElement) => {
@@ -639,9 +659,12 @@ export class MainView extends React.Component {
Document={invisibleDoc}
dropAction={"move"}
isSelected={returnFalse}
+ docViewPath={returnEmptyDoclist}
select={returnFalse}
rootSelected={returnFalse}
renderDepth={0}
+ layerProvider={undefined}
+ styleProvider={undefined}
addDocTab={returnFalse}
pinToPres={returnFalse}
ScreenToLocalTransform={Transform.Identity}
diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx
index 4ffccbc90..03afbe7bf 100644
--- a/src/client/views/MarqueeAnnotator.tsx
+++ b/src/client/views/MarqueeAnnotator.tsx
@@ -20,6 +20,7 @@ const _global = (window /* browser */ || global /* node */) as any;
export interface MarqueeAnnotatorProps {
rootDoc: Doc;
down: number[];
+ scrollTop: number;
scaling?: () => number;
containerOffset?: () => number[];
mainCont: HTMLDivElement;
@@ -73,7 +74,7 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
const containerOffset = this.props.containerOffset?.() || [0, 0];
const mainAnnoDoc = Docs.Create.FreeformDocument([], { backgroundColor: color, annotationOn: this.props.rootDoc, title: "Annotation on " + this.props.rootDoc.title });
if (anno.style.left) mainAnnoDoc.x = (parseInt(anno.style.left) - containerOffset[0]) / scale;
- if (anno.style.top) mainAnnoDoc.y = (parseInt(anno.style.top) - containerOffset[1] + NumCast(this.props.rootDoc._scrollTop)) / scale;
+ if (anno.style.top) mainAnnoDoc.y = (parseInt(anno.style.top) - containerOffset[1]) / scale + NumCast(this.props.scrollTop);
if (anno.style.height) mainAnnoDoc._height = parseInt(anno.style.height) / scale;
if (anno.style.width) mainAnnoDoc._width = parseInt(anno.style.width) / scale;
mainAnnoDoc.group = mainAnnoDoc;
diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx
index 78053be92..64f907f4c 100644
--- a/src/client/views/OverlayView.tsx
+++ b/src/client/views/OverlayView.tsx
@@ -5,7 +5,7 @@ import ReactLoading from 'react-loading';
import { Doc } from "../../fields/Doc";
import { Id } from "../../fields/FieldSymbols";
import { Cast, NumCast } from "../../fields/Types";
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnOne, returnTrue, setupMoveUpEvents, Utils } from "../../Utils";
+import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnOne, returnTrue, setupMoveUpEvents, Utils, emptyPath } from "../../Utils";
import { CurrentUserUtils } from "../util/CurrentUserUtils";
import { DragManager } from "../util/DragManager";
import { Scripting } from "../util/Scripting";
@@ -193,6 +193,8 @@ export class OverlayView extends React.Component {
whenActiveChanged={emptyFunction}
focus={emptyFunction}
styleProvider={DefaultStyleProvider}
+ layerProvider={undefined}
+ docViewPath={returnEmptyDoclist}
addDocTab={returnFalse}
pinToPres={emptyFunction}
docFilters={returnEmptyFilter}
diff --git a/src/client/views/Palette.tsx b/src/client/views/Palette.tsx
index d181c7651..fbf67f0a0 100644
--- a/src/client/views/Palette.tsx
+++ b/src/client/views/Palette.tsx
@@ -3,7 +3,7 @@ import { observer } from "mobx-react";
import * as React from "react";
import { Doc } from "../../fields/Doc";
import { NumCast } from "../../fields/Types";
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnTrue } from "../../Utils";
+import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnTrue, emptyPath } from "../../Utils";
import { Transform } from "../util/Transform";
import { DocumentView } from "./nodes/DocumentView";
import "./Palette.scss";
@@ -50,7 +50,9 @@ export default class Palette extends React.Component<PaletteProps> {
PanelHeight={() => window.screen.height}
renderDepth={0}
focus={emptyFunction}
+ docViewPath={returnEmptyDoclist}
styleProvider={returnEmptyString}
+ layerProvider={undefined}
parentActive={returnTrue}
whenActiveChanged={emptyFunction}
bringToFront={emptyFunction}
diff --git a/src/client/views/PreviewCursor.scss b/src/client/views/PreviewCursor.scss
index d384fd284..de9bd69c4 100644
--- a/src/client/views/PreviewCursor.scss
+++ b/src/client/views/PreviewCursor.scss
@@ -7,4 +7,5 @@
left:0;
pointer-events: none;
opacity: 1;
+ z-index: 1001;
} \ No newline at end of file
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index e95649444..ae6c7c50c 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -275,6 +275,8 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
renderDepth={1}
rootSelected={returnFalse}
styleProvider={DefaultStyleProvider}
+ layerProvider={undefined}
+ docViewPath={returnEmptyDoclist}
freezeDimensions={true}
dontCenter={"y"}
NativeWidth={layoutDoc.type === DocumentType.RTF ? this.rtfWidth : undefined}
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 8939eac3e..e94cc71ba 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -12,7 +12,7 @@ import { SnappingManager } from '../util/SnappingManager';
import { UndoManager } from '../util/UndoManager';
import { CollectionViewType } from './collections/CollectionView';
import { MainView } from './MainView';
-import { DocumentViewProps } from "./nodes/DocumentView";
+import { DocumentViewProps, DocumentView } from "./nodes/DocumentView";
import { FieldViewProps } from './nodes/FieldView';
import "./StyleProvider.scss";
import "./collections/TreeView.scss";
@@ -117,6 +117,8 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps |
case DocumentType.LABEL: docColor = docColor || (doc.annotationOn !== undefined ? "rgba(128, 128, 128, 0.18)" : undefined); break;
case DocumentType.BUTTON: docColor = docColor || (darkScheme() ? "#2d2d2d" : "lightgray"); break;
case DocumentType.LINK: return "transparent";
+ case DocumentType.WEB:
+ case DocumentType.PDF:
case DocumentType.VID: docColor = docColor || (darkScheme() ? "#2d2d2d" : "lightgray"); break;
case DocumentType.COL:
if (StrCast(Doc.LayoutField(doc)).includes("SliderBox")) break;
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index 841e8aef9..cdaf1bd0c 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -5,13 +5,14 @@ import { List } from "../../fields/List";
import { ScriptField } from "../../fields/ScriptField";
import { Cast, StrCast } from "../../fields/Types";
import { TraceMobx } from "../../fields/util";
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from "../../Utils";
+import { emptyFunction, emptyPath, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from "../../Utils";
import { Docs, DocUtils } from "../documents/Documents";
import { Scripting } from "../util/Scripting";
import { Transform } from "../util/Transform";
import { undoBatch } from "../util/UndoManager";
import { CollectionTreeView } from "./collections/CollectionTreeView";
import { DocumentView } from "./nodes/DocumentView";
+import { DefaultStyleProvider } from "./StyleProvider";
import './TemplateMenu.scss';
import React = require("react");
@@ -130,6 +131,9 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
CollectionView={undefined}
ContainingCollectionDoc={undefined}
ContainingCollectionView={undefined}
+ styleProvider={DefaultStyleProvider}
+ layerProvider={undefined}
+ docViewPath={returnEmptyDoclist}
docFilters={returnEmptyFilter}
docRangeFilters={returnEmptyFilter}
searchFilterDocs={returnEmptyDoclist}
diff --git a/src/client/views/collections/CollectionLinearView.tsx b/src/client/views/collections/CollectionLinearView.tsx
index 756346356..ead0ce6c4 100644
--- a/src/client/views/collections/CollectionLinearView.tsx
+++ b/src/client/views/collections/CollectionLinearView.tsx
@@ -7,7 +7,7 @@ import { documentSchema } from '../../../fields/documentSchemas';
import { Id } from '../../../fields/FieldSymbols';
import { makeInterface } from '../../../fields/Schema';
import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
-import { emptyFunction, returnTrue, Utils } from '../../../Utils';
+import { emptyFunction, returnTrue, Utils, emptyPath, returnEmptyDoclist } from '../../../Utils';
import { DragManager } from '../../util/DragManager';
import { Transform } from '../../util/Transform';
import { DocumentLinksButton } from '../nodes/DocumentLinksButton';
@@ -152,6 +152,8 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
renderDepth={this.props.renderDepth + 1}
focus={emptyFunction}
styleProvider={this.props.styleProvider}
+ layerProvider={this.props.layerProvider}
+ docViewPath={returnEmptyDoclist}
parentActive={returnTrue}
whenActiveChanged={emptyFunction}
bringToFront={emptyFunction}
diff --git a/src/client/views/collections/CollectionMenu.scss b/src/client/views/collections/CollectionMenu.scss
index e36e5caa7..47adb6a1c 100644
--- a/src/client/views/collections/CollectionMenu.scss
+++ b/src/client/views/collections/CollectionMenu.scss
@@ -6,7 +6,7 @@
display: inline-flex;
width: 100%;
opacity: 0.9;
- z-index: 9001;
+ z-index: 901;
transition: top .5s;
background: #323232;
color: white;
@@ -30,7 +30,7 @@
height: 32px;
border-bottom: .5px solid rgb(180, 180, 180);
overflow: visible;
- z-index: 9001;
+ z-index: 901;
border: unset;
.collectionViewBaseChrome {
@@ -315,10 +315,17 @@
}
}
+.collectionMenu-webUrlButtons {
+ margin-left: 44;
+ background: lightGray;
+ width: 100%;
+ display: flex;
+}
+
.webBox-urlEditor {
position: relative;
opacity: 0.9;
- z-index: 9001;
+ z-index: 901;
transition: top .5s;
.urlEditor {
@@ -354,7 +361,7 @@
}
}
-.webpage-urlInput {
+.collectionMenu-urlInput {
padding: 12px 10px 11px 10px;
border: 0px;
color: black;
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index 5670d45f5..d6e4b01c4 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -5,7 +5,7 @@ import { Tooltip } from "@material-ui/core";
import { action, computed, Lambda, observable, reaction, runInAction } from "mobx";
import { observer } from "mobx-react";
import { ColorState } from "react-color";
-import { Doc, DocListCast, Field, Opt } from "../../../fields/Doc";
+import { Doc, DocListCast, Opt } from "../../../fields/Doc";
import { Document } from "../../../fields/documentSchemas";
import { Id } from "../../../fields/FieldSymbols";
import { InkTool } from "../../../fields/InkField";
@@ -15,7 +15,6 @@ import { RichTextField } from "../../../fields/RichTextField";
import { listSpec } from "../../../fields/Schema";
import { ScriptField } from "../../../fields/ScriptField";
import { BoolCast, Cast, NumCast, StrCast } from "../../../fields/Types";
-import { WebField } from "../../../fields/URLField";
import { emptyFunction, setupMoveUpEvents, Utils } from "../../../Utils";
import { DocumentType } from "../../documents/DocumentTypes";
import { CurrentUserUtils } from "../../util/CurrentUserUtils";
@@ -26,7 +25,7 @@ import { undoBatch } from "../../util/UndoManager";
import { AntimodeMenu, AntimodeMenuProps } from "../AntimodeMenu";
import { EditableView } from "../EditableView";
import { GestureOverlay } from "../GestureOverlay";
-import { ActiveFillColor, ActiveInkColor, SetActiveArrowEnd, SetActiveArrowStart, SetActiveBezierApprox, SetActiveFillColor, SetActiveInkColor, SetActiveInkWidth } from "../InkingStroke";
+import { ActiveFillColor, ActiveInkColor, SetActiveArrowEnd, SetActiveArrowStart, SetActiveBezierApprox, SetActiveFillColor, SetActiveInkColor, SetActiveInkWidth, ActiveArrowStart, ActiveArrowEnd } from "../InkingStroke";
import { CollectionFreeFormDocumentView } from "../nodes/CollectionFreeFormDocumentView";
import { DocumentView } from "../nodes/DocumentView";
import { RichTextMenu } from "../nodes/formattedText/RichTextMenu";
@@ -34,6 +33,7 @@ import { PresBox } from "../nodes/PresBox";
import "./CollectionMenu.scss";
import { CollectionViewType, COLLECTION_BORDER_WIDTH } from "./CollectionView";
import { TabDocView } from "./TabDocView";
+import { LightboxView } from "../LightboxView";
@observer
export class CollectionMenu extends AntimodeMenu<AntimodeMenuProps> {
@@ -488,8 +488,14 @@ export class CollectionViewBaseChrome extends React.Component<CollectionMenuProp
@computed get lightboxButton() {
const targetDoc = this.selectedDoc;
- return !targetDoc ? (null) : <Tooltip title={<div className="dash-tooltip">{"Show Lightbox of Images"}</div>} placement="top">
- <button className="antimodeMenu-button" onPointerDown={action(() => targetDoc._isLightboxOpen = true)}>
+ return !targetDoc ? (null) : <Tooltip title={<div className="dash-tooltip">{"Show Lightbox of Documents"}</div>} placement="top">
+ <button className="antimodeMenu-button" onPointerDown={action(() => {
+ const docs = DocListCast(targetDoc[Doc.LayoutFieldKey(targetDoc)]);
+ if (docs.length) {
+ LightboxView.LightboxDoc = docs[0];
+ LightboxView.LightboxFuture = docs.slice(1);
+ }
+ })}>
<FontAwesomeIcon className="documentdecorations-icon" icon="desktop" size="lg" />
</button>
</Tooltip>;
@@ -549,17 +555,13 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu
@computed get dataField() {
return this.document[this.props.docView.LayoutFieldKey + (this.props.isOverlay ? "-annotations" : "")];
}
- @computed get childDocs() {
- return DocListCast(this.dataField);
- }
-
- @computed get selectedDocumentView() {
- return SelectionManager.Views().length ? SelectionManager.Views()[0] : undefined;
- }
+ @computed get childDocs() { return DocListCast(this.dataField); }
+ @computed get selectedDocumentView() { return SelectionManager.Views().length ? SelectionManager.Views()[0] : undefined; }
@computed get selectedDoc() { return this.selectedDocumentView?.rootDoc; }
@computed get isText() {
return this.selectedDoc?.type === DocumentType.RTF || (RichTextMenu.Instance?.view as any) ? true : false;
}
+ @computed get webBoxUrl() { return this.selectedDocumentView?.ComponentView?.url?.(); }
@undoBatch
@action
@@ -591,20 +593,24 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu
private _draw = ["∿", "⎯", "→", "↔︎", "ロ", "O"];
private _head = ["", "", "", "arrow", "", ""];
private _end = ["", "", "arrow", "arrow", "", ""];
- private _shape = ["", "line", "line", "line", "rectangle", "circle"];
+ private _shapePrims = ["", "line", "line", "line", "rectangle", "circle"];
private _title = ["pen", "line", "line with arrow", "line with double arrows", "square", "circle",];
private _faName = ["pen-fancy", "minus", "long-arrow-alt-right", "arrows-alt-h", "square", "circle"];
- @observable _shapesNum = this._shape.length;
- @observable _selected = this._shapesNum;
-
- @observable _keepMode = false;
-
+ @observable _selectedPrimitive = this._shapePrims.length;
+ @observable _keepPrimitiveMode = false; // for whether primitive selection enters a one-shot or persistent mode
@observable _colorBtn = false;
@observable _widthBtn = false;
@observable _fillBtn = false;
- @action
- clearKeep() { this._selected = this._shapesNum; }
+ @action clearKeepPrimitiveMode() { this._selectedPrimitive = this._shapePrims.length; }
+ @action primCreated() {
+ if (!this._keepPrimitiveMode) { //get out of ink mode after each stroke=
+ Doc.SetSelectedTool(InkTool.None);
+ this._selectedPrimitive = this._shapePrims.length;
+ SetActiveArrowStart("none");
+ SetActiveArrowEnd("none");
+ }
+ }
@action
changeColor = (color: string, type: string) => {
@@ -636,17 +642,17 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu
@computed get drawButtons() {
const func = action((i: number, keep: boolean) => {
- this._keepMode = keep;
- if (this._selected !== i) {
- this._selected = i;
+ this._keepPrimitiveMode = keep;
+ if (this._selectedPrimitive !== i) {
+ this._selectedPrimitive = i;
Doc.SetSelectedTool(InkTool.Pen);
SetActiveArrowStart(this._head[i]);
SetActiveArrowEnd(this._end[i]);
SetActiveBezierApprox("300");
- GestureOverlay.Instance.InkShape = this._shape[i];
+ GestureOverlay.Instance.InkShape = this._shapePrims[i];
} else {
- this._selected = this._shapesNum;
+ this._selectedPrimitive = this._shapePrims.length;
Doc.SetSelectedTool(InkTool.None);
SetActiveArrowStart("");
SetActiveArrowEnd("");
@@ -660,7 +666,7 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu
<button className="antimodeMenu-button"
onPointerDown={() => func(i, false)}
onDoubleClick={() => func(i, true)}
- style={{ backgroundColor: i === this._selected ? "525252" : "", fontSize: "20" }}>
+ style={{ backgroundColor: i === this._selectedPrimitive ? "525252" : "", fontSize: "20" }}>
<FontAwesomeIcon icon={this._faName[i] as IconProp} size="sm" />
</button>
</Tooltip>)}
@@ -729,120 +735,35 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu
</div>;
}
- onUrlDrop = (e: React.DragEvent) => {
+ onWebUrlDrop = (e: React.DragEvent) => {
const { dataTransfer } = e;
const html = dataTransfer.getData("text/html");
const uri = dataTransfer.getData("text/uri-list");
- const url = uri || html || this._url;
+ const url = uri || html || this.webBoxUrl || "";
const newurl = url.startsWith(window.location.origin) ?
- url.replace(window.location.origin, this._url.match(/http[s]?:\/\/[^\/]*/)?.[0] || "") : url;
- this.submitURL(newurl);
+ url.replace(window.location.origin, this.webBoxUrl?.match(/http[s]?:\/\/[^\/]*/)?.[0] || "") : url;
+ this.submitWebUrl(newurl);
e.stopPropagation();
}
- onUrlDragover = (e: React.DragEvent) => {
- e.preventDefault();
- }
-
- @computed get _url() {
- return this.selectedDoc?.data instanceof WebField ? Cast(this.selectedDoc.data, WebField, null)?.url.toString() : Field.toString(this.selectedDoc?.data as Field);
- }
-
- set _url(value) {
- if (this.selectedDoc) {
- Doc.GetProto(this.selectedDoc).data = new WebField(value);
- Doc.SetInPlace(this.selectedDoc, "title", value, true);
- const annots = Doc.GetProto(this.selectedDoc)["data-annotations-" + this.urlHash(value)];
- Doc.GetProto(this.selectedDoc)["data-annotations"] = annots instanceof ObjectField ? ObjectField.MakeCopy(annots) : new List<Doc>([]);
- }
- }
-
- @action
- submitURL = (url: string) => {
- if (!url.startsWith("http")) url = "http://" + url;
- try {
- const selectedDoc = this.selectedDoc;
- if (selectedDoc) {
- const URLy = new URL(url);
- const future = Cast(selectedDoc["data-future"], listSpec("string"), null);
- const history = Cast(selectedDoc["data-history"], listSpec("string"), null);
- const annos = DocListCast(selectedDoc["data-annotations"]);
- if (Field.toString(selectedDoc.data as Field) === Field.toString(new WebField(URLy))) {
- Doc.GetProto(selectedDoc).data = new WebField(new URL("http://cs.brown.edu/~avd"));
- setTimeout(action(() => Doc.GetProto(selectedDoc).data = new WebField(URLy)), 100);
- } else {
- if (url) {
- Doc.GetProto(selectedDoc)["data-annotations-" + this.urlHash(this._url)] = new List<Doc>(annos);
- if (history === undefined) {
- selectedDoc["data-history"] = new List<string>([this._url]);
- } else {
- history.push(this._url);
- }
- this.props.docView.props.Document._scrollTop = 0;
- future && (future.length = 0);
- }
- this._url = url;
- }
- }
- } catch (e) {
- console.log("WebBox URL error:" + url);
- }
- }
-
- urlHash(s: string) {
- return s.split('').reduce((a: any, b: any) => { a = ((a << 5) - a) + b.charCodeAt(0); return a & a; }, 0);
- }
-
- onValueKeyDown = async (e: React.KeyboardEvent) => {
- e.key === "Enter" && this.submitURL(this._keyInput.current!.value);
+ onWebUrlValueKeyDown = (e: React.KeyboardEvent) => {
+ e.key === "Enter" && this.submitWebUrl(this._keyInput.current!.value);
e.stopPropagation();
}
-
- @action
- forward = () => {
- const selectedDoc = this.selectedDoc;
- if (selectedDoc) {
- const future = Cast(selectedDoc["data-future"], listSpec("string"), null);
- const history = Cast(selectedDoc["data-history"], listSpec("string"), null);
- if (future?.length) {
- history?.push(this._url);
- Doc.GetProto(selectedDoc)["data-annotations-" + this.urlHash(this._url)] = new List<Doc>(DocListCast(selectedDoc["data-annotations"]));
- const newurl = future.pop()!;
- Doc.GetProto(selectedDoc).data = new WebField(new URL(this._url = newurl));
- Doc.GetProto(selectedDoc)["data-annotations"] = new List<Doc>(DocListCast(selectedDoc["data-annotations-" + this.urlHash(newurl)]));
- }
- }
- }
-
- @action
- back = () => {
- const selectedDoc = this.selectedDoc;
- if (selectedDoc) {
- const future = Cast(selectedDoc["data-future"], listSpec("string"), null);
- const history = Cast(selectedDoc["data-history"], listSpec("string"), null);
- if (history?.length) {
- if (future === undefined) selectedDoc["data-future"] = new List<string>([this._url]);
- else future.push(this._url);
- Doc.GetProto(selectedDoc)["data-annotations-" + this.urlHash(this._url)] = new List<Doc>(DocListCast(selectedDoc["data-annotations"]));
- const newurl = history.pop()!;
- Doc.GetProto(selectedDoc).data = new WebField(new URL(this._url = newurl));
- Doc.GetProto(selectedDoc)["data-annotations"] = new List<Doc>(DocListCast(selectedDoc["data-annotations-" + this.urlHash(newurl)]));
- }
- }
- }
+ submitWebUrl = (url: string) => this.selectedDocumentView?.ComponentView?.submitURL?.(url);
+ webUrlForward = () => this.selectedDocumentView?.ComponentView?.forward?.();
+ webUrlBack = () => this.selectedDocumentView?.ComponentView?.back?.();
private _keyInput = React.createRef<HTMLInputElement>();
@computed get urlEditor() {
return (
- <div className="webBox-buttons"
- onDrop={this.onUrlDrop}
- onDragOver={this.onUrlDragover} style={{ display: "flex" }}>
- <input className="webpage-urlInput" key={this._url}
+ <div className="collectionMenu-webUrlButtons" onDrop={this.onWebUrlDrop} onDragOver={e => e.preventDefault()} >
+ <input className="collectionMenu-urlInput" key={this.webBoxUrl}
placeholder="ENTER URL"
- defaultValue={this._url}
- onDrop={this.onUrlDrop}
- onDragOver={this.onUrlDragover}
- onKeyDown={this.onValueKeyDown}
+ defaultValue={this.webBoxUrl}
+ onDrop={this.onWebUrlDrop}
+ onDragOver={e => e.preventDefault()}
+ onKeyDown={this.onWebUrlValueKeyDown}
onClick={(e) => {
this._keyInput.current!.select();
e.stopPropagation();
@@ -850,13 +771,13 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu
ref={this._keyInput}
/>
<div style={{ display: "flex", flexDirection: "row", justifyContent: "space-between", maxWidth: "250px", }}>
- <button className="submitUrl" onClick={() => this.submitURL(this._keyInput.current!.value)} onDragOver={this.onUrlDragover} onDrop={this.onUrlDrop}>
+ <button className="submitUrl" onClick={() => this.submitWebUrl(this._keyInput.current!.value)} onDragOver={e => e.stopPropagation()} onDrop={this.onWebUrlDrop}>
GO
</button>
- <button className="submitUrl" onClick={this.back}>
+ <button className="submitUrl" onClick={this.webUrlBack}>
<FontAwesomeIcon icon="caret-left" size="lg"></FontAwesomeIcon>
</button>
- <button className="submitUrl" onClick={this.forward}>
+ <button className="submitUrl" onClick={this.webUrlForward}>
<FontAwesomeIcon icon="caret-right" size="lg"></FontAwesomeIcon>
</button>
</div>
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index c39f8b255..66064e354 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -11,7 +11,7 @@ import { listSpec } from "../../../fields/Schema";
import { PastelSchemaPalette, SchemaHeaderField } from "../../../fields/SchemaHeaderField";
import { Cast, NumCast } from "../../../fields/Types";
import { TraceMobx } from "../../../fields/util";
-import { emptyFunction, returnFalse, setupMoveUpEvents } from "../../../Utils";
+import { emptyFunction, emptyPath, returnFalse, setupMoveUpEvents, returnEmptyDoclist } from "../../../Utils";
import { SelectionManager } from "../../util/SelectionManager";
import { SnappingManager } from "../../util/SnappingManager";
import { Transform } from "../../util/Transform";
@@ -414,6 +414,8 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
docRangeFilters={this.docRangeFilters}
searchFilterDocs={this.searchFilterDocs}
styleProvider={DefaultStyleProvider}
+ layerProvider={undefined}
+ docViewPath={returnEmptyDoclist}
ContainingCollectionDoc={this.props.CollectionView?.props.Document}
ContainingCollectionView={this.props.CollectionView}
moveDocument={this.props.moveDocument}
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index d8a8723cd..9c12d5020 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -80,13 +80,10 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument,
return docs.map((d, i) => {
const height = () => this.getDocHeight(d);
const width = () => this.getDocWidth(d);
- const dref = React.createRef<HTMLDivElement>();
- const dxf = () => this.getDocTransform(d, dref.current!);
- this._docXfs.push({ dxf, width, height });
const rowSpan = Math.ceil((height() + this.gridGap) / this.gridGap);
const style = this.isStackingView ? { width: width(), marginTop: i ? this.gridGap : 0, height: height() } : { gridRowEnd: `span ${rowSpan}` };
- return <div className={`collectionStackingView-${this.isStackingView ? "columnDoc" : "masonryDoc"}`} key={d[Id]} ref={dref} style={style} >
- {this.getDisplayDoc(d, dxf, width)}
+ return <div className={`collectionStackingView-${this.isStackingView ? "columnDoc" : "masonryDoc"}`} key={d[Id]} style={style} >
+ {this.getDisplayDoc(d, width)}
</div>;
});
}
@@ -171,8 +168,7 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument,
return this.props.addDocTab(doc, where);
}
-
- focusDocument = (doc: Doc, willZoom?: boolean, scale?: number, afterFocus?: DocAfterFocusFunc, dontCenter?: boolean, didFocus?: boolean) => {
+ focusDocument = (doc: Doc, willZoom?: boolean, scale?: number, afterFocus?: DocAfterFocusFunc) => {
Doc.BrushDoc(doc);
this.props.focus(this.props.Document, true); // bcz: want our containing collection to zoom
Doc.linkFollowHighlight(doc);
@@ -186,27 +182,32 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument,
afterFocus && setTimeout(afterFocus, 500);
}
- getDisplayDoc(doc: Doc, dxf: () => Transform, width: () => number) {
+ styleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps | FieldViewProps>, property: string) => {
+ if (property === StyleProp.Opacity && doc) {
+ if (this.props.childOpacity) {
+ return this.props.childOpacity();
+ }
+ if (this.Document._currentFrame !== undefined) {
+ return CollectionFreeFormDocumentView.getValues(doc, NumCast(this.Document._currentFrame))?.opacity;
+ }
+ }
+ return this.props.styleProvider?.(doc, props, property);
+ }
+ getDisplayDoc(doc: Doc, width: () => number) {
const dataDoc = (!doc.isTemplateDoc && !doc.isTemplateForField && !doc.PARAMS) ? undefined : this.props.DataDoc;
const height = () => this.getDocHeight(doc);
- const styleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps | FieldViewProps>, property: string) => {
- if (property === StyleProp.Opacity && doc) {
- if (this.props.childOpacity) {
- return this.props.childOpacity();
- }
- if (this.Document._currentFrame !== undefined) {
- return CollectionFreeFormDocumentView.getValues(doc, NumCast(this.Document._currentFrame))?.opacity;
- }
- }
- return this.props.styleProvider?.(doc, props, property);
- };
- return <DocumentView
+
+ let dref: Opt<HTMLDivElement>;
+ const stackedDocTransform = () => this.getDocTransform(doc, dref);
+ return <DocumentView ref={r => dref = r?.ContentDiv ? r.ContentDiv : undefined}
Document={doc}
DataDoc={dataDoc || (!Doc.AreProtosEqual(doc[DataSym], doc) && doc[DataSym])}
renderDepth={this.props.renderDepth + 1}
PanelWidth={width}
PanelHeight={height}
- styleProvider={styleProvider}
+ styleProvider={this.styleProvider}
+ layerProvider={this.props.layerProvider}
+ docViewPath={this.props.docViewPath}
LayoutTemplate={this.props.childLayoutTemplate}
LayoutTemplateString={this.props.childLayoutString}
freezeDimensions={this.props.childFreezeDimensions}
@@ -218,7 +219,7 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument,
dropAction={StrCast(this.layoutDoc.childDropAction) as dropActionType}
onClick={this.onChildClickHandler}
onDoubleClick={this.onChildDoubleClickHandler}
- ScreenToLocalTransform={dxf}
+ ScreenToLocalTransform={stackedDocTransform}
focus={this.focusDocument}
docFilters={this.docFilters}
docRangeFilters={this.docRangeFilters}
@@ -385,14 +386,10 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument,
/>;
}
- getDocTransform(doc: Doc, dref: HTMLDivElement) {
- if (!dref) return Transform.Identity();
+ getDocTransform(doc: Doc, dref?: HTMLDivElement) {
const y = this._scroll; // required for document decorations to update when the text box container is scrolled
const { scale, translateX, translateY } = Utils.GetScreenTransform(dref);
- const outerXf = Utils.GetScreenTransform(this._masonryGridRef!);
- const offset = this.props.ScreenToLocalTransform().transformDirection(outerXf.translateX - translateX, outerXf.translateY - translateY);
- const offsety = 0;
- return this.props.ScreenToLocalTransform().translate(offset[0], offset[1] + offsety);
+ return new Transform(-translateX, -translateY, 1).scale(this.props.ScreenToLocalTransform().Scale);
}
forceAutoHeight = () => {
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index b9ad1680a..a1be6d8f2 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -165,7 +165,9 @@ export class
renderDepth={this.props.renderDepth + 1}
rootSelected={returnTrue}
//dontRegisterView={true}
+ docViewPath={this.props.docViewPath}
styleProvider={this.props.styleProvider}
+ layerProvider={this.props.layerProvider}
PanelWidth={this.rtfWidth}
PanelHeight={this.rtfOutlineHeight}
focus={this.props.focus}
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 03d8606d7..9ae469930 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -1,17 +1,16 @@
import { action, computed, observable } from 'mobx';
import { observer } from "mobx-react";
import * as React from 'react';
-import Lightbox from 'react-image-lightbox-with-rotate';
import 'react-image-lightbox-with-rotate/style.css'; // This only needs to be imported once in your app
import { DateField } from '../../../fields/DateField';
-import { AclAddonly, AclAdmin, AclEdit, AclPrivate, AclReadonly, AclSym, DataSym, Doc, DocListCast, Field } from '../../../fields/Doc';
+import { AclAddonly, AclAdmin, AclEdit, AclPrivate, AclReadonly, AclSym, DataSym, Doc, DocListCast } from '../../../fields/Doc';
import { Id } from '../../../fields/FieldSymbols';
import { List } from '../../../fields/List';
import { ObjectField } from '../../../fields/ObjectField';
-import { BoolCast, Cast, ScriptCast, StrCast } from '../../../fields/Types';
-import { ImageField } from '../../../fields/URLField';
+import { ScriptField } from '../../../fields/ScriptField';
+import { Cast, ScriptCast, StrCast } from '../../../fields/Types';
import { denormalizeEmail, distributeAcls, GetEffectiveAcl, SharingPermissions, TraceMobx } from '../../../fields/util';
-import { returnFalse, Utils } from '../../../Utils';
+import { returnFalse } from '../../../Utils';
import { Docs, DocUtils } from '../../documents/Documents';
import { DocumentType } from '../../documents/DocumentTypes';
import { CurrentUserUtils } from '../../util/CurrentUserUtils';
@@ -38,7 +37,6 @@ import { SubCollectionViewProps } from './CollectionSubView';
import { CollectionTimeView } from './CollectionTimeView';
import { CollectionTreeView } from "./CollectionTreeView";
import './CollectionView.scss';
-import { ScriptField } from '../../../fields/ScriptField';
export const COLLECTION_BORDER_WIDTH = 2;
const path = require('path');
@@ -85,8 +83,6 @@ export class CollectionView extends Touchable<CollectionViewProps> {
public static LayoutString(fieldStr: string) { return FieldView.LayoutString(CollectionView, fieldStr); }
_isChildActive = false; //TODO should this be observable?
- get _isLightboxOpen() { return BoolCast(this.props.Document._isLightboxOpen); }
- set _isLightboxOpen(value) { this.props.Document._isLightboxOpen = value; }
@observable private _curLightboxImg = 0;
@observable private static _safeMode = false;
public static SetSafeMode(safeMode: boolean) { this._safeMode = safeMode; }
@@ -280,7 +276,6 @@ export class CollectionView extends Touchable<CollectionViewProps> {
!Doc.UserDoc().noviceMode && subItems.push({ description: "Pivot/Time", event: () => func(CollectionViewType.Time), icon: "columns" });
!Doc.UserDoc().noviceMode && subItems.push({ description: "Map", event: () => func(CollectionViewType.Map), icon: "globe-americas" });
subItems.push({ description: "Grid", event: () => func(CollectionViewType.Grid), icon: "th-list" });
- subItems.push({ description: "lightbox", event: action(() => this._isLightboxOpen = true), icon: "eye" });
if (!Doc.IsSystem(this.props.Document) && !this.props.Document.annotationOn) {
const existingVm = ContextMenu.Instance.findByDescription(category);
@@ -342,27 +337,6 @@ export class CollectionView extends Touchable<CollectionViewProps> {
}
}
- lightbox = (images: { image: string, title: string, caption: string }[]) => {
- if (!images.length) return (null);
- const mainPath = path.extname(images[this._curLightboxImg].image);
- const nextPath = path.extname(images[(this._curLightboxImg + 1) % images.length].image);
- const prevPath = path.extname(images[(this._curLightboxImg + images.length - 1) % images.length].image);
- const main = images[this._curLightboxImg].image.replace(mainPath, "_o" + mainPath);
- const title = images[this._curLightboxImg].title;
- const caption = images[this._curLightboxImg].caption;
- const next = images[(this._curLightboxImg + 1) % images.length].image.replace(nextPath, "_o" + nextPath);
- const prev = images[(this._curLightboxImg + images.length - 1) % images.length].image.replace(prevPath, "_o" + prevPath);
- return !this._isLightboxOpen ? (null) : (<Lightbox key="lightbox"
- mainSrc={main}
- nextSrc={next}
- prevSrc={prev}
- imageTitle={title}
- imageCaption={caption}
- onCloseRequest={action(() => this._isLightboxOpen = false)}
- onMovePrevRequest={action(() => this._curLightboxImg = (this._curLightboxImg + images.length - 1) % images.length)}
- onMoveNextRequest={action(() => this._curLightboxImg = (this._curLightboxImg + 1) % images.length)} />);
- }
-
bodyPanelWidth = () => this.props.PanelWidth();
childLayoutTemplate = () => this.props.childLayoutTemplate?.() || Cast(this.props.Document.childLayoutTemplate, Doc, null);
@@ -389,13 +363,6 @@ export class CollectionView extends Touchable<CollectionViewProps> {
style={{ pointerEvents: this.props.layerProvider?.(this.props.Document) === false ? "none" : undefined }}>
{this.showIsTagged()}
{this.collectionViewType !== undefined ? this.SubView(this.collectionViewType, props) : (null)}
- {this.lightbox(DocListCast(this.props.Document[this.props.fieldKey]).filter(d => Cast(d.data, ImageField, null)).map(d =>
- ({
- image: (Cast(d.data, ImageField)!.url.href.indexOf(window.location.origin) === -1) ?
- Utils.CorsProxy(Cast(d.data, ImageField)!.url.href) : Cast(d.data, ImageField)!.url.href,
- title: StrCast(d.title),
- caption: Field.toString(d.caption as Field)
- })))}
</div>);
}
}
diff --git a/src/client/views/collections/SchemaTable.tsx b/src/client/views/collections/SchemaTable.tsx
index d77f70607..d4b4cf333 100644
--- a/src/client/views/collections/SchemaTable.tsx
+++ b/src/client/views/collections/SchemaTable.tsx
@@ -15,7 +15,7 @@ import { ComputedField } from "../../../fields/ScriptField";
import { Cast, FieldValue, NumCast, StrCast } from "../../../fields/Types";
import { ImageField } from "../../../fields/URLField";
import { GetEffectiveAcl } from "../../../fields/util";
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse } from "../../../Utils";
+import { emptyFunction, emptyPath, returnEmptyDoclist, returnEmptyFilter, returnFalse } from "../../../Utils";
import { Docs, DocumentOptions } from "../../documents/Documents";
import { DocumentType } from "../../documents/DocumentTypes";
import { CompileScript, Transformer, ts } from "../../util/Scripting";
@@ -25,6 +25,7 @@ import { COLLECTION_BORDER_WIDTH, SCHEMA_DIVIDER_WIDTH } from '../../views/globa
import { ContextMenu } from "../ContextMenu";
import '../DocumentDecorations.scss';
import { DocumentView } from "../nodes/DocumentView";
+import { DefaultStyleProvider } from "../StyleProvider";
import { CellProps, CollectionSchemaButtons, CollectionSchemaCell, CollectionSchemaCheckboxCell, CollectionSchemaDateCell, CollectionSchemaDocCell, CollectionSchemaImageCell, CollectionSchemaListCell, CollectionSchemaNumberCell, CollectionSchemaStringCell } from "./CollectionSchemaCells";
import { CollectionSchemaAddColumnHeader, KeysDropdown } from "./CollectionSchemaHeaders";
import { MovableColumn, MovableRow } from "./CollectionSchemaMovableTableHOC";
@@ -570,6 +571,9 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
ref="overlay"><DocumentView
Document={this._showDoc}
DataDoc={this._showDataDoc}
+ styleProvider={DefaultStyleProvider}
+ layerProvider={undefined}
+ docViewPath={returnEmptyDoclist}
freezeDimensions={true}
focus={emptyFunction}
renderDepth={this.props.renderDepth}
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index c66734556..e1e1c8656 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -12,7 +12,7 @@ import { FieldId } from "../../../fields/RefField";
import { listSpec } from '../../../fields/Schema';
import { Cast, NumCast, StrCast } from "../../../fields/Types";
import { TraceMobx } from '../../../fields/util';
-import { emptyFunction, returnFalse, returnTrue, setupMoveUpEvents, Utils } from "../../../Utils";
+import { emptyFunction, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents, Utils } from "../../../Utils";
import { DocServer } from "../../DocServer";
import { DocumentType } from '../../documents/DocumentTypes';
import { CurrentUserUtils } from '../../util/CurrentUserUtils';
@@ -22,9 +22,10 @@ import { SelectionManager } from '../../util/SelectionManager';
import { SnappingManager } from '../../util/SnappingManager';
import { Transform } from '../../util/Transform';
import { undoBatch, UndoManager } from "../../util/UndoManager";
-import { DocumentView, DocAfterFocusFunc, DocumentViewProps } from "../nodes/DocumentView";
+import { LightboxView } from '../LightboxView';
+import { DocAfterFocusFunc, DocumentView, DocumentViewProps } from "../nodes/DocumentView";
import { FieldViewProps } from '../nodes/FieldView';
-import { PresBox, PresMovement, PinProps } from '../nodes/PresBox';
+import { PinProps, PresBox, PresMovement } from '../nodes/PresBox';
import { DefaultLayerProvider, DefaultStyleProvider, StyleLayers, StyleProp } from '../StyleProvider';
import { CollectionDockingView } from './CollectionDockingView';
import { CollectionDockingViewMenu } from './CollectionDockingViewMenu';
@@ -277,9 +278,11 @@ export class TabDocView extends React.Component<TabDocViewProps> {
case "close": return CollectionDockingView.CloseSplit(doc, locationParams);
case "fullScreen": return CollectionDockingView.OpenFullScreen(doc);
case "replace": return CollectionDockingView.ReplaceTab(doc, locationParams, this.stack);
+ case "lightbox": return runInAction(() => LightboxView.LightboxDoc = doc) ? true : false;
case "inPlace":
case "add":
- default: return CollectionDockingView.AddSplit(doc, locationParams, this.stack);
+ default:
+ return CollectionDockingView.AddSplit(doc, locationParams, this.stack);
}
}
@@ -311,6 +314,7 @@ export class TabDocView extends React.Component<TabDocViewProps> {
ContainingCollectionView={undefined}
ContainingCollectionDoc={undefined}
parentActive={returnFalse}
+ docViewPath={returnEmptyDoclist}
childLayoutTemplate={this.childLayoutTemplate} // bcz: Ugh .. should probably be rendering a CollectionView or the minimap should be part of the collectionFreeFormView to avoid having to set stuff like this.
noOverlay={true} // don't render overlay Docs since they won't scale
active={returnTrue}
@@ -331,6 +335,7 @@ export class TabDocView extends React.Component<TabDocViewProps> {
whenActiveChanged={emptyFunction}
focus={emptyFunction}
styleProvider={TabDocView.miniStyleProvider}
+ layerProvider={undefined}
addDocTab={this.addDocTab}
pinToPres={TabDocView.PinDoc}
docFilters={CollectionDockingView.Instance.docFilters}
@@ -351,7 +356,7 @@ export class TabDocView extends React.Component<TabDocViewProps> {
</Tooltip>
</>;
}
- focusFunc = (doc: Doc, willZoom?: boolean, scale?: number, afterFocus?: DocAfterFocusFunc, dontCenter?: boolean, notFocused?: boolean) => {
+ focusFunc = (doc: Doc, willZoom?: boolean, scale?: number, afterFocus?: DocAfterFocusFunc) => {
if (!this.tab.header.parent._activeContentItem || this.tab.header.parent._activeContentItem !== this.tab.contentItem) {
this.tab.header.parent.setActiveContentItem(this.tab.contentItem); // glr: Panning does not work when this is set - (this line is for trying to make a tab that is not topmost become topmost)
}
@@ -405,6 +410,7 @@ export class TabDocView extends React.Component<TabDocViewProps> {
parentActive={this.active}
whenActiveChanged={emptyFunction}
focus={this.focusFunc}
+ docViewPath={returnEmptyDoclist}
bringToFront={emptyFunction}
pinToPres={TabDocView.PinDoc} />
{this._document._viewType !== CollectionViewType.Freeform ? (null) :
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 816778c59..0d89c7b43 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -1,7 +1,6 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, computed, observable } from "mobx";
import { observer } from "mobx-react";
-import { TREE_BULLET_WIDTH } from '../globalCssVariables.scss';
import { DataSym, Doc, DocListCast, DocListCastOrNull, Field, HeightSym, Opt, WidthSym } from '../../../fields/Doc';
import { Id } from '../../../fields/FieldSymbols';
import { List } from '../../../fields/List';
@@ -10,7 +9,7 @@ import { listSpec } from '../../../fields/Schema';
import { ComputedField, ScriptField } from '../../../fields/ScriptField';
import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
import { TraceMobx } from '../../../fields/util';
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnTrue, returnZero, simulateMouseClick, Utils } from '../../../Utils';
+import { emptyFunction, emptyPath, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnTrue, returnZero, simulateMouseClick, Utils } from '../../../Utils';
import { Docs, DocUtils } from '../../documents/Documents';
import { DocumentType } from "../../documents/DocumentTypes";
import { CurrentUserUtils } from '../../util/CurrentUserUtils';
@@ -21,17 +20,18 @@ import { SnappingManager } from '../../util/SnappingManager';
import { Transform } from '../../util/Transform';
import { undoBatch, UndoManager } from '../../util/UndoManager';
import { EditableView } from "../EditableView";
+import { TREE_BULLET_WIDTH } from '../globalCssVariables.scss';
import { DocumentView, DocumentViewProps, StyleProviderFunc } from '../nodes/DocumentView';
import { FieldViewProps } from '../nodes/FieldView';
import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
import { RichTextMenu } from '../nodes/formattedText/RichTextMenu';
import { KeyValueBox } from '../nodes/KeyValueBox';
+import { SliderBox } from '../nodes/SliderBox';
import { StyleProp, testDocProps } from '../StyleProvider';
import { CollectionTreeView } from './CollectionTreeView';
import { CollectionView, CollectionViewType } from './CollectionView';
import "./TreeView.scss";
import React = require("react");
-import { SliderBox } from '../nodes/SliderBox';
export interface TreeViewProps {
document: Doc;
@@ -557,6 +557,8 @@ export class TreeView extends React.Component<TreeViewProps> {
Document={this.doc}
DataDoc={undefined}
styleProvider={this.titleStyleProvider}
+ layerProvider={undefined}
+ docViewPath={returnEmptyDoclist}
treeViewDoc={this.props.treeView.props.Document}
addDocument={undefined}
addDocTab={this.props.addDocTab}
@@ -647,6 +649,8 @@ export class TreeView extends React.Component<TreeViewProps> {
renderDepth={this.props.renderDepth + 1}
rootSelected={returnTrue}
styleProvider={asText ? this.titleStyleProvider : this.embeddedStyleProvider}
+ layerProvider={undefined}
+ docViewPath={this.props.treeView.props.docViewPath}
docFilters={returnEmptyFilter}
docRangeFilters={returnEmptyFilter}
searchFilterDocs={returnEmptyDoclist}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
index ae5688b48..51cb9387a 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -41,44 +41,41 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
setTimeout(action(() => this._opacity = 1), 0); // since the render code depends on querying the Dom through getBoudndingClientRect, we need to delay triggering render()
setTimeout(action(() => (!LinkDocs.length || !linkDoc.linkDisplay) && (this._opacity = 0.05)), 750); // this will unhighlight the link line.
if (!linkDoc.linkAutoMove) return;
- const acont = A.props.Document.type === DocumentType.LINK ? A.ContentDiv.getElementsByClassName("linkAnchorBox-cont") : [];
- const bcont = B.props.Document.type === DocumentType.LINK ? B.ContentDiv.getElementsByClassName("linkAnchorBox-cont") : [];
- const adiv = (acont.length ? acont[0] : A.ContentDiv);
- const bdiv = (bcont.length ? bcont[0] : B.ContentDiv);
+ const acont = A.rootDoc.type === DocumentType.LINK ? A.ContentDiv.getElementsByClassName("linkAnchorBox-cont") : [];
+ const bcont = B.rootDoc.type === DocumentType.LINK ? B.ContentDiv.getElementsByClassName("linkAnchorBox-cont") : [];
+ const adiv = acont.length ? acont[0] : A.ContentDiv;
+ const bdiv = bcont.length ? bcont[0] : B.ContentDiv;
const a = adiv.getBoundingClientRect();
const b = bdiv.getBoundingClientRect();
const { left: aleft, top: atop, width: awidth, height: aheight } = adiv.parentElement!.getBoundingClientRect();
const { left: bleft, top: btop, width: bwidth, height: bheight } = bdiv.parentElement!.getBoundingClientRect();
const apt = Utils.closestPtBetweenRectangles(aleft, atop, awidth, aheight, bleft, btop, bwidth, bheight, a.left + a.width / 2, a.top + a.height / 2);
const bpt = Utils.closestPtBetweenRectangles(bleft, btop, bwidth, bheight, aleft, atop, awidth, aheight, apt.point.x, apt.point.y);
- const afield = A.props.LayoutTemplateString?.indexOf("anchor1") === -1 ? "anchor2" : "anchor1";
- const bfield = afield === "anchor1" ? "anchor2" : "anchor1";
// really hacky stuff to make the LinkAnchorBox display where we want it to:
- // if there's an element in the DOM with a classname containing the link's id and a data-targetids attribute containing the other end of the link,
+ // if there's an element in the DOM with a classname containing a link anchor's id,
// then that DOM element is a hyperlink source for the current anchor and we want to place our link box at it's top right
// otherwise, we just use the computed nearest point on the document boundary to the target Document
- const linkEles = Array.from(window.document.getElementsByClassName(linkDoc[Id]));
- const targetAhyperlink = linkEles.find((ele: any) => ele.dataset.targetids?.includes((linkDoc[afield] as Doc)[Id]));
- const targetBhyperlink = linkEles.find((ele: any) => ele.dataset.targetids?.includes((linkDoc[bfield] as Doc)[Id]));
+ const targetAhyperlink = Array.from(window.document.getElementsByClassName((linkDoc.anchor1 as Doc)[Id])).lastElement();
+ const targetBhyperlink = Array.from(window.document.getElementsByClassName((linkDoc.anchor2 as Doc)[Id])).lastElement();
if ((!targetAhyperlink && !a.width) || (!targetBhyperlink && !b.width)) return;
- if (!targetBhyperlink) {
- A.rootDoc[afield + "_x"] = (apt.point.x - aleft) / awidth * 100;
- A.rootDoc[afield + "_y"] = (apt.point.y - atop) / aheight * 100;
+ if (!targetAhyperlink) {
+ linkDoc.anchor1_x = (apt.point.x - aleft) / awidth * 100;
+ linkDoc.anchor1_y = (apt.point.y - atop) / aheight * 100;
} else {
- const m = targetBhyperlink.getBoundingClientRect();
+ const m = targetAhyperlink.getBoundingClientRect();
const mp = A.props.ScreenToLocalTransform().transformPoint(m.right, m.top + 5);
- A.rootDoc[afield + "_x"] = Math.min(1, mp[0] / A.props.PanelWidth()) * 100;
- A.rootDoc[afield + "_y"] = Math.min(1, mp[1] / A.props.PanelHeight()) * 100;
+ linkDoc.anchor1_x = Math.min(1, mp[0] / A.props.PanelWidth()) * 100;
+ linkDoc.anchor1_y = Math.min(1, mp[1] / A.props.PanelHeight()) * 100;
}
- if (!targetAhyperlink) {
- B.rootDoc[bfield + "_x"] = (bpt.point.x - bleft) / bwidth * 100;
- B.rootDoc[bfield + "_y"] = (bpt.point.y - btop) / bheight * 100;
+ if (!targetBhyperlink) {
+ linkDoc.anchor2_x = (bpt.point.x - bleft) / bwidth * 100;
+ linkDoc.anchor2_y = (bpt.point.y - btop) / bheight * 100;
} else {
- const m = targetAhyperlink.getBoundingClientRect();
+ const m = targetBhyperlink.getBoundingClientRect();
const mp = B.props.ScreenToLocalTransform().transformPoint(m.right, m.top + 5);
- B.rootDoc[bfield + "_x"] = Math.min(1, mp[0] / B.props.PanelWidth()) * 100;
- B.rootDoc[bfield + "_y"] = Math.min(1, mp[1] / B.props.PanelHeight()) * 100;
+ linkDoc.anchor2_x = Math.min(1, mp[0] / B.props.PanelWidth()) * 100;
+ linkDoc.anchor2_y = Math.min(1, mp[1] / B.props.PanelHeight()) * 100;
}
}
@@ -162,8 +159,8 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
const ptlen = Math.sqrt((pt1[0] - pt2[0]) * (pt1[0] - pt2[0]) + (pt1[1] - pt2[1]) * (pt1[1] - pt2[1])) / 2;
const pt1norm = clipped ? [0, 0] : [pt1vec[0] / pt1len * ptlen, pt1vec[1] / pt1len * ptlen];
const pt2norm = clipped ? [0, 0] : [pt2vec[0] / pt2len * ptlen, pt2vec[1] / pt2len * ptlen];
- const aActive = A.isSelected() || Doc.IsBrushed(A.props.Document);
- const bActive = B.isSelected() || Doc.IsBrushed(B.props.Document);
+ const aActive = A.isSelected() || Doc.IsBrushed(A.rootDoc);
+ const bActive = B.isSelected() || Doc.IsBrushed(B.rootDoc);
const textX = (Math.min(pt1[0], pt2[0]) + Math.max(pt1[0], pt2[0])) / 2 + NumCast(LinkDocs[0].linkOffsetX);
const textY = (pt1[1] + pt2[1]) / 2 + NumCast(LinkDocs[0].linkOffsetY);
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
index 4dab8f15b..5e0b31754 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
@@ -3,32 +3,27 @@ import { observer } from "mobx-react";
import { Doc } from "../../../../fields/Doc";
import { Id } from "../../../../fields/FieldSymbols";
import { Utils } from "../../../../Utils";
-import { DocumentType } from "../../../documents/DocumentTypes";
import { DocumentManager } from "../../../util/DocumentManager";
import { DocumentView } from "../../nodes/DocumentView";
import "./CollectionFreeFormLinksView.scss";
import { CollectionFreeFormLinkView } from "./CollectionFreeFormLinkView";
import React = require("react");
+import { DocumentType } from "../../../documents/DocumentTypes";
@observer
export class CollectionFreeFormLinksView extends React.Component {
@computed get uniqueConnections() {
- const connections = DocumentManager.Instance.LinkedDocumentViews.reduce((drawnPairs, connection) => {
- if (!drawnPairs.reduce((found, drawnPair) => {
- const match1 = (connection.a === drawnPair.a && connection.b === drawnPair.b);
- const match2 = (connection.a === drawnPair.b && connection.b === drawnPair.a);
- const match = match1 || match2;
- if (match && !drawnPair.l.reduce((found, link) => found || link[Id] === connection.l[Id], false)) {
- drawnPair.l.push(connection.l);
- }
- return match || found;
- }, false)) {
- drawnPairs.push({ a: connection.a, b: connection.b, l: [connection.l] });
- }
- return drawnPairs;
- }, [] as { a: DocumentView, b: DocumentView, l: Doc[] }[]);
- return connections.filter(c => c.a.props.Document.type === DocumentType.LINK)
- .map(c => <CollectionFreeFormLinkView key={Utils.GenerateGuid()} A={c.a} B={c.b} LinkDocs={c.l} />);
+ const connections = DocumentManager.Instance.LinkedDocumentViews
+ .filter(c => c.a.props.Document.type === DocumentType.LINK || c.b.props.Document.type === DocumentType.LINK)
+ .reduce((drawnPairs, connection) => {
+ const matchingPairs = drawnPairs.filter(pair => connection.a === pair.a && connection.b === pair.b);
+ matchingPairs.forEach(drawnPair => drawnPair.l.add(connection.l));
+ if (!matchingPairs.length) drawnPairs.push({ a: connection.a, b: connection.b, l: new Set<Doc>([connection.l]) });
+ return drawnPairs;
+ }, [] as { a: DocumentView, b: DocumentView, l: Set<Doc> }[]);
+ const set = new Map<Doc, { a: DocumentView, b: DocumentView, l: Doc[] }>();
+ connections.map(c => !set.has(Array.from(c.l)[0]) && set.set(Array.from(c.l)[0], { a: c.a, b: c.b, l: Array.from(c.l) }));
+ return Array.from(set.values()).map(c => <CollectionFreeFormLinkView key={c.l[0][Id]} A={c.a} B={c.b} LinkDocs={c.l} />);
}
render() {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 6c7512f7c..bba0807a4 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -49,6 +49,7 @@ import { CurrentUserUtils } from "../../../util/CurrentUserUtils";
import { StyleProp, StyleLayers } from "../../StyleProvider";
import { DocumentDecorations } from "../../DocumentDecorations";
import { FieldViewProps } from "../../nodes/FieldView";
+import { reset } from "colors";
export const panZoomSchema = createSchema({
_panX: "number",
@@ -271,12 +272,11 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
@undoBatch
@action
- internalPdfAnnoDrop(e: Event, annoDragData: DragManager.AnchorAnnoDragData, xp: number, yp: number) {
+ internalAnchorAnnoDrop(e: Event, annoDragData: DragManager.AnchorAnnoDragData, xp: number, yp: number) {
const dragDoc = annoDragData.dropDocument!;
const dropPos = [NumCast(dragDoc.x), NumCast(dragDoc.y)];
dragDoc.x = xp - annoDragData.offset[0] + (NumCast(dragDoc.x) - dropPos[0]);
dragDoc.y = yp - annoDragData.offset[1] + (NumCast(dragDoc.y) - dropPos[1]);
- annoDragData.targetContext = this.props.Document; // dropped a PDF annotation, so we need to set the targetContext on the dragData which the PDF view uses at the end of the drop operation
this.bringToFront(dragDoc);
return true;
}
@@ -303,7 +303,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
const [xp, yp] = this.getTransform().transformPoint(de.x, de.y);
if (this.isAnnotationOverlay !== true && de.complete.linkDragData) return this.internalLinkDrop(e, de, de.complete.linkDragData, xp, yp);
- if (de.complete.annoDragData?.dragDocument && super.onInternalDrop(e, de)) return this.internalPdfAnnoDrop(e, de.complete.annoDragData, xp, yp);
+ if (de.complete.annoDragData?.dragDocument && super.onInternalDrop(e, de)) return this.internalAnchorAnnoDrop(e, de.complete.annoDragData, xp, yp);
if (de.complete.docDragData?.droppedDocuments.length) return this.internalDocDrop(e, de, de.complete.docDragData, xp, yp);
return false;
}
@@ -789,6 +789,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
@action
zoom = (pointX: number, pointY: number, deltaY: number): void => {
+ if (this.Document._isGroup) return;
let deltaScale = deltaY > 0 ? (1 / 1.05) : 1.05;
if (deltaScale < 0) deltaScale = -deltaScale;
const [x, y] = this.getTransform().transformPoint(pointX, pointY);
@@ -815,10 +816,16 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
e.stopPropagation();
}
else if (this.props.active(true)) {
- e.stopPropagation();
- e.preventDefault();
- if (!e.ctrlKey && MarqueeView.DragMarquee) this.setPan(this.panX() + e.deltaX, this.panY() + e.deltaY, "None", true);
- else this.zoom(e.clientX, e.clientY, e.deltaY); // if (!this.props.isAnnotationOverlay) // bcz: do we want to zoom in on images/videos/etc?
+ if (!e.ctrlKey && MarqueeView.DragMarquee) {
+ this.setPan(this.panX() + e.deltaX, this.panY() + e.deltaY, "None", true);
+ e.stopPropagation();
+ e.preventDefault();
+ }
+ else if (!this.Document._isGroup) {
+ e.stopPropagation();
+ e.preventDefault();
+ this.zoom(e.clientX, e.clientY, e.deltaY); // if (!this.props.isAnnotationOverlay) // bcz: do we want to zoom in on images/videos/etc?
+ }
}
this.props.Document.targetScale = NumCast(this.props.Document[this.scaleFieldKey]);
@@ -876,6 +883,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
});
scaleAtPt(docpt: number[], scale: number) {
+ if (this.Document._isGroup) return;
const screenXY = this.getTransform().inverse().transformPoint(docpt[0], docpt[1]);
this.Document._viewTransition = "transform 500ms";
this.layoutDoc[this.scaleFieldKey] = scale;
@@ -886,7 +894,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
this.layoutDoc._panY = NumCast(this.layoutDoc._panY) - newpan[1];
}
- focusDocument = (doc: Doc, willZoom?: boolean, scale?: number, afterFocus?: DocAfterFocusFunc, dontCenter?: boolean, didFocus?: boolean) => {
+ focusDocument = (doc: Doc, willZoom?: boolean, scale?: number, afterFocus?: DocAfterFocusFunc, docTransform?: Transform) => {
const state = HistoryUtil.getState();
// TODO This technically isn't correct if type !== "doc", as
@@ -904,77 +912,68 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
}
SelectionManager.DeselectAll();
if (this.props.Document.scrollHeight) {
- const annotOn = Cast(doc.annotationOn, Doc) as Doc;
- let delay = 1000;
- if (!annotOn) {
- !dontCenter && this.props.focus(doc);
- afterFocus && setTimeout(afterFocus, delay);
- } else {
- const contextHgt = NumCast(annotOn._height);
- const curScroll = NumCast(this.props.Document._scrollTop);
- let scrollTo = curScroll;
- if (curScroll + contextHgt < NumCast(doc.y)) {
- scrollTo = NumCast(doc.y) + Math.max(NumCast(doc._height), 100) - contextHgt;
- } else if (curScroll > NumCast(doc.y)) {
- scrollTo = Math.max(0, NumCast(doc.y) - 50);
- }
- if (curScroll !== scrollTo || this.props.Document._viewTransition) {
- this.props.Document._scrollPreviewY = this.props.Document._scrollY = scrollTo;
- delay = Math.abs(scrollTo - curScroll) > 5 ? 1000 : 0;
- !dontCenter && this.props.focus(this.props.Document);
- afterFocus && setTimeout(afterFocus, delay);
- } else {
- !dontCenter && delay && this.props.focus(this.props.Document);
- afterFocus?.(!dontCenter && delay ? true : false);
- }
- }
-
+ this.props.focus(doc, undefined, undefined, afterFocus);
} else {
+ const xfToCollection = docTransform ?? Transform.Identity();
const layoutdoc = Doc.Layout(doc);
const savedState = { px: NumCast(this.Document._panX), py: NumCast(this.Document._panY), s: this.Document[this.scaleFieldKey], pt: this.Document._viewTransition };
-
const newState = HistoryUtil.getState();
- let newPanX = savedState.px;
- let newPanY = savedState.py;
- if (!layoutdoc.annotationOn) { // only pan and zoom to focus on a document if the document is not an annotation in an annotation overlay collection
- willZoom && this.setScaleToZoom(layoutdoc, scale);
- newPanX = (NumCast(doc.x) + doc[WidthSym]() / 2) - (this.isAnnotationOverlay ? (Doc.NativeWidth(this.props.Document)) / 2 / this.zoomScaling() : 0);
- newPanY = (NumCast(doc.y) + doc[HeightSym]() / 2) - (this.isAnnotationOverlay ? (Doc.NativeHeight(this.props.Document)) / 2 / this.zoomScaling() : 0);
- newState.initializers![this.Document[Id]] = { panX: newPanX, panY: newPanY };
+ const cantTransform = this.props.isAnnotationOverlay || this.rootDoc._isGroup;
+ const { px, py } = cantTransform ? savedState : this.setPanIntoView(layoutdoc, xfToCollection, willZoom ? scale || .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: px, panY: py };
HistoryUtil.pushState(newState);
}
+ // focus on the document in the collection
+ const didMove = !doc.z && (px !== savedState.px || py !== savedState.py);
+ const focusSpeed = didMove ? (doc.focusSpeed !== undefined ? Number(doc.focusSpeed) : 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) this.setPan(px, py, `transform ${focusSpeed}ms`, true); // docs that are floating in their collection can't be panned to from their collection -- need to propagate the pan to a parent freeform somehow
+ Doc.BrushDoc(this.rootDoc);
+ !doc.hidden && Doc.linkFollowHighlight(doc);
- if (DocListCast(this.dataDoc[this.props.fieldKey]).includes(doc)) {
- // glr: freeform transform speed can be set by adjusting presTransition field - needs a way of knowing when presentation is not active...
- if (!doc.z) this.setPan(newPanX, newPanY, doc.focusSpeed || doc.focusSpeed === 0 ? `transform ${doc.focusSpeed}ms` : "transform 500ms", true); // docs that are floating in their collection can't be panned to from their collection -- need to propagate the pan to a parent freeform somehow
- }
- Doc.BrushDoc(this.props.Document);
-
- const newDidFocus = didFocus || (newPanX !== savedState.px || newPanY !== savedState.py);
-
- const newAfterFocus = (didFocus: boolean) => {
- afterFocus && setTimeout(() => {
- // @ts-ignore
- if (afterFocus?.(!dontCenter && (didFocus || (newPanX !== savedState.px || newPanY !== savedState.py)))) {
- this.Document._panX = savedState.px;
- this.Document._panY = savedState.py;
- this.Document[this.scaleFieldKey] = savedState.s;
- this.Document._viewTransition = savedState.pt;
- }
- doc.hidden && Doc.UnHighlightDoc(doc);
- }, newPanX !== savedState.px || newPanY !== savedState.py ? 500 : 0);
- return false;
+ // focus on this collection within its parent view. the parent view after focusing determines whether to reset the view change within the collection
+ const endFocus = async (moved: boolean) => {
+ doc.hidden && Doc.UnHighlightDoc(doc);
+ const resetView = afterFocus ? await afterFocus(moved) : false;
+ if (resetView) {
+ this.Document._panX = savedState.px;
+ this.Document._panY = savedState.py;
+ this.Document[this.scaleFieldKey] = savedState.s;
+ this.Document._viewTransition = savedState.pt;
+ }
+ return resetView;
};
- this.props.focus(this.props.Document, undefined, undefined, newAfterFocus, undefined, newDidFocus);
- !doc.hidden && Doc.linkFollowHighlight(doc);
+ const xf = !cantTransform ? Transform.Identity() :
+ this.props.isAnnotationOverlay ?
+ new Transform(NumCast(this.rootDoc.x), NumCast(this.rootDoc.y), this.rootDoc[WidthSym]() / Doc.NativeWidth(this.rootDoc))
+ :
+ new Transform(NumCast(this.rootDoc.x) + this.rootDoc[WidthSym]() / 2 - NumCast(this.rootDoc._panX),
+ NumCast(this.rootDoc.y) + this.rootDoc[HeightSym]() / 2 - NumCast(this.rootDoc._panY), 1);
+ this.props.focus(cantTransform ? doc : this.rootDoc, willZoom, scale, (didFocus: boolean) =>
+ new Promise<boolean>(res => setTimeout(async () => res(await endFocus(didMove || didFocus)), focusSpeed)), xf.transform(docTransform ?? Transform.Identity()));
}
-
}
- setScaleToZoom = (doc: Doc, scale: number = 0.75) => {
- const pw = this.isAnnotationOverlay ? Doc.NativeWidth(this.props.Document) : this.props.PanelWidth();
- const ph = this.isAnnotationOverlay ? Doc.NativeHeight(this.props.Document) : this.props.PanelHeight();
- pw && ph && (this.Document[this.scaleFieldKey] = scale * Math.min(pw / NumCast(doc._width), ph / NumCast(doc._height)));
+ setPanIntoView = (doc: Doc, xf: Transform, scale?: number) => {
+ const pw = this.props.PanelWidth() / NumCast(this.layoutDoc._viewScale, 1);
+ const ph = this.props.PanelHeight() / NumCast(this.layoutDoc._viewScale, 1);
+ const pt = xf.transformPoint(NumCast(doc.x), NumCast(doc.y));
+ const pt2 = xf.transformPoint(NumCast(doc.x) + doc[WidthSym](), NumCast(doc.y) + doc[HeightSym]());
+ const bounds = { left: pt[0], right: pt2[0], top: pt[1], bot: pt2[1] };
+
+ if (scale) {
+ this.Document[this.scaleFieldKey] = scale * Math.min(this.props.PanelWidth() / Math.abs(pt2[0] - pt[0])), this.props.PanelHeight() / Math.abs(pt2[1] - pt[1]);
+ return { px: (bounds.left + bounds.right) / 2, py: (bounds.top + bounds.bot) / 2 };
+ } else {
+ const cx = NumCast(this.layoutDoc._panX);
+ const cy = NumCast(this.layoutDoc._panY);
+ const screen = { left: cx - pw / 2, right: cx + pw / 2, top: cy - ph / 2, bot: cy + ph / 2 };
+ return {
+ px: cx + Math.min(0, bounds.left - pw / 10 - screen.left) + Math.max(0, bounds.right + pw / 10 - screen.right),
+ py: cy + Math.min(0, bounds.top - ph / 10 - screen.top) + Math.max(0, bounds.bot + ph / 10 - screen.bot)
+ };
+ }
}
onChildClickHandler = () => this.props.childClickScript || ScriptCast(this.Document.onChildClick);
@@ -988,6 +987,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
pinToPres: this.props.pinToPres,
whenActiveChanged: this.props.whenActiveChanged,
parentActive: this.parentActive,
+ docViewPath: this.props.docViewPath,
DataDoc: childData,
Document: childLayout,
ContainingCollectionView: this.props.CollectionView,
@@ -1005,6 +1005,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
searchFilterDocs: this.searchFilterDocs,
focus: this.focusDocument,
styleProvider: this.getClusterColor,
+ layerProvider: this.props.layerProvider,
freezeDimensions: this.props.childFreezeDimensions,
dropAction: StrCast(this.props.Document.childDropAction) as dropActionType,
bringToFront: this.bringToFront,
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 0edbfe7a5..f7fb2b83d 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -643,7 +643,11 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
render() {
return <div className="marqueeView"
- style={{ overflow: (!this.props.ContainingCollectionView && this.props.isAnnotationOverlay) ? "visible" : StrCast(this.props.Document._overflow), cursor: MarqueeView.DragMarquee && this ? "crosshair" : "hand" }}
+ style={{
+ overflow: (!this.props.ContainingCollectionView && this.props.isAnnotationOverlay) ? "visible" :
+ StrCast(this.props.Document._overflow),
+ cursor: MarqueeView.DragMarquee && this ? "crosshair" : "hand"
+ }}
onDragOver={e => e.preventDefault()}
onScroll={(e) => e.currentTarget.scrollTop = e.currentTarget.scrollLeft = 0} onClick={this.onClick} onPointerDown={this.onPointerDown}>
{this._visible ? this.marqueeDiv : null}
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
index 85013b960..e51417f64 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
@@ -6,7 +6,7 @@ import { documentSchema } from '../../../../fields/documentSchemas';
import { List } from '../../../../fields/List';
import { makeInterface } from '../../../../fields/Schema';
import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
-import { returnFalse } from '../../../../Utils';
+import { returnFalse, emptyPath, returnEmptyDoclist } from '../../../../Utils';
import { DragManager, dropActionType } from '../../../util/DragManager';
import { Transform } from '../../../util/Transform';
import { undoBatch } from '../../../util/UndoManager';
@@ -217,6 +217,8 @@ export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocu
Document={layout}
DataDoc={layout.resolvedDataDoc as Doc}
styleProvider={this.props.styleProvider}
+ layerProvider={undefined}
+ docViewPath={returnEmptyDoclist}
LayoutTemplate={this.props.childLayoutTemplate}
LayoutTemplateString={this.props.childLayoutString}
freezeDimensions={this.props.childFreezeDimensions}
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
index 4f5c8af95..d61a2bb72 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
@@ -6,7 +6,7 @@ import { documentSchema } from '../../../../fields/documentSchemas';
import { List } from '../../../../fields/List';
import { makeInterface } from '../../../../fields/Schema';
import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
-import { returnFalse } from '../../../../Utils';
+import { returnFalse, emptyPath, returnEmptyDoclist } from '../../../../Utils';
import { DragManager, dropActionType } from '../../../util/DragManager';
import { Transform } from '../../../util/Transform';
import { undoBatch } from '../../../util/UndoManager';
@@ -217,6 +217,8 @@ export class CollectionMultirowView extends CollectionSubView(MultirowDocument)
Document={layout}
DataDoc={layout.resolvedDataDoc as Doc}
styleProvider={this.props.styleProvider}
+ layerProvider={undefined}
+ docViewPath={returnEmptyDoclist}
LayoutTemplate={this.props.childLayoutTemplate}
LayoutTemplateString={this.props.childLayoutString}
freezeDimensions={this.props.childFreezeDimensions}
diff --git a/src/client/views/linking/LinkEditor.tsx b/src/client/views/linking/LinkEditor.tsx
index 435b9d904..f74b422d3 100644
--- a/src/client/views/linking/LinkEditor.tsx
+++ b/src/client/views/linking/LinkEditor.tsx
@@ -4,7 +4,6 @@ import { action, computed, observable } from "mobx";
import { observer } from "mobx-react";
import { Doc } from "../../../fields/Doc";
import { DateCast, StrCast } from "../../../fields/Types";
-import { Utils } from "../../../Utils";
import { LinkManager } from "../../util/LinkManager";
import { undoBatch } from "../../util/UndoManager";
import './LinkEditor.scss';
@@ -21,31 +20,41 @@ interface LinkEditorProps {
export class LinkEditor extends React.Component<LinkEditorProps> {
@observable description = StrCast(LinkManager.currentLink?.description);
+ @observable relationship = StrCast(LinkManager.currentLink?.linkRelationship);
@observable openDropdown: boolean = false;
@observable showInfo: boolean = false;
@computed get infoIcon() { if (this.showInfo) { return "chevron-up"; } return "chevron-down"; }
@observable private buttonColor: string = "";
-
+ @observable private relationshipButtonColor: string = "";
//@observable description = this.props.linkDoc.description ? StrCast(this.props.linkDoc.description) : "DESCRIPTION";
- @undoBatch @action
+ @undoBatch
deleteLink = (): void => {
LinkManager.Instance.deleteLink(this.props.linkDoc);
this.props.showLinks();
}
- @undoBatch @action
- setDescripValue = (value: string) => {
+ @undoBatch
+ setRelationshipValue = action((value: string) => {
+ if (LinkManager.currentLink) {
+ LinkManager.currentLink.linkRelationship = value;
+ this.relationshipButtonColor = "rgb(62, 133, 55)";
+ setTimeout(action(() => this.relationshipButtonColor = ""), 750);
+ return true;
+ }
+ });
+
+ @undoBatch
+ setDescripValue = action((value: string) => {
if (LinkManager.currentLink) {
LinkManager.currentLink.description = value;
this.buttonColor = "rgb(62, 133, 55)";
setTimeout(action(() => this.buttonColor = ""), 750);
return true;
}
- }
+ });
- @action
onKey = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Enter") {
this.setDescripValue(this.description);
@@ -53,22 +62,48 @@ export class LinkEditor extends React.Component<LinkEditorProps> {
}
}
- @action
- onDown = () => {
- this.setDescripValue(this.description);
+ onRelationshipKey = (e: React.KeyboardEvent<HTMLInputElement>) => {
+ if (e.key === "Enter") {
+ this.setRelationshipValue(this.relationship);
+ document.getElementById('input')?.blur();
+ }
}
+ onDown = () => this.setDescripValue(this.description);
+ onRelationshipDown = () => this.setRelationshipValue(this.description);
+
@action
- handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
- this.description = e.target.value;
- }
+ handleChange = (e: React.ChangeEvent<HTMLInputElement>) => { this.description = e.target.value; }
+ @action
+ handleRelationshipChange = (e: React.ChangeEvent<HTMLInputElement>) => { this.relationship = e.target.value; }
+ @computed
+ get editRelationship() {
+ return <div className="linkEditor-description">
+ <div className="linkEditor-description-label">Link Relationship:</div>
+ <div className="linkEditor-description-input">
+ <div className="linkEditor-description-editing">
+ <input
+ style={{ width: "100%" }}
+ id="input"
+ value={this.relationship}
+ placeholder={"enter link label"}
+ // color={"rgb(88, 88, 88)"}
+ onKeyDown={this.onRelationshipKey}
+ onChange={this.handleRelationshipChange}
+ ></input>
+ </div>
+ <div className="linkEditor-description-add-button"
+ style={{ background: this.relationshipButtonColor }}
+ onPointerDown={this.onRelationshipDown}>Set</div>
+ </div>
+ </div>;
+ }
@computed
get editDescription() {
return <div className="linkEditor-description">
- <div className="linkEditor-description-label">
- Link Label:</div>
+ <div className="linkEditor-description-label">Link Description:</div>
<div className="linkEditor-description-input">
<div className="linkEditor-description-editing">
<input
@@ -84,13 +119,12 @@ export class LinkEditor extends React.Component<LinkEditorProps> {
<div className="linkEditor-description-add-button"
style={{ background: this.buttonColor }}
onPointerDown={this.onDown}>Set</div>
- </div></div>;
+ </div>
+ </div>;
}
@action
- changeDropdown = () => {
- this.openDropdown = !this.openDropdown;
- }
+ changeDropdown = () => { this.openDropdown = !this.openDropdown; }
@undoBatch
changeFollowBehavior = action((follow: string) => {
@@ -101,8 +135,7 @@ export class LinkEditor extends React.Component<LinkEditorProps> {
@computed
get followingDropdown() {
return <div className="linkEditor-followingDropdown">
- <div className="linkEditor-followingDropdown-label">
- Follow Behavior:</div>
+ <div className="linkEditor-followingDropdown-label">Follow Behavior:</div>
<div className="linkEditor-followingDropdown-dropdown">
<div className="linkEditor-followingDropdown-header"
onPointerDown={this.changeDropdown}>
@@ -157,9 +190,7 @@ export class LinkEditor extends React.Component<LinkEditorProps> {
}
@action
- changeInfo = () => {
- this.showInfo = !this.showInfo;
- }
+ changeInfo = () => { this.showInfo = !this.showInfo; }
render() {
const destination = LinkManager.getOppositeAnchor(this.props.linkDoc, this.props.sourceDoc);
@@ -186,8 +217,9 @@ export class LinkEditor extends React.Component<LinkEditorProps> {
{DateCast(this.props.linkDoc.creationDate).toString()}</div> : null}</div>
</div> : null}
- <div>{this.editDescription}</div>
- <div>{this.followingDropdown}</div>
+ {this.editDescription}
+ {this.editRelationship}
+ {this.followingDropdown}
</div>
);
diff --git a/src/client/views/linking/LinkMenu.scss b/src/client/views/linking/LinkMenu.scss
index 0e03b46db..a90bf8b0a 100644
--- a/src/client/views/linking/LinkMenu.scss
+++ b/src/client/views/linking/LinkMenu.scss
@@ -4,29 +4,23 @@
width: auto;
height: auto;
position: absolute;
- z-index: 999;
-
- .linkMenu-list {
+ z-index: 2001;
+ .linkMenu-list,
+ .linkMenu-listEditor
+ {
display: inline-block;
-
+ position: relative;
border: 1px solid black;
-
box-shadow: 3px 3px 1.5px grey;
+ background: white;
+ min-width: 170px;
max-height: 170px;
overflow-y: scroll;
- position: relative;
z-index: 10;
- background: white;
- min-width: 170px;
- //border-radius: 5px;
- //padding-top: 6.5px;
- //padding-bottom: 6.5px;
- //padding-left: 6.5px;
- //padding-right: 2px;
- //width: calc(auto + 50px);
-
+ }
+ .linkMenu-list {
white-space: nowrap;
overflow-x: hidden;
width: 240px;
@@ -40,22 +34,6 @@
scrollbar-color: rgb(201, 239, 252);
}
}
-
- .linkMenu-listEditor {
-
- display: inline-block;
-
- border: 1px solid black;
-
- box-shadow: 3px 3px 1.5px grey;
-
- max-height: 170px;
- overflow-y: scroll;
- position: relative;
- z-index: 10;
- background: white;
- min-width: 170px;
- }
}
.linkMenu-group {
diff --git a/src/client/views/linking/LinkMenu.tsx b/src/client/views/linking/LinkMenu.tsx
index b32022024..c7888c5ee 100644
--- a/src/client/views/linking/LinkMenu.tsx
+++ b/src/client/views/linking/LinkMenu.tsx
@@ -13,49 +13,27 @@ import React = require("react");
interface Props {
docView: DocumentView;
changeFlyout: () => void;
- docprops: DocumentViewSharedProps;
}
@observer
export class LinkMenu extends React.Component<Props> {
-
- @observable private _editingLink?: Doc;
- @observable private _linkMenuRef = React.createRef<HTMLDivElement>();
private _editorRef = React.createRef<HTMLDivElement>();
+ @observable _editingLink?: Doc;
+ @observable _linkMenuRef = React.createRef<HTMLDivElement>();
- //@observable private _numLinks: number = 0;
-
- // @computed get overflow() {
- // if (this._numLinks) {
- // return "scroll";
- // }
- // return "auto";
- // }
-
- @action
- onClick = (e: PointerEvent) => {
-
- LinkDocPreview.LinkInfo = undefined;
-
-
- if (this._linkMenuRef && !this._linkMenuRef.current?.contains(e.target as any)) {
- if (this._editorRef && !this._editorRef.current?.contains(e.target as any)) {
- DocumentLinksButton.EditLink = undefined;
- }
- }
- }
- @action
- componentDidMount() {
- this._editingLink = undefined;
- document.addEventListener("pointerdown", this.onClick);
+ @computed get position() {
+ return ((dv) => ({ x: dv?.left || 0, y: dv?.top || 0, r: dv?.right || 0, b: dv?.bottom || 0 }))(this.props.docView.getBounds());
}
- componentWillUnmount() {
- document.removeEventListener("pointerdown", this.onClick);
- }
+ componentDidMount() { document.addEventListener("pointerdown", this.onPointerDown); }
+ componentWillUnmount() { document.removeEventListener("pointerdown", this.onPointerDown); }
- clearAllLinks = () => {
- LinkManager.Instance.deleteAllLinksOnAnchor(this.props.docView.props.Document);
+ onPointerDown = (e: PointerEvent) => {
+ LinkDocPreview.Clear();
+ if (!this._linkMenuRef.current?.contains(e.target as any) &&
+ !this._editorRef.current?.contains(e.target as any)) {
+ DocumentLinksButton.ClearLinkEditor();
+ }
}
renderAllGroups = (groups: Map<string, Array<Doc>>): Array<JSX.Element> => {
@@ -66,32 +44,23 @@ export class LinkMenu extends React.Component<Props> {
sourceDoc={this.props.docView.props.Document}
group={group[1]}
groupType={group[0]}
- showEditor={action(linkDoc => this._editingLink = linkDoc)}
- docprops={this.props.docprops} />);
+ showEditor={action(linkDoc => this._editingLink = linkDoc)} />);
return linkItems.length ? linkItems : [<p key="">No links have been created yet. Drag the linking button onto another document to create a link.</p>];
}
- @computed
- get position() {
- const docView = this.props.docView.getBounds();
- return { x: docView?.left || 0, y: docView?.top || 0, r: docView?.right || 0, b: docView?.bottom || 0 };
- }
-
render() {
const sourceDoc = this.props.docView.props.Document;
- const groups: Map<string, Doc[]> = LinkManager.Instance.getRelatedGroupedLinks(sourceDoc);
- return <div className="linkMenu" style={{ left: this.position.x, top: this.props.docView.topMost ? undefined : this.position.b + 15, bottom: this.props.docView.topMost ? 20 : undefined }} ref={this._linkMenuRef} >
- {!this._editingLink ?
- <div className="linkMenu-list" >
- {this.renderAllGroups(groups)}
- </div> :
+ return <div className="linkMenu" ref={this._linkMenuRef}
+ style={{ left: this.position.x, top: this.props.docView.topMost ? undefined : this.position.b + 15, bottom: this.props.docView.topMost ? 20 : undefined }}
+ >
+ {this._editingLink ?
<div className="linkMenu-listEditor">
- <LinkEditor sourceDoc={this.props.docView.props.Document} linkDoc={this._editingLink}
- showLinks={action(() => this._editingLink = undefined)} />
- </div>
- }
-
+ <LinkEditor sourceDoc={sourceDoc} linkDoc={this._editingLink} showLinks={action(() => this._editingLink = undefined)} />
+ </div> :
+ <div className="linkMenu-list" >
+ {this.renderAllGroups(LinkManager.Instance.getRelatedGroupedLinks(sourceDoc))}
+ </div>}
</div>;
}
} \ No newline at end of file
diff --git a/src/client/views/linking/LinkMenuGroup.tsx b/src/client/views/linking/LinkMenuGroup.tsx
index 7db908393..74af78234 100644
--- a/src/client/views/linking/LinkMenuGroup.tsx
+++ b/src/client/views/linking/LinkMenuGroup.tsx
@@ -3,7 +3,7 @@ import { Doc } from "../../../fields/Doc";
import { Id } from "../../../fields/FieldSymbols";
import { Cast } from "../../../fields/Types";
import { LinkManager } from "../../util/LinkManager";
-import { DocumentView, DocumentViewSharedProps } from "../nodes/DocumentView";
+import { DocumentView } from "../nodes/DocumentView";
import './LinkMenu.scss';
import { LinkMenuItem } from "./LinkMenuItem";
import React = require("react");
@@ -13,14 +13,11 @@ interface LinkMenuGroupProps {
group: Doc[];
groupType: string;
showEditor: (linkDoc: Doc) => void;
- docprops: DocumentViewSharedProps;
docView: DocumentView;
}
@observer
export class LinkMenuGroup extends React.Component<LinkMenuGroupProps> {
-
- private _drag = React.createRef<HTMLDivElement>();
private _menuRef = React.createRef<HTMLDivElement>();
render() {
@@ -31,7 +28,6 @@ export class LinkMenuGroup extends React.Component<LinkMenuGroupProps> {
if (destination && this.props.sourceDoc) {
return <LinkMenuItem key={linkDoc[Id]}
groupType={this.props.groupType}
- docprops={this.props.docprops}
docView={this.props.docView}
linkDoc={linkDoc}
sourceDoc={this.props.sourceDoc}
@@ -43,11 +39,9 @@ export class LinkMenuGroup extends React.Component<LinkMenuGroupProps> {
return (
<div className="linkMenu-group" ref={this._menuRef}>
-
<div className="linkMenu-group-name">
- <p ref={this._drag} className={this.props.groupType === "*" || this.props.groupType === "" ? "" : "expand-one"} > {this.props.groupType}:</p>
+ <p className={this.props.groupType === "*" || this.props.groupType === "" ? "" : "expand-one"} > {this.props.groupType}:</p>
</div>
-
<div className="linkMenu-group-wrapper">
{groupItems}
</div>
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index 19ef03a31..53a7ae9ab 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -6,7 +6,7 @@ import { observer } from "mobx-react";
import { Doc, DocListCast } from '../../../fields/Doc';
import { Cast, StrCast } from '../../../fields/Types';
import { WebField } from '../../../fields/URLField';
-import { emptyFunction, setupMoveUpEvents } from '../../../Utils';
+import { emptyFunction, setupMoveUpEvents, returnFalse } from '../../../Utils';
import { DocumentType } from '../../documents/DocumentTypes';
import { DocumentManager } from '../../util/DocumentManager';
import { DragManager } from '../../util/DragManager';
@@ -18,6 +18,7 @@ import { DocumentView, DocumentViewSharedProps } from '../nodes/DocumentView';
import { LinkDocPreview } from '../nodes/LinkDocPreview';
import './LinkMenuItem.scss';
import React = require("react");
+import { setup } from 'mocha';
interface LinkMenuItemProps {
@@ -27,7 +28,6 @@ interface LinkMenuItemProps {
sourceDoc: Doc;
destinationDoc: Doc;
showEditor: (linkDoc: Doc) => void;
- docprops: DocumentViewSharedProps;
menuRef: React.Ref<HTMLDivElement>;
}
@@ -69,9 +69,6 @@ export async function StartLinkTargetsDrag(dragEle: HTMLElement, docView: Docume
@observer
export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
private _drag = React.createRef<HTMLDivElement>();
- private _downX = 0;
- private _downY = 0;
- private _eleClone: any;
_editRef = React.createRef<HTMLDivElement>();
_buttonRef = React.createRef<HTMLDivElement>();
@@ -81,82 +78,45 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
onEdit = (e: React.PointerEvent): void => {
LinkManager.currentLink = this.props.linkDoc;
- setupMoveUpEvents(this, e, this.editMoved, emptyFunction, () => this.props.showEditor(this.props.linkDoc));
- }
-
- editMoved = (e: PointerEvent) => {
- const dragData = new DragManager.DocumentDragData([this.props.linkDoc]);
- DragManager.StartDocumentDrag([this._editRef.current!], dragData, e.x, e.y);
- return true;
+ setupMoveUpEvents(this, e, e => {
+ DragManager.StartDocumentDrag([this._editRef.current!], new DragManager.DocumentDragData([this.props.linkDoc]), e.x, e.y);
+ return true;
+ }, emptyFunction, () => this.props.showEditor(this.props.linkDoc));
}
- @action
onLinkButtonDown = (e: React.PointerEvent): void => {
- this._downX = e.clientX;
- this._downY = e.clientY;
- this._eleClone = this._drag.current!.cloneNode(true);
- e.stopPropagation();
- document.removeEventListener("pointermove", this.onLinkButtonMoved);
- document.addEventListener("pointermove", this.onLinkButtonMoved);
- document.removeEventListener("pointerup", this.onLinkButtonUp);
- document.addEventListener("pointerup", this.onLinkButtonUp);
-
- if (this._buttonRef && !!!this._buttonRef.current?.contains(e.target as any)) {
- LinkDocPreview.LinkInfo = undefined;
- }
- }
-
- onLinkButtonUp = (e: PointerEvent): void => {
- document.removeEventListener("pointermove", this.onLinkButtonMoved);
- document.removeEventListener("pointerup", this.onLinkButtonUp);
- LinkManager.FollowLink(this.props.linkDoc, this.props.sourceDoc, this.props.docView.props, false);
-
- e.stopPropagation();
- }
-
- onLinkButtonMoved = async (e: PointerEvent) => {
- if (this._drag.current !== null && Math.abs((e.clientX - this._downX) * (e.clientX - this._downX) + (e.clientY - this._downY) * (e.clientY - this._downY)) > 5) {
- document.removeEventListener("pointermove", this.onLinkButtonMoved);
- document.removeEventListener("pointerup", this.onLinkButtonUp);
-
- this._eleClone.style.transform = `translate(${e.x}px, ${e.y}px)`;
- StartLinkTargetsDrag(this._eleClone, this.props.docView, e.x, e.y, this.props.sourceDoc, [this.props.linkDoc]);
- }
- e.stopPropagation();
+ setupMoveUpEvents(this, e,
+ e => {
+ const eleClone: any = this._drag.current!.cloneNode(true);
+ eleClone.style.transform = `translate(${e.x}px, ${e.y}px)`;
+ StartLinkTargetsDrag(eleClone, this.props.docView, e.x, e.y, this.props.sourceDoc, [this.props.linkDoc]);
+ DocumentLinksButton.ClearLinkEditor();
+ return true;
+ },
+ emptyFunction,
+ () => {
+ DocumentLinksButton.ClearLinkEditor();
+ LinkManager.FollowLink(this.props.linkDoc, this.props.sourceDoc, this.props.docView.props, false);
+ });
}
- @undoBatch
- @action
deleteLink = (e: React.PointerEvent): void => {
- this.props.linkDoc.linksToAnnotation && Hypothesis.deleteLink(this.props.linkDoc, this.props.sourceDoc, this.props.destinationDoc);
- LinkManager.Instance.deleteLink(this.props.linkDoc);
- e.stopPropagation();
-
- runInAction(() => {
- LinkDocPreview.LinkInfo = undefined;
- DocumentLinksButton.EditLink = undefined;
- });
+ setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => {
+ this.props.linkDoc.linksToAnnotation && Hypothesis.deleteLink(this.props.linkDoc, this.props.sourceDoc, this.props.destinationDoc);
+ LinkManager.Instance.deleteLink(this.props.linkDoc);
+ })));
}
- @undoBatch
- @action
autoMove = (e: React.PointerEvent) => {
- e.stopPropagation();
- this.props.linkDoc.linkAutoMove = !this.props.linkDoc.linkAutoMove;
+ setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => this.props.linkDoc.linkAutoMove = !this.props.linkDoc.linkAutoMove)));
}
- @undoBatch
- @action
showLink = (e: React.PointerEvent) => {
- e.stopPropagation();
- this.props.linkDoc.linkDisplay = !this.props.linkDoc.linkDisplay;
+ setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => this.props.linkDoc.linkDisplay = !this.props.linkDoc.linkDisplay)));
}
- @undoBatch
- @action
showAnchor = (e: React.PointerEvent) => {
- e.stopPropagation();
- this.props.linkDoc.hidden = !this.props.linkDoc.hidden;
+ setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => this.props.linkDoc.hidden = !this.props.linkDoc.hidden)));
}
render() {
@@ -178,13 +138,14 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
<div className={"linkMenu-item-content expand-two"}>
<div ref={this._drag} className="linkMenu-name" //title="drag to view target. click to customize."
- onPointerLeave={action(() => LinkDocPreview.LinkInfo = undefined)}
- onPointerEnter={action(e => this.props.linkDoc && (LinkDocPreview.LinkInfo = {
- docprops: this.props.docprops,
+ onPointerLeave={LinkDocPreview.Clear}
+ onPointerEnter={e => this.props.linkDoc && LinkDocPreview.SetLinkInfo({
+ docProps: this.props.docView.props,
linkSrc: this.props.sourceDoc,
linkDoc: this.props.linkDoc,
- Location: [e.clientX, e.clientY + 20]
- }))}
+ showHeader: false,
+ location: [e.clientX, e.clientY + 20]
+ })}
onPointerDown={this.onLinkButtonDown}>
<div className="linkMenu-text">
@@ -203,26 +164,26 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
<div className="linkMenu-item-buttons" ref={this._buttonRef} >
<Tooltip title={<><div className="dash-tooltip">{this.props.linkDoc.hidden ? "Show Anchor" : "Hide Anchor"}</div></>}>
- <div className="button" ref={this._editRef} onPointerDown={this.showAnchor}>
+ <div className="button" ref={this._editRef} onPointerDown={this.showAnchor} onClick={e => e.stopPropagation()}>
<FontAwesomeIcon className="fa-icon" icon={this.props.linkDoc.hidden ? "eye-slash" : "eye"} size="sm" /></div>
</Tooltip>
<Tooltip title={<><div className="dash-tooltip">{!this.props.linkDoc.linkDisplay ? "Show link" : "Hide link"}</div></>}>
- <div className="button" ref={this._editRef} onPointerDown={this.showLink}>
+ <div className="button" ref={this._editRef} onPointerDown={this.showLink} onClick={e => e.stopPropagation()}>
<FontAwesomeIcon className="fa-icon" icon={!this.props.linkDoc.linkDisplay ? "eye-slash" : "eye"} size="sm" /></div>
</Tooltip>
<Tooltip title={<><div className="dash-tooltip">{!this.props.linkDoc.linkAutoMove ? "Auto move dot" : "Freeze dot position"}</div></>}>
- <div className="button" ref={this._editRef} onPointerDown={this.autoMove}>
+ <div className="button" ref={this._editRef} onPointerDown={this.autoMove} onClick={e => e.stopPropagation()}>
<FontAwesomeIcon className="fa-icon" icon={this.props.linkDoc.linkAutoMove ? "play" : "pause"} size="sm" /></div>
</Tooltip>
<Tooltip title={<><div className="dash-tooltip">Edit Link</div></>}>
- <div className="button" ref={this._editRef} onPointerDown={this.onEdit}>
+ <div className="button" ref={this._editRef} onPointerDown={this.onEdit} onClick={e => e.stopPropagation()}>
<FontAwesomeIcon className="fa-icon" icon="edit" size="sm" /></div>
</Tooltip>
<Tooltip title={<><div className="dash-tooltip">Delete Link</div></>}>
- <div className="button" onPointerDown={this.deleteLink}>
+ <div className="button" onPointerDown={this.deleteLink} onClick={e => e.stopPropagation()}>
<FontAwesomeIcon className="fa-icon" icon="trash" size="sm" /></div>
</Tooltip>
</div>
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index 692eaae66..e24a671d0 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -107,7 +107,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
//this._disposers.scrubbing = reaction(() => AudioBox._scrubTime, (time) => this.layoutDoc.playOnSelect && this.playFromTime(AudioBox._scrubTime));
this._disposers.triggerAudio = reaction(
- () => !LinkDocPreview.TargetDoc && !FormattedTextBoxComment.linkDoc && this.props.renderDepth !== -1 ? NumCast(this.Document._triggerAudio, null) : undefined,
+ () => !LinkDocPreview.LinkInfo && this.props.renderDepth !== -1 ? NumCast(this.Document._triggerAudio, null) : undefined,
start => start !== undefined && setTimeout(() => {
this.playFrom(start);
setTimeout(() => {
@@ -119,7 +119,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
);
this._disposers.audioStop = reaction(
- () => this.props.renderDepth !== -1 && !LinkDocPreview.TargetDoc && !FormattedTextBoxComment.linkDoc ? Cast(this.Document._audioStop, "number", null) : undefined,
+ () => this.props.renderDepth !== -1 && !LinkDocPreview.LinkInfo ? Cast(this.Document._audioStop, "number", null) : undefined,
audioStop => audioStop !== undefined && setTimeout(() => {
this.Pause();
setTimeout(() => this.Document._audioStop = undefined, 10);
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 09d89170c..4b0422ed3 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -23,7 +23,7 @@ import React = require("react");
export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
dataProvider?: (doc: Doc, replica: string) => { x: number, y: number, zIndex?: number, opacity?: number, highlight?: boolean, z: number, transition?: string } | undefined;
sizeProvider?: (doc: Doc, replica: string) => { width: number, height: number } | undefined;
- layerProvider?: (doc: Doc, assign?: boolean) => boolean;
+ layerProvider: ((doc: Doc, assign?: boolean) => boolean) | undefined;
zIndex?: number;
highlight?: boolean;
jitterRotation: number;
@@ -162,6 +162,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
...this.props,
CollectionFreeFormDocumentView: this.returnThis,
styleProvider: this.styleProvider,
+ layerProvider: this.props.layerProvider,
ScreenToLocalTransform: this.screenToLocalTransform,
PanelWidth: this.panelWidth,
PanelHeight: this.panelHeight,
diff --git a/src/client/views/nodes/DocHolderBox.tsx b/src/client/views/nodes/DocHolderBox.tsx
index 533a4931a..765751a65 100644
--- a/src/client/views/nodes/DocHolderBox.tsx
+++ b/src/client/views/nodes/DocHolderBox.tsx
@@ -124,6 +124,8 @@ export class DocHolderBox extends ViewBoxAnnotatableComponent<FieldViewProps, Do
ContainingCollectionView={this as any} // bcz: hack! need to pass a prop that can be used to select the container (ie, 'this') when the up selector in document decorations is clicked. currently, the up selector allows only a containing collection to be selected
ContainingCollectionDoc={undefined}
styleProvider={this.props.styleProvider}
+ layerProvider={this.props.layerProvider}
+ docViewPath={this.props.docViewPath}
LayoutTemplateString={layoutTemplate}
LayoutTemplate={this.layoutTemplateDoc}
rootSelected={this.props.isSelected}
@@ -150,6 +152,8 @@ export class DocHolderBox extends ViewBoxAnnotatableComponent<FieldViewProps, Do
ContainingCollectionView={this as any} // bcz: hack! need to pass a prop that can be used to select the container (ie, 'this') when the up selector in document decorations is clicked. currently, the up selector allows only a containing collection to be selected
ContainingCollectionDoc={undefined}
styleProvider={this.props.styleProvider}
+ layerProvider={this.props.layerProvider}
+ docViewPath={this.props.docViewPath}
ignoreAutoHeight={true}
LayoutTemplateString={layoutTemplate}
LayoutTemplate={this.layoutTemplateDoc}
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index f969bee85..4b4720d58 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -111,7 +111,6 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & Fo
scaling?: () => number,
layoutKey: string,
hideOnLeave?: boolean,
- makeLink?: () => Opt<Doc>, // function to call when a link is made
}> {
@computed get layout(): string {
TraceMobx();
@@ -141,16 +140,17 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & Fo
CreateBindings(onClick: Opt<ScriptField>, onInput: Opt<ScriptField>): JsxBindings {
const docOnlyProps = [ // these are the properties in DocumentViewProps that need to be removed to pass on only DocumentSharedViewProps to the FieldViews
"freezeDimensions",
+ "hideResizeHandles",
"hideTitle",
"treeViewDoc",
- "dragDivName",
"contentPointerEvents",
"radialMenu",
"LayoutTemplateString",
"LayoutTemplate",
+ "dontCenter",
"ContentScaling",
- "contentFittingScaling",
"contextMenuItems",
+ "onClick",
"onDoubleClick",
"onPointerDown",
"onPointerUp",
@@ -166,7 +166,11 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & Fo
return { props: list };
}
- render() {
+ componentWillUpdate(oldProps: any, newState: any) {
+ // console.log("willupdate", oldProps, this.props); // bcz: if you get a message saying something invalidated because reactive props changed, then this method allows you to figure out which prop changed
+ }
+
+ @computed get renderData() {
TraceMobx();
let layoutFrame = this.layout;
@@ -201,13 +205,18 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & Fo
};
const onClick = makeFuncProp("onClick");
const onInput = makeFuncProp("onInput");
-
const bindings = this.CreateBindings(onClick, onInput);
- // layoutFrame = splits.length > 1 ? splits[0] + splits[1].replace(/{([^{}]|(?R))*}/, replacer4) : ""; // might have been more elegant if javascript supported recursive patterns
+ return { bindings, layoutFrame };
+ }
+
+ render() {
+ TraceMobx();
+ const { bindings, layoutFrame } = this.renderData;
+
return (this.props.renderDepth > 12 || !layoutFrame || !this.layoutDoc || GetEffectiveAcl(this.layoutDoc) === AclPrivate) ? (null) :
<ObserverJsxParser
key={42}
- blacklistedAttrs={[]}
+ blacklistedAttrs={emptyPath}
renderInWrapper={false}
components={{
FormattedTextBox, ImageBox, DirectoryImportBox, FontIconBox, LabelBox, SliderBox, FieldView,
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
index 2cac2d0b8..13a6c9df8 100644
--- a/src/client/views/nodes/DocumentLinksButton.tsx
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -33,16 +33,16 @@ interface DocumentLinksButtonProps {
@observer
export class DocumentLinksButton extends React.Component<DocumentLinksButtonProps, {}> {
private _linkButton = React.createRef<HTMLDivElement>();
-
@observable public static StartLink: Doc | undefined;
@observable public static StartLinkView: DocumentView | undefined;
@observable public static AnnotationId: string | undefined;
@observable public static AnnotationUri: string | undefined;
- @observable public static EditLink: DocumentView | undefined;
+ @observable public static LinkEditorDocView: DocumentView | undefined;
@observable public static invisibleWebDoc: Opt<Doc>;
public static invisibleWebRef = React.createRef<HTMLDivElement>();
+ @action public static ClearLinkEditor() { DocumentLinksButton.LinkEditorDocView = undefined; }
@action @undoBatch
onLinkButtonMoved = (e: PointerEvent) => {
if (this.props.InMenu && this.props.StartLink) {
@@ -58,8 +58,6 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
// however, the dropped document isn't so accessible. What we do is set the newly created link document on the documentView
// The documentView passes a function prop returning this link doc to its descendants who can react to changes to it.
dropEv.linkDragData?.linkDropCallback?.(dropEv as { linkDocument: Doc }); // bcz: typescript can't figure out that this is valid even though we tested dropEv.linkDocument above
- runInAction(() => this.props.View.LinkBeingCreated = dropEv.linkDocument);
- setTimeout(action(() => this.props.View.LinkBeingCreated = undefined), 0);
}
linkDrag?.end();
},
@@ -85,7 +83,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
DocumentLinksButton.StartLinkView = this.props.View;
}
} else if (!this.props.InMenu) {
- DocumentLinksButton.EditLink = this.props.View;
+ DocumentLinksButton.LinkEditorDocView = this.props.View;
}
}));
}
@@ -105,7 +103,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
//action(() => Doc.BrushDoc(this.props.View.Document));
} else if (!this.props.InMenu) {
- DocumentLinksButton.EditLink = this.props.View;
+ DocumentLinksButton.LinkEditorDocView = this.props.View;
}
}
@@ -118,7 +116,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
DocumentLinksButton.AnnotationId = undefined;
} else if (DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== this.props.View.props.Document) {
const sourceDoc = DocumentLinksButton.StartLink;
- const targetDoc = this.props.View.props.Document;
+ const targetDoc = this.props.View.ComponentView?.getAnchor?.() || this.props.View.Document;
const linkDoc = DocUtils.MakeLink({ doc: sourceDoc }, { doc: targetDoc }, "long drag");
LinkManager.currentLink = linkDoc;
@@ -163,15 +161,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
endLink = endLinkView?.docView?._componentView?.getAnchor?.() || endLink;
startLink = DocumentLinksButton.StartLinkView?.docView?._componentView?.getAnchor?.() || startLink;
const linkDoc = DocUtils.MakeLink({ doc: startLink }, { doc: endLink }, DocumentLinksButton.AnnotationId ? "hypothes.is annotation" : "long drag", undefined, undefined, true);
- // this notifies any of the subviews that a document is made so that they can make finer-grained hyperlinks (). see note above in onLInkButtonMoved
- if (endLinkView) {
- endLinkView.LinkBeingCreated = linkDoc;
- DocumentLinksButton.StartLinkView && (DocumentLinksButton.StartLinkView.LinkBeingCreated = linkDoc);
- setTimeout(action(() => {
- DocumentLinksButton.StartLinkView && (DocumentLinksButton.StartLinkView.LinkBeingCreated = undefined);
- endLinkView.LinkBeingCreated = undefined;
- }), 0);
- }
+
LinkManager.currentLink = linkDoc;
if (DocumentLinksButton.AnnotationId && DocumentLinksButton.AnnotationUri) { // if linking from a Hypothes.is annotation
@@ -274,7 +264,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
{this.linkButtonInner}
</Tooltip>
:
- !DocumentLinksButton.EditLink && !this.props.InMenu ?
+ !DocumentLinksButton.LinkEditorDocView && !this.props.InMenu ?
<Tooltip title={<><div className="dash-tooltip">{title}</div></>}>
{this.linkButtonInner}
</Tooltip>
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index b653b35bc..2c418e499 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1,6 +1,6 @@
import { action, computed, observable, runInAction } from "mobx";
import { observer } from "mobx-react";
-import { AclAdmin, AclEdit, AclPrivate, DataSym, Doc, DocListCast, Field, Opt, StrListCast } from "../../../fields/Doc";
+import { AclAdmin, AclEdit, AclPrivate, DataSym, Doc, DocListCast, Field, Opt, StrListCast, HeightSym } from "../../../fields/Doc";
import { Document } from '../../../fields/documentSchemas';
import { Id } from '../../../fields/FieldSymbols';
import { InkTool } from '../../../fields/InkField';
@@ -43,10 +43,19 @@ import React = require("react");
import { List } from '../../../fields/List';
import { Tooltip } from '@material-ui/core';
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { LinkDocPreview } from "./LinkDocPreview";
-export type DocAfterFocusFunc = (notFocused: boolean) => boolean;
-export type DocFocusFunc = (doc: Doc, willZoom?: boolean, scale?: number, afterFocus?: DocAfterFocusFunc, dontCenter?: boolean, focused?: boolean) => void;
+export type DocAfterFocusFunc = (notFocused: boolean) => Promise<boolean>;
+export type DocFocusFunc = (doc: Doc, willZoom?: boolean, scale?: number, afterFocus?: DocAfterFocusFunc, docTransform?: Transform) => void;
export type StyleProviderFunc = (doc: Opt<Doc>, props: Opt<DocumentViewProps | FieldViewProps>, property: string) => any;
+export interface DocComponentView {
+ getAnchor: () => Doc;
+ scrollFocus?: (doc: Doc, smooth: boolean, afterFocus?: DocAfterFocusFunc) => void;
+ back?: () => boolean;
+ forward?: () => boolean;
+ url?: () => string;
+ submitURL?: (url: string) => boolean;
+}
export interface DocumentViewSharedProps {
renderDepth: number;
Document: Doc;
@@ -54,12 +63,13 @@ export interface DocumentViewSharedProps {
fitContentsToDoc?: boolean; // used by freeformview to fit its contents to its panel. corresponds to _fitToBox property on a Document
ContainingCollectionView: Opt<CollectionView>;
ContainingCollectionDoc: Opt<Doc>;
- setContentView?: (view: { getAnchor: () => Doc }) => any;
+ setContentView?: (view: DocComponentView) => any;
CollectionFreeFormDocumentView?: () => CollectionFreeFormDocumentView;
PanelWidth: () => number;
PanelHeight: () => number;
- layerProvider?: (doc: Doc, assign?: boolean) => boolean;
- styleProvider?: StyleProviderFunc;
+ docViewPath: () => DocumentView[];
+ layerProvider: undefined | ((doc: Doc, assign?: boolean) => boolean);
+ styleProvider: Opt<StyleProviderFunc>;
focus: DocFocusFunc;
docFilters: () => string[];
docRangeFilters: () => string[];
@@ -108,7 +118,8 @@ export interface DocumentViewInternalProps extends DocumentViewProps {
NativeHeight: () => number;
isSelected: (outsideReaction?: boolean) => boolean;
select: (ctrlPressed: boolean) => void;
- DocumentView: any;
+ DocumentView: () => DocumentView;
+ viewPath: () => DocumentView[];
}
@observer
@@ -125,6 +136,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
private _timeout: NodeJS.Timeout | undefined;
private _dropDisposer?: DragManager.DragDropDisposer;
private _holdDisposer?: InteractionUtils.MultiTouchEventDisposer;
+ _componentView: Opt<DocComponentView>;
protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
private get topMost() { return this.props.renderDepth === 0; }
@@ -144,7 +156,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
@computed get docContents() { return this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.DocContents); }
@computed get headerMargin() { return this.props?.styleProvider?.(this.layoutDoc, this.props, StyleProp.HeaderMargin) || 0; }
@computed get pointerEvents() { return this.props.styleProvider?.(this.Document, this.props, StyleProp.PointerEvents + (this.props.isSelected() ? ":selected" : "")); }
- @computed get finalLayoutKey() { return StrCast(this.props.Document.layoutKey, "layout"); }
+ @computed get finalLayoutKey() { return StrCast(this.Document.layoutKey, "layout"); }
@computed get nativeWidth() { return this.props.NativeWidth(); }
@computed get nativeHeight() { return this.props.NativeHeight(); }
@computed get onClickHandler() { return this.props.onClick?.() ?? Cast(this.Document.onClick, ScriptField, Cast(this.layoutDoc.onClick, ScriptField, null)); }
@@ -336,7 +348,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
startDragging(x: number, y: number, dropAction: dropActionType) {
if (this._mainCont.current) {
const ffview = this.props.CollectionFreeFormDocumentView;
- ffview && runInAction(() => (ffview().props.CollectionFreeFormView.ChildDrag = this.props.DocumentView));
+ ffview && runInAction(() => (ffview().props.CollectionFreeFormView.ChildDrag = this.props.DocumentView()));
const dragData = new DragManager.DocumentDragData([this.props.Document]);
const [left, top] = this.props.ScreenToLocalTransform().scale(this.ContentScale).inverse().transformPoint(0, 0);
dragData.offset = this.props.ScreenToLocalTransform().scale(this.ContentScale).transformDirection(x - left, y - top);
@@ -366,6 +378,12 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
}
}
+ focus = (doc: Doc, willZoom?: boolean, scale?: number, afterFocus?: DocAfterFocusFunc, docTransform?: Transform) => {
+ if (this._componentView?.scrollFocus) {
+ return this._componentView?.scrollFocus?.(doc, !LinkDocPreview.LinkInfo, afterFocus); // bcz: smooth parameter should really be passed into focus() instead of inferred here
+ }
+ return this.props.focus(doc, willZoom, scale, afterFocus, docTransform);
+ }
onClick = action((e: React.MouseEvent | React.PointerEvent) => {
if (!e.nativeEvent.cancelBubble && !this.Document.ignoreClick && this.props.renderDepth >= 0 &&
(Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD)) {
@@ -384,7 +402,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
self: this.rootDoc,
scriptContext: this.props.scriptContext,
thisContainer: this.props.ContainingCollectionDoc,
- documentView: this.props.DocumentView,
+ documentView: this.props.DocumentView(),
clientX: e.clientX,
clientY: e.clientY,
shiftKey: e.shiftKey
@@ -394,7 +412,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
if (this.props.Document.type !== DocumentType.LABEL) {
UndoManager.RunInBatch(() => {
const fullScreenDoc = Cast(this.props.Document._fullScreenView, Doc, null) || this.props.Document;
- this.props.addDocTab(fullScreenDoc, "add");
+ this.props.addDocTab(fullScreenDoc, "lightbox");
}, "double tap");
SelectionManager.DeselectAll();
}
@@ -410,7 +428,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
self: this.rootDoc,
scriptContext: this.props.scriptContext,
thisContainer: this.props.ContainingCollectionDoc,
- documentView: this.props.DocumentView,
+ documentView: this.props.DocumentView(),
clientX: clientX,
clientY: clientY,
shiftKey
@@ -447,7 +465,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
if (!(InteractionUtils.IsType(e, InteractionUtils.MOUSETYPE) || Doc.GetSelectedTool() === InkTool.Highlighter || Doc.GetSelectedTool() === InkTool.Pen)) {
if (!InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) {
e.stopPropagation();
- if (SelectionManager.IsSelected(this.props.DocumentView, true) && this.props.Document._viewType !== CollectionViewType.Docking) e.preventDefault(); // goldenlayout needs to be able to move its tabs, so can't preventDefault for it
+ if (SelectionManager.IsSelected(this.props.DocumentView(), true) && this.props.Document._viewType !== CollectionViewType.Docking) e.preventDefault(); // goldenlayout needs to be able to move its tabs, so can't preventDefault for it
// TODO: check here for panning/inking
}
return;
@@ -462,7 +480,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
(e.button === 0 || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) &&
!CurrentUserUtils.OverlayDocs.includes(this.layoutDoc)) {
e.stopPropagation();
- if (SelectionManager.IsSelected(this.props.DocumentView, true) && this.layoutDoc._viewType !== CollectionViewType.Docking) e.preventDefault(); // goldenlayout needs to be able to move its tabs, so can't preventDefault for it
+ if (SelectionManager.IsSelected(this.props.DocumentView(), true) && this.layoutDoc._viewType !== CollectionViewType.Docking) e.preventDefault(); // goldenlayout needs to be able to move its tabs, so can't preventDefault for it
}
document.removeEventListener("pointermove", this.onPointerMove);
document.removeEventListener("pointerup", this.onPointerUp);
@@ -585,7 +603,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
if (e && this.rootDoc._hideContextMenu && Doc.UserDoc().noviceMode) {
e.preventDefault();
e.stopPropagation();
- !this.props.isSelected(true) && SelectionManager.SelectView(this.props.DocumentView, false);
+ !this.props.isSelected(true) && SelectionManager.SelectView(this.props.DocumentView(), false);
}
// the touch onContextMenu is button 0, the pointer onContextMenu is button 2
if (e) {
@@ -664,7 +682,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
const more = cm.findByDescription("More...");
const moreItems = more && "subitems" in more ? more.subitems : [];
if (!Doc.IsSystem(this.rootDoc)) {
- (this.rootDoc._viewType !== CollectionViewType.Docking || !Doc.UserDoc().noviceMode) && moreItems.push({ description: "Share", event: () => SharingManager.Instance.open(this.props.DocumentView), icon: "users" });
+ (this.rootDoc._viewType !== CollectionViewType.Docking || !Doc.UserDoc().noviceMode) && moreItems.push({ description: "Share", event: () => SharingManager.Instance.open(this.props.DocumentView()), icon: "users" });
if (!Doc.UserDoc().noviceMode) {
moreItems.push({ description: "Make View of Metadata Field", event: () => Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.DataDoc), icon: "concierge-bell" });
moreItems.push({ description: `${this.Document._chromeStatus !== "disabled" ? "Hide" : "Show"} Chrome`, event: () => this.Document._chromeStatus = (this.Document._chromeStatus !== "disabled" ? "disabled" : "enabled"), icon: "project-diagram" });
@@ -694,7 +712,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
if (!this.topMost) e?.stopPropagation(); // DocumentViews should stop propagation of this event
cm.displayMenu((e?.pageX || pageX || 0) - 15, (e?.pageY || pageY || 0) - 15);
- !this.props.isSelected(true) && setTimeout(() => SelectionManager.SelectView(this.props.DocumentView, false), 300); // on a mac, the context menu is triggered on mouse down, but a YouTube video becaomes interactive when selected which means that the context menu won't show up. by delaying the selection until hopefully after the pointer up, the context menu will appear.
+ !this.props.isSelected(true) && setTimeout(() => SelectionManager.SelectView(this.props.DocumentView(), false), 300); // on a mac, the context menu is triggered on mouse down, but a YouTube video becaomes interactive when selected which means that the context menu won't show up. by delaying the selection until hopefully after the pointer up, the context menu will appear.
}
rootSelected = (outsideReaction?: boolean) => this.props.isSelected(outsideReaction) || (this.props.Document.rootDocument && this.props.rootSelected?.(outsideReaction)) || false;
@@ -703,11 +721,9 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
screenToLocal = () => this.props.ScreenToLocalTransform().translate(0, -this.headerMargin);
contentScaling = () => this.ContentScale;
onClickFunc = () => this.onClickHandler;
- makeLink = () => this.props.DocumentView.LinkBeingCreated; // pass the link placeholde to child views so they can react to make a specialized anchor. This is essentially a function call to the descendants since the value of the _link variable will immediately get set back to undefined.
- setContentView = (view: { getAnchor: () => Doc }) => this._componentView = view;
+ setContentView = (view: { getAnchor: () => Doc, forward?: () => boolean, back?: () => boolean }) => this._componentView = view;
@observable contentsActive: () => boolean = returnFalse;
@action setContentsActive = (setActive: () => boolean) => this.contentsActive = setActive;
- _componentView: { getAnchor: () => Doc } | undefined;
@computed get contents() {
TraceMobx();
return <div className="documentView-contentsView"
@@ -716,29 +732,20 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
height: this.headerMargin ? `calc(100% - ${this.headerMargin}px)` : undefined,
}}>
<DocumentContentsView key={1} {...this.props}
+ docViewPath={this.props.viewPath}
setContentView={this.setContentView}
scaling={this.contentScaling}
PanelHeight={this.panelHeight}
contentsActive={this.setContentsActive}
parentActive={this.parentActive}
ScreenToLocalTransform={this.screenToLocal}
- makeLink={this.makeLink}
rootSelected={this.rootSelected}
onClick={this.onClickFunc}
+ focus={this.focus}
layoutKey={this.finalLayoutKey} />
{this.layoutDoc.hideAllLinks ? (null) : this.allAnchors}
{this.hideLinkButton ? (null) :
- <DocumentLinksButton View={this.props.DocumentView} links={this.allLinks} Offset={[this.topMost ? 0 : -15, undefined, undefined, this.topMost ? 10 : -20]} />}
- {!this.props.Document.numUsersShared && !this.props.Document.numGroupsShared ? (null) :
- <Tooltip title={<> <div className="dash-tooltip">Tap to open sharing menu</div></>}>
- <div className="sharingIndicator"
- onPointerDown={() => SharingManager.Instance.open(undefined, this.props.Document)}
- style={{ backgroundColor: GetEffectiveAcl(this.props.Document[DataSym]) === AclAdmin ? "#9dca96" : "lightgrey" }}
- >
- <FontAwesomeIcon size="lg" icon={this.indicatorIcon} />
- </div>
- </Tooltip >
- }
+ <DocumentLinksButton View={this.props.DocumentView()} links={this.allLinks} Offset={[this.topMost ? 0 : -15, undefined, undefined, this.topMost ? 10 : -20]} />}
</div>;
}
@@ -762,18 +769,19 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
if (this.props.LayoutTemplateString?.includes(LinkAnchorBox.name)) return null;
if (this.layoutDoc.presBox || this.rootDoc.type === DocumentType.LINK || this.props.dontRegisterView) return (null);
- const filtered = DocUtils.FilterDocs(this.directLinks, this.props.docFilters(), []).filter(d => !d.hidden);
- return filtered.map((d, i) =>
+ // need to use allLinks for RTF since embedded linked text anchors are not rendered with DocumentViews. All other documents render their anchors with nested DocumentViews so we just need to render the directLinks here
+ const filtered = DocUtils.FilterDocs(this.rootDoc.type === DocumentType.RTF ? this.allLinks : this.directLinks, this.props.docFilters(), []).filter(d => !d.hidden);
+ return filtered.map((link, i) =>
<div className="documentView-anchorCont" key={i + 1}>
<DocumentView {...this.props}
- Document={d}
+ Document={link}
PanelWidth={this.anchorPanelWidth}
PanelHeight={this.anchorPanelHeight}
dontRegisterView={false}
styleProvider={this.anchorStyleProvider}
removeDocument={this.hideLinkAnchor}
LayoutTemplate={undefined}
- LayoutTemplateString={LinkAnchorBox.LayoutString(`anchor${Doc.LinkEndpoint(d, this.props.Document)}`)} />
+ LayoutTemplateString={LinkAnchorBox.LayoutString(`anchor${Doc.LinkEndpoint(link, this.rootDoc)}`)} />
</div >);
}
@@ -784,7 +792,11 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
const showTitleHover = StrCast(this.layoutDoc._showTitleHover);
const showCaption = StrCast(this.layoutDoc._showCaption);
const captionView = !showCaption ? (null) :
- <div className="documentView-captionWrapper" style={{ backgroundColor: StrCast(this.layoutDoc["caption-backgroundColor"]), color: StrCast(this.layoutDoc["caption-color"]) }}>
+ <div className="documentView-captionWrapper"
+ style={{
+ backgroundColor: StrCast(this.layoutDoc["caption-backgroundColor"]),
+ color: StrCast(this.layoutDoc["caption-color"])
+ }}>
<DocumentContentsView {...OmitKeys(this.props, ['children']).omit}
yMargin={10}
xMargin={10}
@@ -874,7 +886,6 @@ export class DocumentView extends React.Component<DocumentViewProps> {
public get displayName() { return "DocumentView(" + this.props.Document?.title + ")"; } // this makes mobx trace() statements more descriptive
public ContentRef = React.createRef<HTMLDivElement>();
- @observable LinkBeingCreated: Opt<Doc>; // see DocumentLinksButton for explanation of how this works
@observable public docView: DocumentViewInternal | undefined | null;
get Document() { return this.props.Document; }
@@ -883,9 +894,11 @@ export class DocumentView extends React.Component<DocumentViewProps> {
get dataDoc() { return this.docView?.dataDoc || this.Document; }
get finalLayoutKey() { return this.docView?.finalLayoutKey || "layout"; }
get ContentDiv() { return this.docView?.ContentDiv; }
+ get ComponentView() { return this.docView?._componentView; }
get allLinks() { return this.docView?.allLinks || []; }
get LayoutFieldKey() { return this.docView?.LayoutFieldKey || "layout"; }
+ @computed get docViewPath() { return this.props.docViewPath ? [...this.props.docViewPath(), this] : [this]; }
@computed get layoutDoc() { return Doc.Layout(this.Document, this.props.LayoutTemplate?.()); }
@computed get nativeWidth() { return returnVal(this.props.NativeWidth?.(), Doc.NativeWidth(this.layoutDoc, this.props.DataDoc, this.props.freezeDimensions)); }
@computed get nativeHeight() { return returnVal(this.props.NativeHeight?.(), Doc.NativeHeight(this.layoutDoc, this.props.DataDoc, this.props.freezeDimensions) || 0); }
@@ -914,6 +927,9 @@ export class DocumentView extends React.Component<DocumentViewProps> {
toggleNativeDimensions = () => this.docView && Doc.toggleNativeDimensions(this.layoutDoc, this.docView.ContentScale, this.props.PanelWidth(), this.props.PanelHeight());
contentsActive = () => this.docView?.contentsActive();
+ focus = (doc: Doc, willZoom?: boolean, scale?: number, afterFocus?: DocAfterFocusFunc) => {
+ return this.docView?.focus(doc, willZoom, scale, afterFocus);
+ }
getBounds = () => {
if (!this.docView || !this.docView.ContentDiv || this.docView.props.renderDepth === 0 || this.docView.props.treeViewDoc || Doc.AreProtosEqual(this.props.Document, Doc.UserDoc())) {
return undefined;
@@ -953,6 +969,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
}), 400);
});
+ docViewPathFunc = () => this.docViewPath;
isSelected = (outsideReaction?: boolean) => SelectionManager.IsSelected(this, outsideReaction);
select = (ctrlPressed: boolean) => SelectionManager.SelectView(this, ctrlPressed);
NativeWidth = () => this.nativeWidth;
@@ -960,6 +977,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
PanelWidth = () => this.panelWidth;
PanelHeight = () => this.panelHeight;
ContentScale = () => this.nativeScaling;
+ selfView = () => this;
screenToLocalTransform = () => {
return this.props.ScreenToLocalTransform().translate(-this.centeringX, -this.centeringY).scale(1 / this.nativeScaling);
}
@@ -975,7 +993,8 @@ export class DocumentView extends React.Component<DocumentViewProps> {
TraceMobx();
const internalProps = {
...this.props,
- DocumentView: this,
+ DocumentView: this.selfView,
+ viewPath: this.docViewPathFunc,
PanelWidth: this.PanelWidth,
PanelHeight: this.PanelHeight,
NativeWidth: this.NativeWidth,
diff --git a/src/client/views/nodes/FilterBox.tsx b/src/client/views/nodes/FilterBox.tsx
index dfa81d3bb..87e49df61 100644
--- a/src/client/views/nodes/FilterBox.tsx
+++ b/src/client/views/nodes/FilterBox.tsx
@@ -374,7 +374,9 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps, FilterBoxDoc
treeViewHideHeaderFields={true}
onCheckedClick={this.scriptField}
dontRegisterView={true}
- styleProvider={this.FilteringStyleProvider}
+ styleProvider={this.props.styleProvider}
+ layerProvider={this.props.layerProvider}
+ docViewPath={this.props.docViewPath}
scriptContext={this.props.scriptContext}
moveDocument={returnFalse}
removeDocument={returnFalse}
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 92d6e2612..286b3bf5f 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -222,7 +222,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
if (!/\.(png|jpg|jpeg|gif|webp)$/.test(lower)) return url.href; //Why is this here
const ext = path.extname(url.href);
- this._curSuffix = this.props.renderDepth < 1 ? "_o" : this.props.PanelWidth() < 100 ? "_s" : "_m";
+ const scrSize = this.props.ScreenToLocalTransform().inverse().transformDirection(this.nativeSize.nativeWidth, this.nativeSize.nativeHeight);
+ this._curSuffix = this.props.renderDepth < 1 ? "_o" : scrSize[0] < 100 ? "_s" : scrSize[0] < 400 || !this.props.isSelected() ? "_m" : "_o";
return url.href.replace(ext, this._curSuffix + ext);
}
@@ -466,7 +467,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
</CollectionFreeFormView>
{this.annotationLayer}
{!this._marqueeing || !this._mainCont.current || !this._annotationLayer.current ? (null) :
- <MarqueeAnnotator rootDoc={this.rootDoc} down={this._marqueeing} scaling={this.props.scaling} addDocument={this.addDocument} finishMarquee={this.finishMarquee} savedAnnotations={this._savedAnnotations} annotationLayer={this._annotationLayer.current} mainCont={this._mainCont.current} />}
+ <MarqueeAnnotator rootDoc={this.rootDoc} scrollTop={0} down={this._marqueeing} scaling={this.props.scaling} addDocument={this.addDocument} finishMarquee={this.finishMarquee} savedAnnotations={this._savedAnnotations} annotationLayer={this._annotationLayer.current} mainCont={this._mainCont.current} />}
</div >);
}
}
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index 3c10cc5fe..ce9d8bed5 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -1,7 +1,7 @@
import { action, observable } from 'mobx';
import { observer } from "mobx-react";
import { Doc, Field, Opt } from '../../../fields/Doc';
-import { emptyFunction, returnFalse, returnOne, returnZero, returnEmptyFilter, returnEmptyDoclist } from '../../../Utils';
+import { emptyFunction, returnFalse, returnOne, returnZero, returnEmptyFilter, returnEmptyDoclist, emptyPath } from '../../../Utils';
import { Docs } from '../../documents/Documents';
import { Transform } from '../../util/Transform';
import { undoBatch } from '../../util/UndoManager';
@@ -12,6 +12,7 @@ import { KeyValueBox } from './KeyValueBox';
import "./KeyValueBox.scss";
import "./KeyValuePair.scss";
import React = require("react");
+import { DefaultStyleProvider } from '../StyleProvider';
// Represents one row in a key value plane
@@ -58,6 +59,9 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
docFilters: returnEmptyFilter,
docRangeFilters: returnEmptyFilter,
searchFilterDocs: returnEmptyDoclist,
+ styleProvider: DefaultStyleProvider,
+ layerProvider: undefined,
+ docViewPath: returnEmptyDoclist,
ContainingCollectionView: undefined,
ContainingCollectionDoc: undefined,
fieldKey: this.props.keyName,
diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx
index d86dfd7b1..db5414069 100644
--- a/src/client/views/nodes/LinkAnchorBox.tsx
+++ b/src/client/views/nodes/LinkAnchorBox.tsx
@@ -134,13 +134,14 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps, LinkAnch
</div>}
</div>
);
- return <div className={`linkAnchorBox-cont${small ? "-small" : ""} ${this.rootDoc[Id]}`}
- onPointerLeave={action(() => LinkDocPreview.LinkInfo = undefined)}
- onPointerEnter={action(e => LinkDocPreview.LinkInfo = {
- docprops: this.props,
+ return <div className={`linkAnchorBox-cont${small ? "-small" : ""}`}
+ onPointerLeave={LinkDocPreview.Clear}
+ onPointerEnter={e => LinkDocPreview.SetLinkInfo({
+ docProps: this.props,
linkSrc: linkSource,
linkDoc: this.rootDoc,
- Location: [e.clientX, e.clientY + 20]
+ showHeader: true,
+ location: [e.clientX, e.clientY + 20]
})}
onPointerDown={this.onPointerDown} onClick={this.onClick} title={targetTitle} onContextMenu={this.specificContextMenu}
ref={this._ref}
diff --git a/src/client/views/nodes/LinkDocPreview.scss b/src/client/views/nodes/LinkDocPreview.scss
new file mode 100644
index 000000000..b7aeaa072
--- /dev/null
+++ b/src/client/views/nodes/LinkDocPreview.scss
@@ -0,0 +1,72 @@
+ .linkDocPreview {
+ position: absolute;
+ pointer-events: all;
+ background-color: lightblue;
+ border: 8px solid white;
+ border-radius: 7px;
+ box-shadow: 3px 3px 1.5px grey;
+ border-bottom: 8px solid white;
+ border-right: 8px solid white;
+ z-index: 2004;
+ .linkDocPreview-inner {
+ background-color: white;
+ border: 8px solid white;
+ width: 100%;
+ height: 100%;
+ pointer-events: none;
+
+ .linkDocPreview-info {
+ height: 37px;
+ white-space: pre;
+
+ .linkDocPreview-buttonBar {
+ float: right;
+ }
+ .linkDocPreview-title {
+ padding-right: 4px;
+ float: left;
+ width: calc(100% - 48px);
+ overflow: hidden;
+ text-overflow: ellipsis;
+ height: 25px;
+
+ .linkDocPreview-description {
+ text-decoration: none;
+ font-style: italic;
+ color: rgb(95, 97, 102);
+ font-size: 10px;
+ }
+ }
+
+ .linkDocPreview-button {
+ display: inline-flex;
+ margin: 0;
+ margin-right: 3px;
+ border-radius: 50%;
+ pointer-events: auto;
+ background-color: black;
+ color: white;
+ transition: transform 0.2s;
+ text-align: center;
+ position: relative;
+ font-size: 12px;
+ width: 20px;
+ height: 20px;
+ align-items: center;
+ justify-content: center;
+
+ &:hover {
+ background-color: rgb(77, 77, 77);
+ cursor: pointer;
+ }
+ }
+
+ .linkDocPreview-preview-wrapper {
+ overflow: hidden;
+ align-content: center;
+ justify-content: center;
+ background-color: rgb(160, 160, 160);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx
index 7934dba8e..6155ed493 100644
--- a/src/client/views/nodes/LinkDocPreview.tsx
+++ b/src/client/views/nodes/LinkDocPreview.tsx
@@ -1,131 +1,180 @@
-import { action, computed, observable, runInAction } from 'mobx';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { Tooltip } from '@material-ui/core';
+import { action, computed, observable } from 'mobx';
import { observer } from "mobx-react";
import wiki from "wikijs";
-import { Doc, DocCastAsync, HeightSym, Opt, WidthSym } from "../../../fields/Doc";
-import { Id } from '../../../fields/FieldSymbols';
-import { Cast, FieldValue, NumCast } from "../../../fields/Types";
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse } from "../../../Utils";
+import { Doc, DocListCast, HeightSym, Opt, WidthSym } from "../../../fields/Doc";
+import { NumCast, StrCast } from "../../../fields/Types";
+import { emptyFunction, emptyPath, returnEmptyDoclist, returnEmptyFilter, returnFalse, setupMoveUpEvents, Utils } from "../../../Utils";
+import { DocServer } from '../../DocServer';
import { Docs } from "../../documents/Documents";
import { LinkManager } from '../../util/LinkManager';
import { Transform } from "../../util/Transform";
-import { ContextMenu } from '../ContextMenu';
-import { DocumentLinksButton } from './DocumentLinksButton';
-import { DocumentView, StyleProviderFunc, DocumentViewSharedProps } from "./DocumentView";
+import { undoBatch } from '../../util/UndoManager';
+import { DocumentView, DocumentViewSharedProps } from "./DocumentView";
+import './LinkDocPreview.scss';
import React = require("react");
-interface Props {
+interface LinkDocPreviewProps {
linkDoc?: Doc;
linkSrc?: Doc;
- href?: string;
- docprops: DocumentViewSharedProps;
+ docProps: DocumentViewSharedProps;
location: number[];
+ hrefs?: string[];
+ showHeader?: boolean;
}
@observer
-export class LinkDocPreview extends React.Component<Props> {
- static TargetDoc: Doc | undefined;
- @observable public static LinkInfo: Opt<{ linkDoc?: Doc; linkSrc: Doc; href?: string; Location: number[], docprops: DocumentViewSharedProps }>;
+export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
+ @action public static Clear() { LinkDocPreview.LinkInfo = undefined; }
+ @action public static SetLinkInfo(info?: LinkDocPreviewProps) { LinkDocPreview.LinkInfo != info && (LinkDocPreview.LinkInfo = info); }
+
+ _infoRef = React.createRef<HTMLDivElement>();
+ @observable public static LinkInfo: Opt<LinkDocPreviewProps>;
@observable _targetDoc: Opt<Doc>;
+ @observable _linkDoc: Opt<Doc>;
+ @observable _linkSrc: Opt<Doc>;
@observable _toolTipText = "";
- _editRef = React.createRef<HTMLDivElement>();
+ @observable _hrefInd = 0;
- @action
- onContextMenu = (e: React.MouseEvent) => {
- DocumentLinksButton.EditLink = undefined;
- LinkDocPreview.LinkInfo = undefined;
- e.preventDefault();
- ContextMenu.Instance.addItem({ description: "Follow Default Link", event: () => this.followDefault(), icon: "arrow-right" });
- ContextMenu.Instance.displayMenu(e.clientX, e.clientY);
+ @action init() {
+ var linkTarget = this.props.linkDoc;
+ this._linkSrc = this.props.linkSrc;
+ this._linkDoc = this.props.linkDoc;
+ const anchor1 = this._linkDoc?.anchor1 as Doc;
+ const anchor2 = this._linkDoc?.anchor2 as Doc;
+ if (anchor1 && anchor2) {
+ linkTarget = Doc.AreProtosEqual(anchor1, this._linkSrc) || Doc.AreProtosEqual(anchor1?.annotationOn as Doc, this._linkSrc) ? anchor2 : anchor1;
+ }
+ this._targetDoc = linkTarget?.annotationOn as Doc ?? linkTarget;
+ this._toolTipText = "";
+ }
+ componentDidUpdate(props: any) {
+ if (props.linkSrc !== this.props.linkSrc || props.linkDoc !== this.props.linkDoc || props.hrefs !== this.props.hrefs) this.init();
+ }
+ componentDidMount() {
+ this.init();
+ document.addEventListener("pointerdown", this.onPointerDown);
}
- @action.bound
- async followDefault() {
- DocumentLinksButton.EditLink = undefined;
- LinkDocPreview.LinkInfo = undefined;
- this._targetDoc && LinkManager.FollowLink(this.props.linkDoc, this._targetDoc, this.props.docprops, false);
+ componentWillUnmount() {
+ LinkDocPreview.SetLinkInfo(undefined);
+ document.removeEventListener("pointerdown", this.onPointerDown);
}
- componentWillUnmount() { LinkDocPreview.TargetDoc = undefined; }
- componentDidUpdate() { this.updatePreview(); }
- componentDidMount() { this.updatePreview(); }
- async updatePreview() {
- const linkDoc = this.props.linkDoc;
- const linkSrc = this.props.linkSrc;
- LinkDocPreview.TargetDoc = undefined;
- if (this.props.href) {
- if (this.props.href.startsWith("https://en.wikipedia.org/wiki/")) {
- wiki().page(this.props.href.replace("https://en.wikipedia.org/wiki/", "")).then(page => page.summary().then(action(summary => this._toolTipText = summary.substring(0, 500))));
- } else {
- runInAction(() => this._toolTipText = "external => " + this.props.href);
- }
- } else if (linkDoc && linkSrc) {
- const anchor = FieldValue(Doc.AreProtosEqual(FieldValue(Cast(linkDoc.anchor1, Doc)), linkSrc) ? Cast(linkDoc.anchor2, Doc) : (Cast(linkDoc.anchor1, Doc)) || linkDoc);
- const target = anchor?.annotationOn ? await DocCastAsync(anchor.annotationOn) : anchor;
- runInAction(() => {
- this._toolTipText = "";
- LinkDocPreview.TargetDoc = this._targetDoc = target;
- if (this._targetDoc) {
- this._targetDoc._scrollToPreviewLinkID = linkDoc?.[Id];
- if (anchor !== this._targetDoc && anchor) {
- this._targetDoc._scrollPreviewY = NumCast(anchor?.y);
- }
+ onPointerDown = (e: PointerEvent) => {
+ !this._infoRef.current?.contains(e.target as any) && LinkDocPreview.Clear(); // close preview when not clicking anywhere other than the info bar of the preview
+ }
+
+ @computed get href() {
+ if (this.props.hrefs?.length) {
+ const href = this.props.hrefs[this._hrefInd];
+ if (href.indexOf(Utils.prepend("/doc/")) !== 0) { // link to a web page URL -- try to show a preview
+ if (href.startsWith("https://en.wikipedia.org/wiki/")) {
+ wiki().page(href.replace("https://en.wikipedia.org/wiki/", "")).then(page => page.summary().then(action(summary => this._toolTipText = summary.substring(0, 500))));
+ } else {
+ setTimeout(action(() => this._toolTipText = "url => " + href));
}
- });
+ } else { // hyperlink to a document .. decode doc id and retrieve from the server. this will trigger vals() being invalidated
+ const anchorDoc = href.replace(Utils.prepend("/doc/"), "").split("?")[0];
+ anchorDoc && DocServer.GetRefField(anchorDoc).then(action(anchor => {
+ if (anchor instanceof Doc && DocListCast(anchor.links).length) {
+ this._linkDoc = DocListCast(anchor.links)[0];
+ this._linkSrc = anchor;
+ const linkTarget = LinkManager.getOppositeAnchor(this._linkDoc, this._linkSrc);
+ this._targetDoc = linkTarget?.annotationOn as Doc ?? linkTarget;
+ this._toolTipText = "";
+ }
+ }));
+ }
+ return href;
}
+ return undefined;
}
- pointerDown = (e: React.PointerEvent) => {
- if (this.props.linkDoc && this.props.linkSrc) {
- LinkManager.FollowLink(this.props.linkDoc, this.props.linkSrc, this.props.docprops, false);
- } else if (this.props.href) {
- this.props.docprops?.addDocTab(Docs.Create.WebDocument(this.props.href, { title: this.props.href, _width: 200, _height: 400, useCors: true }), "add:right");
+ deleteLink = (e: React.PointerEvent) => {
+ setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(() => this._linkDoc && LinkManager.Instance.deleteLink(this._linkDoc)));
+ }
+ nextHref = (e: React.PointerEvent) => {
+ setupMoveUpEvents(this, e, returnFalse, emptyFunction, action(() => this._hrefInd = (this._hrefInd + 1) % (this.props.hrefs?.length || 1)));
+ }
+
+ followLink = (e: React.PointerEvent) => {
+ if (this._linkDoc && this._linkSrc) {
+ LinkManager.FollowLink(this._linkDoc, this._linkSrc, this.props.docProps, false);
+ } else if (this.props.hrefs?.length) {
+ this.props.docProps?.addDocTab(Docs.Create.WebDocument(this.props.hrefs[0], { title: this.props.hrefs[0], _width: 200, _height: 400, useCors: true }), "add:right");
}
}
width = () => Math.min(225, NumCast(this._targetDoc?.[WidthSym](), 225));
height = () => Math.min(225, NumCast(this._targetDoc?.[HeightSym](), 225));
- @computed get targetDocView() {
- return !this._targetDoc ?
- <div style={{ pointerEvents: "all", maxWidth: 225, maxHeight: 225, width: "100%", height: "100%", overflow: "hidden" }}>
- <div style={{ width: "100%", height: "100%", textOverflow: "ellipsis", }} onPointerDown={this.pointerDown}>
- {this._toolTipText}
+ @computed get previewHeader() {
+ return !this._linkDoc || !this._targetDoc || !this._linkSrc ? (null) :
+ <div className="linkDocPreview-info" ref={this._infoRef}>
+ <div className="linkDocPreview-title">
+ {StrCast(this._targetDoc.title).length > 16 ? StrCast(this._targetDoc.title).substr(0, 16) + "..." : this._targetDoc.title}
+ <p className="linkDocPreview-description"> {StrCast(this._linkDoc.description)}</p>
+ </div>
+ <div className="linkDocPreview-buttonBar" >
+ <Tooltip title={<div className="dash-tooltip">Next Link</div>} placement="top">
+ <div className="linkDocPreview-button" style={{ background: (this.props.hrefs?.length || 0) <= 1 ? "gray" : "green" }} onPointerDown={this.nextHref}>
+ <FontAwesomeIcon className="linkDocPreview-fa-icon" icon="chevron-right" color="white" size="sm" />
+ </div>
+ </Tooltip>
+
+ <Tooltip title={<div className="dash-tooltip">Delete Link</div>} placement="top">
+ <div className="linkDocPreview-button" onPointerDown={this.deleteLink}>
+ <FontAwesomeIcon className="linkDocPreview-fa-icon" icon="trash" color="white" size="sm" />
+ </div>
+ </Tooltip>
</div>
- </div>
- :
- <DocumentView
- Document={this._targetDoc}
- moveDocument={returnFalse}
- rootSelected={returnFalse}
- ScreenToLocalTransform={Transform.Identity}
- parentActive={returnFalse}
- addDocument={returnFalse}
- removeDocument={returnFalse}
- addDocTab={returnFalse}
- pinToPres={returnFalse}
- dontRegisterView={true}
- docFilters={returnEmptyFilter}
- docRangeFilters={returnEmptyFilter}
- searchFilterDocs={returnEmptyDoclist}
- ContainingCollectionDoc={undefined}
- ContainingCollectionView={undefined}
- renderDepth={-1}
- PanelWidth={this.width}
- PanelHeight={this.height}
- focus={emptyFunction}
- whenActiveChanged={returnFalse}
- bringToFront={returnFalse}
- styleProvider={this.props.docprops?.styleProvider} />;
+ </div>;
+ }
+
+ @computed get docPreview() {
+ const href = this.href; // needs to be here to trigger lookup of web pages and docs on server
+ return (!this._linkDoc || !this._targetDoc || !this._linkSrc) && !this._toolTipText ? (null) :
+ <div className="linkDocPreview-inner">
+ {!this.props.showHeader ? (null) : this.previewHeader}
+ <div className="linkDocPreview-preview-wrapper">
+ {this._toolTipText ? this._toolTipText :
+ <DocumentView ref={(r) => {
+ const targetanchor = LinkManager.getOppositeAnchor(this._linkDoc!, this._linkSrc!);
+ targetanchor && this._targetDoc !== targetanchor && r?.focus(targetanchor);
+ }}
+ Document={this._targetDoc!}
+ moveDocument={returnFalse}
+ rootSelected={returnFalse}
+ styleProvider={this.props.docProps?.styleProvider}
+ layerProvider={this.props.docProps?.layerProvider}
+ docViewPath={returnEmptyDoclist}
+ ScreenToLocalTransform={Transform.Identity}
+ parentActive={returnFalse}
+ addDocument={returnFalse}
+ removeDocument={returnFalse}
+ addDocTab={returnFalse}
+ pinToPres={returnFalse}
+ dontRegisterView={true}
+ docFilters={returnEmptyFilter}
+ docRangeFilters={returnEmptyFilter}
+ searchFilterDocs={returnEmptyDoclist}
+ ContainingCollectionDoc={undefined}
+ ContainingCollectionView={undefined}
+ renderDepth={-1}
+ PanelWidth={this.width}
+ PanelHeight={this.height}
+ focus={emptyFunction}
+ whenActiveChanged={returnFalse}
+ bringToFront={returnFalse}
+ NativeWidth={Doc.NativeWidth(this._targetDoc) ? () => Doc.NativeWidth(this._targetDoc) : undefined}
+ NativeHeight={Doc.NativeHeight(this._targetDoc) ? () => Doc.NativeHeight(this._targetDoc) : undefined}
+ />}
+ </div>
+ </div>;
}
render() {
- return <div className="linkDocPreview"
- style={{
- position: "absolute", left: this.props.location[0],
- top: this.props.location[1], width: this.width() + 16, height: this.height() + 16,
- zIndex: 1000,
- backgroundColor: "lightblue",
- border: "8px solid white", borderRadius: "7px",
- boxShadow: "3px 3px 1.5px grey",
- borderBottom: "8px solid white", borderRight: "8px solid white"
- }}>
- {this.targetDocView}
+ return <div className="linkDocPreview" onPointerDown={this.followLink}
+ style={{ left: this.props.location[0], top: this.props.location[1], width: this.width() + 16 }}>
+ {this.docPreview}
</div>;
}
-}
+} \ No newline at end of file
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index ec9a75302..989be1ab9 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -22,6 +22,7 @@ import { FieldView, FieldViewProps } from './FieldView';
import { pageSchema } from "./ImageBox";
import "./PDFBox.scss";
import React = require("react");
+import { DocAfterFocusFunc } from './DocumentView';
type PdfDocument = makeInterface<[typeof documentSchema, typeof panZoomSchema, typeof pageSchema]>;
const PdfDocument = makeInterface(documentSchema, panZoomSchema, pageSchema);
@@ -79,8 +80,11 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps, PdfDocum
}
}
+ scrollFocus = (doc: Doc, smooth: boolean, afterFocus?: DocAfterFocusFunc) => this._pdfViewer?.scrollFocus(doc, smooth, afterFocus);
+ getAnchor = () => this.rootDoc;
componentWillUnmount() { this._selectReactionDisposer?.(); }
componentDidMount() {
+ this.props.setContentView?.(this);
this._selectReactionDisposer = reaction(() => this.props.isSelected(),
() => {
document.removeEventListener("keydown", this.onKeyDown);
@@ -218,7 +222,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps, PdfDocum
static pdfpromise = new Map<string, Pdfjs.PDFPromise<Pdfjs.PDFDocumentProxy>>();
render() {
TraceMobx();
- if (true) {//this.props.isSelected() || (this.props.active() && this.props.renderDepth === 0) || this.props.Document._scrollY !== undefined) {
+ if (true) {//this.props.isSelected() || (this.props.active() && this.props.renderDepth === 0)) {
this._displayPdfLive = true;
}
if (this._displayPdfLive) {
diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx
index b6feace12..589a1c2ae 100644
--- a/src/client/views/nodes/PresBox.tsx
+++ b/src/client/views/nodes/PresBox.tsx
@@ -364,7 +364,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
bestTarget && runInAction(() => {
if (bestTarget.type === DocumentType.PDF || bestTarget.type === DocumentType.WEB || bestTarget.type === DocumentType.RTF || bestTarget._viewType === CollectionViewType.Stacking) {
bestTarget._viewTransition = activeItem.presTransition ? `transform ${activeItem.presTransition}ms` : 'all 0.5s';
- bestTarget._scrollY = activeItem.presPinViewScroll;
+ bestTarget._scrollTop = activeItem.presPinViewScroll;
} else if (bestTarget.type === DocumentType.COMPARISON) {
bestTarget._clipWidth = activeItem.presPinClipWidth;
} else if (bestTarget.type === DocumentType.VID) {
diff --git a/src/client/views/nodes/VideoBox.scss b/src/client/views/nodes/VideoBox.scss
index b9123587b..dd8d77603 100644
--- a/src/client/views/nodes/VideoBox.scss
+++ b/src/client/views/nodes/VideoBox.scss
@@ -13,6 +13,11 @@
.collectionStackedTimeline {
background: beige;
}
+ .videoBox-stackPanel {
+ z-index: -1;
+ width: 100%;
+ position: absolute;
+ }
}
.videoBox-content-YouTube, .videoBox-content-YouTube-fullScreen,
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 8a1cefbd9..c0247c226 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -70,7 +70,8 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
}
getAnchor = () => {
- return CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.annotationKey + "-timeline", "videoStart", "videoEnd", Cast(this.layoutDoc._currentTimecode, "number", null)) || this.rootDoc;
+ const timecode = Cast(this.layoutDoc._currentTimecode, "number", null);
+ return CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.annotationKey + "-timeline", "videoStart", "videoEnd", timecode ? timecode : undefined) || this.rootDoc;
}
choosePath(url: string) {
@@ -211,7 +212,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
},
{ fireImmediately: true });
this._disposers.triggerVideo = reaction(
- () => !LinkDocPreview.TargetDoc && !FormattedTextBoxComment.linkDoc && this.props.renderDepth !== -1 ? NumCast(this.Document._triggerVideo, null) : undefined,
+ () => !LinkDocPreview.LinkInfo && this.props.renderDepth !== -1 ? NumCast(this.Document._triggerVideo, null) : undefined,
time => time !== undefined && setTimeout(() => {
this.player && this.Play();
setTimeout(() => this.Document._triggerVideo = undefined, 10);
@@ -219,7 +220,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
{ fireImmediately: true }
);
this._disposers.triggerStop = reaction(
- () => this.props.renderDepth !== -1 && !LinkDocPreview.TargetDoc && !FormattedTextBoxComment.linkDoc ? NumCast(this.Document._triggerVideoStop, null) : undefined,
+ () => this.props.renderDepth !== -1 && !LinkDocPreview.LinkInfo ? NumCast(this.Document._triggerVideoStop, null) : undefined,
stop => stop !== undefined && setTimeout(() => {
this.player && this.Pause();
setTimeout(() => this.Document._triggerVideoStop = undefined, 10);
@@ -489,7 +490,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
setAnchorTime = (time: number) => this.player!.currentTime = this.layoutDoc._currentTimecode = time;
timelineHeight = () => this.props.PanelHeight() * (100 - this.heightPercent) / 100;
@computed get renderTimeline() {
- return <div style={{ width: "100%", transition: this.transition, height: `${100 - this.heightPercent}%`, position: "absolute" }}>
+ return <div className="videoBox-stackPanel" style={{ transition: this.transition, height: `${100 - this.heightPercent}%` }}>
<CollectionStackedTimeline ref={this._stackedTimeline} {...this.props}
fieldKey={this.annotationKey}
renderDepth={this.props.renderDepth + 1}
@@ -574,6 +575,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
{this.renderTimeline}
{!this._marqueeing || !this._mainCont.current || !this._annotationLayer.current ? (null) :
<MarqueeAnnotator
+ scrollTop={0}
rootDoc={this.rootDoc}
down={this._marqueeing}
scaling={this.marqueeFitScaling}
diff --git a/src/client/views/nodes/WebBox.scss b/src/client/views/nodes/WebBox.scss
index 5a7c1c904..ca6611a6b 100644
--- a/src/client/views/nodes/WebBox.scss
+++ b/src/client/views/nodes/WebBox.scss
@@ -96,7 +96,7 @@
}
.webBox-annotationToggle {
- z-index: 9001;
+ z-index: 901;
position: absolute;
top: 2;
left: 2;
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 4b7f0bf77..ef0df25b6 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -24,12 +24,12 @@ import { ContextMenu } from "../ContextMenu";
import { ContextMenuProps } from "../ContextMenuItem";
import { ViewBoxAnnotatableComponent } from "../DocComponent";
import { DocumentDecorations } from "../DocumentDecorations";
+import { MarqueeAnnotator } from "../MarqueeAnnotator";
import { Annotation } from "../pdf/Annotation";
-import { AnchorMenu } from "../pdf/AnchorMenu";
+import { DocAfterFocusFunc } from "./DocumentView";
import { FieldView, FieldViewProps } from './FieldView';
import "./WebBox.scss";
import React = require("react");
-import { MarqueeAnnotator } from "../MarqueeAnnotator";
const htmlToText = require("html-to-text");
type WebDocument = makeInterface<[typeof documentSchema]>;
@@ -37,25 +37,23 @@ const WebDocument = makeInterface(documentSchema);
@observer
export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocument>(WebDocument) {
- private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef();
public static LayoutString(fieldKey: string) { return FieldView.LayoutString(WebBox, fieldKey); }
private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
- private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean) => void);
private _disposers: { [name: string]: IReactionDisposer } = {};
private _longPressSecondsHack?: NodeJS.Timeout;
private _outerRef = React.createRef<HTMLDivElement>();
+ private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef();
private _iframeIndicatorRef = React.createRef<HTMLDivElement>();
private _iframeDragRef = React.createRef<HTMLDivElement>();
+ private _ignoreScroll = false;
+ private _initialScroll: Opt<number>;
@observable private _marqueeing: number[] | undefined;
@observable private _url: string = "hello";
@observable private _pressX: number = 0;
@observable private _pressY: number = 0;
@observable private _iframe: HTMLIFrameElement | null = null;
@observable private _savedAnnotations: Dictionary<number, HTMLDivElement[]> = new Dictionary<number, HTMLDivElement[]>();
-
get scrollHeight() { return this.webpage?.scrollHeight || 1000; }
- get _collapsed() { return StrCast(this.layoutDoc._chromeStatus) !== "enabled"; }
- set _collapsed(value) { this.layoutDoc._chromeStatus = !value ? "enabled" : "disabled"; }
get webpage() { return this._iframe?.contentDocument?.children[0]; }
constructor(props: any) {
@@ -64,11 +62,17 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
Doc.SetNativeWidth(this.dataDoc, Doc.NativeWidth(this.dataDoc) || 850);
Doc.SetNativeHeight(this.dataDoc, Doc.NativeHeight(this.dataDoc) || this.Document[HeightSym]() / this.Document[WidthSym]() * 850);
}
+ this._annotationKey = this._annotationKey + "-" + this.urlHash(this._url);
}
- iframeLoaded = action((e: any) => {
+ iframeLoaded = (e: any) => {
const iframe = this._iframe;
if (iframe?.contentDocument) {
+ if (this._initialScroll !== undefined && this._outerRef.current && this.webpage) {
+ this.webpage.scrollTop = this._initialScroll;
+ this._outerRef.current.scrollTop = this._initialScroll;
+ this._initialScroll = undefined;
+ }
iframe.setAttribute("enable-annotation", "true");
iframe.contentDocument.addEventListener("click", undoBatch(action(e => {
let href = "";
@@ -76,86 +80,60 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
href = (typeof (ele.href) === "string" ? ele.href : ele.href?.baseVal) || ele.parentElement?.href || href;
}
if (href) {
- this._url = href.replace(Utils.prepend(""), Cast(this.dataDoc[this.fieldKey], WebField, null)?.url.origin);
- this.submitURL();
+ this.submitURL(href.replace(Utils.prepend(""), Cast(this.dataDoc[this.fieldKey], WebField, null)?.url.origin));
+ if (this.webpage) {
+ this.webpage.scrollTop = NumCast(this.layoutDoc._scrollTop);
+ this.webpage.scrollLeft = 0;
+ }
}
})));
iframe.contentDocument.addEventListener('wheel', this.iframeWheel, false);
- if (this.webpage) {
- this.webpage.scrollTop = NumCast(this.layoutDoc._scrollTop);
- this.webpage.scrollLeft = NumCast(this.layoutDoc._scrollLeft);
- }
}
- this._disposers.scrollReaction?.();
- this._disposers.scrollReaction = reaction(() => ({ scrollY: this.layoutDoc._scrollY, scrollX: this.layoutDoc._scrollX }),
- ({ scrollY, scrollX }) => {
- const delay = this._outerRef.current ? 0 : 250; // wait for mainCont and try again to scroll
- const durationStr = StrCast(this.Document._viewTransition).match(/([0-9]*)ms/);
- const duration = durationStr ? Number(durationStr[1]) : 1000;
- if (scrollY !== undefined) {
- this._forceSmoothScrollUpdate = true;
- this.layoutDoc._scrollY = undefined;
- setTimeout(() => this._outerRef.current && smoothScroll(duration, this._outerRef.current, Math.abs(scrollY || 0), () => this.layoutDoc._scrollTop = scrollY), delay);
- }
- if (scrollX !== undefined) {
- this._forceSmoothScrollUpdate = true;
- this.layoutDoc._scrollX = undefined;
- setTimeout(() => this._outerRef.current && smoothScroll(duration, this._outerRef.current, Math.abs(scrollX || 0), () => this.layoutDoc._scrollLeft = scrollX), delay);
- }
- },
- { fireImmediately: true }
- );
- this._disposers.scrollTop = reaction(() => this.layoutDoc._scrollTop,
- scrollTop => {
- const durationStr = StrCast(this.Document._viewTransition).match(/([0-9]*)ms/);
- const duration = durationStr ? Number(durationStr[1]) : 1000;
- if (scrollTop !== this._outerRef.current?.scrollTop && scrollTop !== undefined && this._forceSmoothScrollUpdate) {
- this._outerRef.current && smoothScroll(duration, this._outerRef.current, Math.abs(scrollTop || 0), () => this._forceSmoothScrollUpdate = true);
- } else this._forceSmoothScrollUpdate = true;
- },
- { fireImmediately: true }
- );
- });
- _forceSmoothScrollUpdate = true;
+ }
- updateScroll = (x: Opt<number>, y: Opt<number>) => {
- if (y !== undefined) {
- this._outerRef.current!.scrollTop = y;
- this.layoutDoc._scrollY = undefined;
- }
- if (x !== undefined) {
- this._outerRef.current!.scrollLeft = x;
- this.layoutDoc.scrollX = undefined;
+ @action
+ onWheelScroll = (scrollTop: number) => {
+ if (this.webpage && this._outerRef.current) {
+ this.webpage.scrollLeft = 0;
+ this._outerRef.current.scrollTop = scrollTop;
+ this._outerRef.current.scrollLeft = 0;
+ this._ignoreScroll = true;
+ if (this.layoutDoc._scrollTop !== scrollTop) {
+ this.layoutDoc._scrollTop = scrollTop;
+ }
+ this._ignoreScroll = false;
}
}
+ iframeWheel = (e: any) => this.webpage && e.target?.children && this.onWheelScroll(this.webpage.scrollTop);
+ onWheel = (e: React.WheelEvent) => {
+ this._outerRef.current && this.onWheelScroll(this._outerRef.current.scrollTop);
+ e.stopPropagation();
+ }
- setPreviewCursor = (func?: (x: number, y: number, drag: boolean) => void) => this._setPreviewCursor = func;
- iframeWheel = (e: any) => {
- if (this._forceSmoothScrollUpdate && e.target?.children) {
- this.webpage && setTimeout(action(() => {
- this.webpage!.scrollLeft = 0;
- const scrollTop = this.webpage!.scrollTop;
- const scrollLeft = this.webpage!.scrollLeft;
- this._outerRef.current!.scrollTop = scrollTop;
- this._outerRef.current!.scrollLeft = scrollLeft;
- if (this.layoutDoc._scrollTop !== scrollTop) {
- this._forceSmoothScrollUpdate = false;
- this.layoutDoc._scrollTop = scrollTop;
- }
- if (this.layoutDoc._scrollLeft !== scrollLeft) {
- this._forceSmoothScrollUpdate = false;
- this.layoutDoc._scrollLeft = scrollLeft;
- }
- }));
+ getAnchor = () => this.rootDoc;
+ scrollFocus = (doc: Doc, smooth: boolean, afterFocus?: DocAfterFocusFunc) => {
+ if (doc !== this.rootDoc && this.webpage && this._outerRef.current) {
+ const scrollTo = Utils.scrollIntoView(NumCast(doc.y), doc[HeightSym](), NumCast(this.layoutDoc._scrollTop), this.props.PanelHeight() / (this.props.scaling?.() || 1));
+ if (scrollTo !== undefined) {
+ this._initialScroll !== undefined && (this._initialScroll = scrollTo);
+ this._ignoreScroll = true;
+ this.goTo(scrollTo, smooth ? 500 : 0);
+ this.layoutDoc._scrollTop = scrollTo;
+ this._ignoreScroll = false;
+ return afterFocus?.(true);
+ }
+ } else {
+ this._initialScroll = NumCast(doc.y);
}
+ afterFocus?.(false);
}
+
async componentDidMount() {
+ this.props.setContentView?.(this); // this tells the DocumentView that this AudioBox is the "content" of the document. this allows the DocumentView to indirectly call getAnchor() on the AudioBox when making a link.
+
const urlField = Cast(this.dataDoc[this.props.fieldKey], WebField);
runInAction(() => this._url = urlField?.url.toString() || "");
- this._disposers.scrollMove = reaction(() => this.layoutDoc.x || this.layoutDoc.y,
- () => this.updateScroll(this.layoutDoc._scrollLeft, this.layoutDoc._scrollTop));
-
this._disposers.selection = reaction(() => this.props.isSelected(),
selected => {
if (!selected) {
@@ -185,6 +163,36 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
this.dataDoc.text = htmlToText.fromString(result.content);
}
}
+
+ var quickScroll = true;
+ this._disposers.scrollReaction = reaction(() => NumCast(this.layoutDoc._scrollTop),
+ (scrollTop) => {
+ if (quickScroll) {
+ this._initialScroll = scrollTop;
+ }
+ else if (!this._ignoreScroll) {
+ const viewTrans = StrCast(this.Document._viewTransition);
+ const durationMiliStr = viewTrans.match(/([0-9]*)ms/);
+ const durationSecStr = viewTrans.match(/([0-9.]*)s/);
+ const duration = durationMiliStr ? Number(durationMiliStr[1]) : durationSecStr ? Number(durationSecStr[1]) * 1000 : 0;
+ this.goTo(scrollTop, duration);
+ }
+ },
+ { fireImmediately: true }
+ );
+ quickScroll = false;
+ }
+
+ goTo = (scrollTop: number, duration: number) => {
+ if (this._outerRef.current && this.webpage) {
+ if (duration) {
+ smoothScroll(duration, this.webpage as any as HTMLElement, scrollTop);
+ smoothScroll(duration, this._outerRef.current, scrollTop);
+ } else {
+ this.webpage.scrollTop = scrollTop;
+ this._outerRef.current.scrollTop = scrollTop;
+ }
+ }
}
componentWillUnmount() {
@@ -194,31 +202,17 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
this._iframe?.removeEventListener('wheel', this.iframeWheel);
}
- onUrlDragover = (e: React.DragEvent) => { e.preventDefault(); };
-
- @undoBatch
- @action
- onUrlDrop = (e: React.DragEvent) => {
- const { dataTransfer } = e;
- const html = dataTransfer.getData("text/html");
- const uri = dataTransfer.getData("text/uri-list");
- const url = uri || html || this._url;
- this._url = url.startsWith(window.location.origin) ?
- url.replace(window.location.origin, this._url.match(/http[s]?:\/\/[^\/]*/)?.[0] || "") : url;
- this.submitURL();
- e.stopPropagation();
- }
-
@action
forward = () => {
const future = Cast(this.dataDoc[this.fieldKey + "-future"], listSpec("string"), null);
const history = Cast(this.dataDoc[this.fieldKey + "-history"], listSpec("string"), null);
if (future.length) {
history.push(this._url);
- this.dataDoc[this.annotationKey + "-" + this.urlHash(this._url)] = new List<Doc>(DocListCast(this.dataDoc[this.annotationKey]));
this.dataDoc[this.fieldKey] = new WebField(new URL(this._url = future.pop()!));
- this.dataDoc[this.annotationKey] = new List<Doc>(DocListCast(this.dataDoc[this.annotationKey + "-" + this.urlHash(this._url)]));
+ this._annotationKey = this.fieldKey + "-annotations-" + this.urlHash(this._url);
+ return true;
}
+ return false;
}
@action
@@ -228,10 +222,11 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
if (history.length) {
if (future === undefined) this.dataDoc[this.fieldKey + "-future"] = new List<string>([this._url]);
else future.push(this._url);
- this.dataDoc[this.annotationKey + "-" + this.urlHash(this._url)] = new List<Doc>(DocListCast(this.dataDoc[this.annotationKey]));
this.dataDoc[this.fieldKey] = new WebField(new URL(this._url = history.pop()!));
- this.dataDoc[this.annotationKey] = new List<Doc>(DocListCast(this.dataDoc[this.annotationKey + "-" + this.urlHash(this._url)]));
+ this._annotationKey = this.fieldKey + "-annotations-" + this.urlHash(this._url);
+ return true;
}
+ return false;
}
urlHash(s: string) {
@@ -239,34 +234,28 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
}
@action
- submitURL = () => {
- if (!this._url.startsWith("http")) this._url = "http://" + this._url;
+ submitURL = (newUrl: string) => {
+ if (!newUrl.startsWith("http")) newUrl = "http://" + newUrl;
try {
- const URLy = new URL(this._url);
const future = Cast(this.dataDoc[this.fieldKey + "-future"], listSpec("string"), null);
const history = Cast(this.dataDoc[this.fieldKey + "-history"], listSpec("string"), null);
- const annos = DocListCast(this.dataDoc[this.annotationKey]);
const url = Cast(this.dataDoc[this.fieldKey], WebField, null)?.url.toString();
if (url) {
if (history === undefined) {
this.dataDoc[this.fieldKey + "-history"] = new List<string>([url]);
-
} else {
history.push(url);
}
+ this.layoutDoc._scrollTop = 0;
future && (future.length = 0);
- this.dataDoc[this.annotationKey + "-" + this.urlHash(url)] = new List<Doc>(annos);
}
- this.dataDoc[this.fieldKey] = new WebField(URLy);
- this.dataDoc[this.annotationKey] = new List<Doc>([]);
+ this._url = newUrl;
+ this._annotationKey = this.fieldKey + "-annotations-" + this.urlHash(this._url);
+ this.dataDoc[this.fieldKey] = new WebField(new URL(newUrl));
} catch (e) {
console.log("WebBox URL error:" + this._url);
}
- }
-
- onValueKeyDown = async (e: React.KeyboardEvent) => {
- if (e.key === "Enter") this.submitURL();
- e.stopPropagation();
+ return true;
}
editToggleBtn() {
@@ -280,15 +269,10 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
}
_ignore = 0;
- onPreWheel = (e: React.WheelEvent) => { this._ignore = e.timeStamp; };
- onPrePointer = (e: React.PointerEvent) => { this._ignore = e.timeStamp; };
- onPostPointer = (e: React.PointerEvent) => {
- if (this._ignore !== e.timeStamp) e.stopPropagation();
- }
-
- onPostWheel = (e: React.WheelEvent) => {
- if (this._ignore !== e.timeStamp) e.stopPropagation();
- }
+ onPreWheel = (e: React.WheelEvent) => this._ignore = e.timeStamp;
+ onPrePointer = (e: React.PointerEvent) => this._ignore = e.timeStamp;
+ onPostPointer = (e: React.PointerEvent) => this._ignore !== e.timeStamp && e.stopPropagation();
+ onPostWheel = (e: React.WheelEvent) => this._ignore !== e.timeStamp && e.stopPropagation();
onLongPressDown = (e: React.PointerEvent) => {
this._pressX = e.clientX;
@@ -372,22 +356,14 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
}
}, 1500);
}
-
onLongPressMove = (e: PointerEvent) => {
// this._pressX = e.clientX;
// this._pressY = e.clientY;
}
-
onLongPressUp = (e: PointerEvent) => {
- if (this._longPressSecondsHack) {
- clearTimeout(this._longPressSecondsHack);
- }
- if (this._iframeIndicatorRef.current) {
- this._iframeIndicatorRef.current.classList.remove("active");
- }
- if (this._iframeDragRef.current) {
- while (this._iframeDragRef.current.firstChild) this._iframeDragRef.current.removeChild(this._iframeDragRef.current.firstChild);
- }
+ this._longPressSecondsHack && clearTimeout(this._longPressSecondsHack);
+ this._iframeIndicatorRef.current?.classList.remove("active");
+ while (this._iframeDragRef.current?.firstChild) this._iframeDragRef.current.removeChild(this._iframeDragRef.current.firstChild);
}
specificContextMenu = (e: React.MouseEvent): void => {
@@ -396,11 +372,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
funcs.push({ description: (this.layoutDoc.useCors ? "Don't Use" : "Use") + " Cors", event: () => this.layoutDoc.useCors = !this.layoutDoc.useCors, icon: "snowflake" });
funcs.push({ description: (this.layoutDoc[this.fieldKey + "-contentWidth"] ? "Unfreeze" : "Freeze") + " Content Width", event: () => this.layoutDoc[this.fieldKey + "-contentWidth"] = this.layoutDoc[this.fieldKey + "-contentWidth"] ? undefined : Doc.NativeWidth(this.layoutDoc), icon: "snowflake" });
cm.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" });
-
}
- //const href = "https://brown365-my.sharepoint.com/personal/bcz_ad_brown_edu/_layouts/15/Doc.aspx?sourcedoc={31aa3178-4c21-4474-b367-877d0a7135e4}&action=embedview&wdStartOn=1";
-
@computed
get urlContent() {
const field = this.dataDoc[this.props.fieldKey];
@@ -410,7 +383,6 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
} else if (field instanceof WebField) {
const url = this.layoutDoc.useCors ? Utils.CorsProxy(field.url.href) : field.url.href;
// view = <iframe className="webBox-iframe" src={url} onLoad={e => { e.currentTarget.before((e.currentTarget.contentDocument?.body || e.currentTarget.contentDocument)?.children[0]!); e.currentTarget.remove(); }}
-
view = <iframe className="webBox-iframe" enable-annotation={"true"} ref={action((r: HTMLIFrameElement | null) => this._iframe = r)} src={url} onLoad={this.iframeLoaded}
// the 'allow-top-navigation' and 'allow-top-navigation-by-user-activation' attributes are left out to prevent iframes from redirecting the top-level Dash page
// sandbox={"allow-forms allow-modals allow-orientation-lock allow-pointer-lock allow-popups allow-popups-to-escape-sandbox allow-presentation allow-same-origin allow-scripts"} />;
@@ -423,10 +395,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
@computed
get content() {
- const view = this.urlContent;
const frozen = !this.props.isSelected() || DocumentDecorations.Instance?.Interacting;
const scale = this.props.scaling?.() || 1;
-
return (<>
<div className={"webBox-cont" + (this.props.isSelected() && Doc.GetSelectedTool() === InkTool.None && !DocumentDecorations.Instance?.Interacting ? "-interactive" : "")}
style={{
@@ -435,7 +405,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
transform: `scale(${scale})`
}}
onWheel={this.onPostWheel} onPointerDown={this.onPostPointer} onPointerMove={this.onPostPointer} onPointerUp={this.onPostPointer}>
- {view}
+ {this.urlContent}
</div>
{!frozen ? (null) :
<div className="webBox-overlay" style={{ pointerEvents: this.props.layerProvider?.(this.layoutDoc) === false ? undefined : "all" }}
@@ -450,7 +420,6 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
@computed get allAnnotations() { return DocListCast(this.dataDoc[this.props.fieldKey + "-annotations"]); }
@computed get nonDocAnnotations() { return this.allAnnotations.filter(a => a.annotations); }
-
@computed get annotationLayer() {
TraceMobx();
return <div className="webBox-annotationLayer" style={{ height: Doc.NativeHeight(this.Document) || undefined }} ref={this._annotationLayer}>
@@ -471,7 +440,9 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
this.props.select(true);
}
- scrollXf = () => this.props.ScreenToLocalTransform().translate(NumCast(this.layoutDoc._scrollLeft), NumCast(this.layoutDoc._scrollTop));
+ panelWidth = () => this.props.PanelWidth() / (this.props.scaling?.() || 1); // (this.Document.scrollHeight || Doc.NativeHeight(this.Document) || 0);
+ panelHeight = () => this.props.PanelHeight() / (this.props.scaling?.() || 1); // () => this._pageSizes.length && this._pageSizes[0] ? this._pageSizes[0].width : Doc.NativeWidth(this.Document);
+ scrollXf = () => this.props.ScreenToLocalTransform().translate(0, NumCast(this.layoutDoc._scrollTop));
render() {
const inactiveLayer = this.props.layerProvider?.(this.layoutDoc) === false;
const scale = this.props.scaling?.() || 1;
@@ -486,21 +457,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
width: `${100 / scale}%`, height: `${100 / scale}%`, transform: `scale(${scale})`,
pointerEvents: this.layoutDoc.isAnnotating && !inactiveLayer ? "all" : "none"
}}
- onWheel={e => {
- const target = this._outerRef.current;
- if (this._forceSmoothScrollUpdate && target && this.webpage) {
- setTimeout(action(() => {
- target.scrollLeft = 0;
- const scrollTop = target.scrollTop;
- const scrollLeft = target.scrollLeft;
- this.webpage!.scrollTop = scrollTop;
- this.webpage!.scrollLeft = scrollLeft;
- if (this.layoutDoc._scrollTop !== scrollTop) this.layoutDoc._scrollTop = scrollTop;
- if (this.layoutDoc._scrollLeft !== scrollLeft) this.layoutDoc._scrollLeft = scrollLeft;
- }));
- }
- e.stopPropagation();
- }}
+ onWheel={this.onWheel}
onPointerDown={this.onMarqueeDown}
onScroll={e => e.stopPropagation()}
>
@@ -514,11 +471,12 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
fieldKey={this.annotationKey}
isAnnotationOverlay={true}
scaling={returnOne}
+ PanelWidth={this.panelWidth}
+ PanelHeight={this.panelHeight}
ScreenToLocalTransform={this.scrollXf}
removeDocument={this.removeDocument}
moveDocument={this.moveDocument}
addDocument={this.addDocument}
- setPreviewCursor={this.setPreviewCursor}
select={emptyFunction}
active={this.active}
whenActiveChanged={this.whenActiveChanged}>
@@ -527,7 +485,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
</div>
{this.annotationLayer}
{!this._marqueeing || !this._mainCont.current || !this._annotationLayer.current ? (null) :
- <MarqueeAnnotator rootDoc={this.rootDoc} down={this._marqueeing} scaling={this.props.scaling} addDocument={this.addDocument} finishMarquee={this.finishMarquee} savedAnnotations={this._savedAnnotations} annotationLayer={this._annotationLayer.current} mainCont={this._mainCont.current} />}
+ <MarqueeAnnotator rootDoc={this.rootDoc} scrollTop={NumCast(this.rootDoc._scrollTop)} down={this._marqueeing} scaling={this.props.scaling} addDocument={this.addDocument} finishMarquee={this.finishMarquee} savedAnnotations={this._savedAnnotations} annotationLayer={this._annotationLayer.current} mainCont={this._mainCont.current} />}
</div >
{this.props.isSelected() ? this.editToggleBtn() : null}
</div>);
diff --git a/src/client/views/nodes/formattedText/DashDocView.tsx b/src/client/views/nodes/formattedText/DashDocView.tsx
index 1fbd3af5c..91d123efe 100644
--- a/src/client/views/nodes/formattedText/DashDocView.tsx
+++ b/src/client/views/nodes/formattedText/DashDocView.tsx
@@ -236,7 +236,9 @@ export class DashDocView extends React.Component<IDashDocView> {
PanelWidth={finalLayout[WidthSym]}
PanelHeight={finalLayout[HeightSym]}
focus={this.outerFocus}
- styleProvider={returnEmptyString}
+ styleProvider={self._textBox.props.styleProvider}
+ layerProvider={self._textBox.props.layerProvider}
+ docViewPath={self._textBox.props.docViewPath}
parentActive={returnFalse}
whenActiveChanged={returnFalse}
bringToFront={emptyFunction}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss
index 81bca4c00..0f2f9cdb7 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.scss
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss
@@ -41,6 +41,7 @@ audiotag:hover {
display: flex;
flex-direction: row;
transition: opacity 1s;
+ width: 100%;
.formattedTextBox-dictation {
height: 12px;
@@ -389,6 +390,7 @@ footnote::after {
overflow-x: hidden;
color: initial;
max-height: 100%;
+ width: 100%;
display: flex;
flex-direction: row;
transition: opacity 1s;
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index d24ccd9ad..183719e31 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -55,9 +55,8 @@ import { DocumentButtonBar } from '../../DocumentButtonBar';
import { AudioBox } from '../AudioBox';
import { FieldView, FieldViewProps } from "../FieldView";
import "./FormattedTextBox.scss";
-import { FormattedTextBoxComment, formattedTextBoxCommentPlugin, findLinkMark } from './FormattedTextBoxComment';
+import { FormattedTextBoxComment, findLinkMark } from './FormattedTextBoxComment';
import React = require("react");
-import { LinkManager } from '../../../util/LinkManager';
import { CollectionStackingView } from '../../collections/CollectionStackingView';
import { CollectionViewType } from '../../collections/CollectionView';
import { SnappingManager } from '../../../util/SnappingManager';
@@ -67,12 +66,16 @@ import { StyleProp } from '../../StyleProvider';
import { AnchorMenu } from '../../pdf/AnchorMenu';
import { CurrentUserUtils } from '../../../util/CurrentUserUtils';
import { DocumentManager } from '../../../util/DocumentManager';
+import { LightboxView } from '../../LightboxView';
+import { DocAfterFocusFunc } from '../DocumentView';
+const translateGoogleApi = require("translate-google-api");
export interface FormattedTextBoxProps {
makeLink?: () => Opt<Doc>; // bcz: hack: notifies the text document when the container has made a link. allows the text doc to react and setup a hyeprlink for any selected text
hideOnLeave?: boolean; // used by DocumentView for setting caption's hide on leave (bcz: would prefer to have caption-hideOnLeave field set or something similar)
xMargin?: number; // used to override document's settings for xMargin --- see CollectionCarouselView
yMargin?: number;
+ noSidebar?: boolean;
dontSelectOnLoad?: boolean; // suppress selecting the text box when loaded
}
export const GoogleRef = "googleDocId";
@@ -90,11 +93,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
public static Instance: FormattedTextBox;
public ProseRef?: HTMLDivElement;
public get EditorView() { return this._editorView; }
+ public get SidebarKey() { return this.fieldKey + "-sidebar"; }
private _boxRef: React.RefObject<HTMLDivElement> = React.createRef();
private _ref: React.RefObject<HTMLDivElement> = React.createRef();
private _scrollRef: React.RefObject<HTMLDivElement> = React.createRef();
private _editorView: Opt<EditorView>;
- private _applyingChange: boolean = false;
+ private _applyingChange: string = "";
private _searchIndex = 0;
private _cachedLinks: Doc[] = [];
private _undoTyping?: UndoManager.Batch;
@@ -102,15 +106,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
private _dropDisposer?: DragManager.DragDropDisposer;
private _recordingStart: number = 0;
private _pause: boolean = false;
- private _animatingScroll: number = 0; // hack to prevent scroll values from being written to document when scroll is animating
+ private _ignoreScroll = false;
@computed get _recording() { return this.dataDoc?.audioState === "recording"; }
set _recording(value) {
this.dataDoc.audioState = value ? "recording" : undefined;
}
- @observable private _entered = false;
-
public static FocusedBox: FormattedTextBox | undefined;
public static SelectOnLoad = "";
public static PasteOnLoad: ClipboardEvent | undefined;
@@ -162,29 +164,38 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
// TODO: bcz: Argh... if a section of text has multiple anchors, this should just remove the intended one.
// but since removing one anchor from the list of attr anchors isn't implemented, this will end up removing nothing.
public RemoveLinkFromDoc(linkDoc?: Doc) {
+ this.unhighlightSearchTerms();
const state = this._editorView?.state;
- if (state && linkDoc && this._editorView) {
- var allLinks: any[] = [];
+ const a1 = linkDoc?.anchor1 as Doc;
+ const a2 = linkDoc?.anchor2 as Doc;
+ if (state && a1 && a2 && this._editorView) {
+ this.removeDocument(a1);
+ this.removeDocument(a2);
+ var allFoundLinkAnchors: any[] = [];
state.doc.nodesBetween(0, state.doc.nodeSize - 2, (node: any, pos: number, parent: any) => {
- const foundMark = findLinkMark(node.marks);
- const newHrefs = foundMark?.attrs.allLinks.filter((a: any) => a.href.includes(linkDoc[Id])) || [];
- allLinks = newHrefs.length ? newHrefs : allLinks;
+ const foundLinkAnchors = findLinkMark(node.marks)?.attrs.allAnchors.filter((a: any) => a.anchorId === a1[Id] || a.anchorId === a2[Id]) || [];
+ allFoundLinkAnchors = foundLinkAnchors.length ? foundLinkAnchors : allFoundLinkAnchors;
return true;
});
- if (allLinks.length) {
- this._editorView.dispatch(removeMarkWithAttrs(state.tr, 0, state.doc.nodeSize - 2, state.schema.marks.linkAnchor, { allLinks }));
+ if (allFoundLinkAnchors.length) {
+ this._editorView.dispatch(removeMarkWithAttrs(state.tr, 0, state.doc.nodeSize - 2, state.schema.marks.linkAnchor, { allAnchors: allFoundLinkAnchors }));
+
+ this.setupEditor(this.config, this.fieldKey);
}
}
}
- // removes all the specified link referneces from the selection.
+ // removes all the specified link references from the selection.
// NOTE: as above, this won't work correctly if there are marks with overlapping but not exact sets of link references.
- public RemoveLinkFromSelection(allLinks: { href: string, title: string, linkId: string, targetId: string }[]) {
+ public RemoveAnchorFromSelection(allAnchors: { href: string, title: string, linkId: string, targetId: string }[]) {
const state = this._editorView?.state;
if (state && this._editorView) {
- this._editorView.dispatch(removeMarkWithAttrs(state.tr, state.selection.from, state.selection.to, state.schema.marks.link, { allLinks }));
+ this._editorView.dispatch(removeMarkWithAttrs(state.tr, state.selection.from, state.selection.to, state.schema.marks.link, { allAnchors }));
+ this.setupEditor(this.config, this.fieldKey);
}
}
+ getAnchor = () => this.makeLinkAnchor(undefined, "add:right", undefined, "Anchored Selection");
+
linkOnDeselect: Map<string, string> = new Map();
doLinkOnDeselect() {
@@ -232,11 +243,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(this.rootDoc, () => this.rootDoc, targetCreator), e.pageX, e.pageY, {
dragComplete: e => {
+ const anchor = this.makeLinkAnchor(undefined, "add:right", undefined, "a link");
if (!e.aborted && e.annoDragData && e.annoDragData.annotationDocument && e.annoDragData.dropDocument && !e.linkDocument) {
- e.linkDocument = DocUtils.MakeLink({ doc: e.annoDragData.annotationDocument }, { doc: e.annoDragData.dropDocument }, "hyperlink", "link to note");
+ e.linkDocument = DocUtils.MakeLink({ doc: anchor }, { doc: e.annoDragData.dropDocument }, "hyperlink", "link to note");
e.annoDragData.annotationDocument.isPushpin = e.annoDragData?.dropDocument.annotationOn === this.rootDoc;
}
- e.linkDocument && e.annoDragData?.dropDocument && this.makeLinkToSelection(e.linkDocument[Id], "a link", "add:right", e.annoDragData.dropDocument[Id]);
e.linkDocument && e.annoDragData?.linkDropCallback?.(e as { linkDocument: Doc });// bcz: typescript can't figure out that this is valid even though we tested e.linkDocument
}
});
@@ -246,6 +257,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
this.props.isSelected(true) && AnchorMenu.Instance.jumpTo(Math.min(coordsT.left, coordsB.left), Math.max(coordsT.bottom, coordsB.bottom));
}
+ _lastText = "";
dispatchTransaction = (tx: Transaction) => {
let timeStamp;
clearTimeout(timeStamp);
@@ -267,8 +279,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
this.linkOnDeselect.set(key, value);
const id = Utils.GenerateDeterministicGuid(this.dataDoc[Id] + key);
- const allLinks = [{ href: Utils.prepend("/doc/" + id), title: value, targetId: id }];
- const link = this._editorView.state.schema.marks.linkAnchor.create({ allLinks, location: "add:right", title: value });
+ const allAnchors = [{ href: Utils.prepend("/doc/" + id), title: value, anchorId: id }];
+ const link = this._editorView.state.schema.marks.linkAnchor.create({ allAnchors, location: "add:right", title: value });
const mval = this._editorView.state.schema.marks.metadataVal.create();
const offset = (tx.selection.to === range!.end - 1 ? -1 : 0);
tx = tx.addMark(textEndSelection - value.length + offset, textEndSelection, link).addMark(textEndSelection - value.length + offset, textEndSelection, mval);
@@ -296,8 +308,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
};
if (effectiveAcl === AclEdit || effectiveAcl === AclAdmin) {
- if (!this._applyingChange && removeSelection(json) !== removeSelection(curProto?.Data)) {
- this._applyingChange = true;
+ if (this._applyingChange !== this.fieldKey && removeSelection(json) !== removeSelection(curProto?.Data)) {
+ this._applyingChange = this.fieldKey;
(curText !== Cast(this.dataDoc[this.fieldKey], RichTextField)?.Text) && (this.dataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now())));
if ((!curTemp && !curProto) || curText || json.includes("dash")) { // if no template, or there's text that didn't come from the layout template, write it to the document. (if this is driven by a template, then this overwrites the template text which is intended)
if (removeSelection(json) !== removeSelection(curLayout?.Data)) {
@@ -325,7 +337,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
this.dataDoc[this.props.fieldKey + "-noTemplate"] = undefined; // mark the data field as not being split from any template it might have
unchanged = false;
}
- this._applyingChange = false;
+ this._applyingChange = "";
if (!unchanged) {
this.updateTitle();
this.tryUpdateHeight();
@@ -404,9 +416,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
res.map(r => r.map(h => flattened.push(h)));
const lastSel = Math.min(flattened.length - 1, this._searchIndex);
this._searchIndex = ++this._searchIndex > flattened.length - 1 ? 0 : this._searchIndex;
- const alink = DocUtils.MakeLink({ doc: this.rootDoc }, { doc: target }, "automatic")!;
- const allLinks = [{ href: Utils.prepend("/doc/" + alink[Id]), title: "a link", targetId: target[Id], linkId: alink[Id] }];
- const link = this._editorView.state.schema.marks.linkAnchor.create({ allLinks, title: "a link", location });
+ const anchor = new Doc();
+ const alink = DocUtils.MakeLink({ doc: anchor }, { doc: target }, "automatic")!;
+ const allAnchors = [{ href: Utils.prepend("/doc/" + anchor[Id]), title: "a link", anchorId: anchor[Id] }];
+ const link = this._editorView.state.schema.marks.linkAnchor.create({ allAnchors, title: "a link", location });
this._editorView.dispatch(tr.addMark(flattened[lastSel].from, flattened[lastSel].to, link));
}
}
@@ -504,8 +517,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
linkDrop = (data: { linkDocument: Doc }) => {
const anchor1Title = data.linkDocument.anchor1 instanceof Doc ? StrCast(data.linkDocument.anchor1.title) : "-untitled-";
- const anchor1Id = data.linkDocument.anchor1 instanceof Doc ? data.linkDocument.anchor1[Id] : "";
- this.makeLinkToSelection(data.linkDocument[Id], anchor1Title, "add:right", anchor1Id);
+ const anchor1 = data.linkDocument.anchor1 instanceof Doc ? data.linkDocument.anchor1 : undefined;
+ this.makeLinkAnchor(anchor1, "add:right", undefined, anchor1Title);
}
getNodeEndpoints(context: Node, node: Node): { from: number, to: number } | null {
@@ -602,6 +615,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
const bounds = this.CurrentDiv.getBoundingClientRect();
this.layoutDoc._sidebarWidthPercent = "" + 100 * Math.max(0, (1 - (e.clientX - bounds.left) / bounds.width)) + "%";
this.layoutDoc._showSidebar = this.layoutDoc._sidebarWidthPercent !== "0%";
+ e.preventDefault();
return false;
}
@undoBatch
@@ -816,43 +830,51 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
props: {
attributes: { class: "ProseMirror-example-setup-style" }
}
- }),
- formattedTextBoxCommentPlugin
+ }), new Plugin({ view(editorView) { return new FormattedTextBoxComment(editorView); } })
]
};
}
- makeLinkToSelection(linkId: string, title: string, location: string, targetId: string, targetHref?: string) {
+ makeLinkAnchor(anchorDoc?: Doc, location?: string, targetHref?: string, title?: string) {
const state = this._editorView?.state;
if (state) {
- const href = targetHref ?? Utils.prepend("/doc/" + linkId);
const sel = state.selection;
const splitter = state.schema.marks.splitter.create({ id: Utils.GenerateGuid() });
let tr = state.tr.addMark(sel.from, sel.to, splitter);
- sel.from !== sel.to && tr.doc.nodesBetween(sel.from, sel.to, (node: any, pos: number, parent: any) => {
- if (node.firstChild === null && node.marks.find((m: Mark) => m.type.name === schema.marks.splitter.name)) {
- const allLinks = [{ href, title, targetId, linkId }];
- allLinks.push(...(node.marks.find((m: Mark) => m.type.name === schema.marks.linkAnchor.name)?.attrs.allLinks ?? []));
- const link = state.schema.marks.linkAnchor.create({ allLinks, title, location, linkId });
- tr = tr.addMark(pos, pos + node.nodeSize, link);
- }
- });
- this.dataDoc[ForceServerWrite] = this.dataDoc[UpdatingFromServer] = true; // need to allow permissions for adding links to readonly/augment only documents
- this._editorView!.dispatch(tr.removeMark(sel.from, sel.to, splitter));
- this.dataDoc[UpdatingFromServer] = this.dataDoc[ForceServerWrite] = false;
+ if (sel.from !== sel.to) {
+ const anchor = anchorDoc ?? Docs.Create.TextanchorDocument();
+ const href = targetHref ?? Utils.prepend("/doc/" + anchor[Id]);
+ if (anchor !== anchorDoc) this.addDocument(anchor);
+ tr.doc.nodesBetween(sel.from, sel.to, (node: any, pos: number, parent: any) => {
+ if (node.firstChild === null && node.marks.find((m: Mark) => m.type.name === schema.marks.splitter.name)) {
+ const allAnchors = [{ href, title, anchorId: anchor[Id] }];
+ allAnchors.push(...(node.marks.find((m: Mark) => m.type.name === schema.marks.linkAnchor.name)?.attrs.allAnchors ?? []));
+ const link = state.schema.marks.linkAnchor.create({ allAnchors, title, location });
+ tr = tr.addMark(pos, pos + node.nodeSize, link);
+ }
+ });
+ this.dataDoc[ForceServerWrite] = this.dataDoc[UpdatingFromServer] = true; // need to allow permissions for adding links to readonly/augment only documents
+ this._editorView!.dispatch(tr.removeMark(sel.from, sel.to, splitter));
+ this.dataDoc[UpdatingFromServer] = this.dataDoc[ForceServerWrite] = false;
+ Doc.GetProto(anchor).title = this._editorView?.state.doc.textBetween(sel.from, sel.to);
+ return anchor;
+ }
+ return anchorDoc ?? this.rootDoc;
}
+ return anchorDoc ?? this.rootDoc;
}
IsActive = () => {
return this.active();//this.props.isSelected() || this._isChildActive || this.props.renderDepth === 0;
}
- scrollToLinkId = (linkId: string) => {
- const findLinkFrag = (frag: Fragment, editor: EditorView) => {
+ scrollFocus = (doc: Doc, smooth: boolean, afterFocus?: DocAfterFocusFunc) => {
+ const anchorId = doc[Id];
+ const findAnchorFrag = (frag: Fragment, editor: EditorView) => {
const nodes: Node[] = [];
let hadStart = start !== 0;
frag.forEach((node, index) => {
- const examinedNode = findLinkNode(node, editor);
+ const examinedNode = findAnchorNode(node, editor);
if (examinedNode?.node.textContent) {
nodes.push(examinedNode.node);
!hadStart && (start = index + examinedNode.start);
@@ -861,20 +883,20 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
});
return { frag: Fragment.fromArray(nodes), start };
};
- const findLinkNode = (node: Node, editor: EditorView) => {
+ const findAnchorNode = (node: Node, editor: EditorView) => {
if (!node.isText) {
- const content = findLinkFrag(node.content, editor);
+ const content = findAnchorFrag(node.content, editor);
return { node: node.copy(content.frag), start: content.start };
}
const marks = [...node.marks];
const linkIndex = marks.findIndex(mark => mark.type === editor.state.schema.marks.linkAnchor);
- return linkIndex !== -1 && marks[linkIndex].attrs.allLinks.find((item: { href: string }) => linkId === item.href.replace(/.*\/doc\//, "")) ? { node, start: 0 } : undefined;
+ return linkIndex !== -1 && marks[linkIndex].attrs.allAnchors.find((item: { href: string }) => anchorId === item.href.replace(/.*\/doc\//, "")) ? { node, start: 0 } : undefined;
};
let start = 0;
- if (this._editorView && linkId) {
+ if (this._editorView && anchorId) {
const editor = this._editorView;
- const ret = findLinkFrag(editor.state.doc.content, editor);
+ const ret = findAnchorFrag(editor.state.doc.content, editor);
if (ret.frag.size > 2 && ret.start >= 0) {
let selection = TextSelection.near(editor.state.doc.resolve(ret.start)); // default to near the start
@@ -882,16 +904,20 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
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
}
editor.dispatch(editor.state.tr.setSelection(new TextSelection(selection.$from, selection.$from)).scrollIntoView());
- const mark = editor.state.schema.mark(this._editorView.state.schema.marks.search_highlight);
- setTimeout(() => editor.dispatch(editor.state.tr.addMark(selection.from, selection.to, mark)), 0);
- setTimeout(() => this.unhighlightSearchTerms(), 2000);
+ const escAnchorId = anchorId[0] > '0' && anchorId[0] <= '9' ? `\\3${anchorId[0]} ${anchorId.substr(1)}` : anchorId;
+ addStyleSheetRule(FormattedTextBox._highlightStyleSheet, `${escAnchorId}`, { background: "yellow" });
+ setTimeout(() => {
+ clearStyleSheetRules(FormattedTextBox._highlightStyleSheet);
+ afterFocus?.(true);
+ }, 1500);
}
- Doc.SetInPlace(this.layoutDoc, "scrollToLinkID", undefined, false);
- Doc.SetInPlace(this.layoutDoc, "_scrollToPreviewLinkID", undefined, false);
+ } else {
+ afterFocus?.(false);
}
}
componentDidMount() {
+ this.props.setContentView?.(this); // this tells the DocumentView that this AudioBox is the "content" of the document. this allows the DocumentView to indirectly call getAnchor() on the AudioBox when making a link.
this.props.contentsActive?.(this.IsActive);
this._cachedLinks = DocListCast(this.Document.links);
this._disposers.sidebarheight = reaction(
@@ -913,34 +939,23 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
}
);
- this._disposers.linkMaker = reaction(
- () => this.props.makeLink?.(),
- (linkDoc: Opt<Doc>) => {
- if (linkDoc) {
- const a1 = Cast(linkDoc.anchor1, Doc, null);
- const a2 = Cast(linkDoc.anchor2, Doc, null);
- const otherAnchor = Doc.AreProtosEqual(a1, this.rootDoc) ? a2 : a1;
- const anchor2Title = StrCast(otherAnchor.title, "-untitled-");
- const anchor2Id = otherAnchor?.[Id] || "";
- this.makeLinkToSelection(linkDoc[Id], anchor2Title, "add:right", anchor2Id);
- }
- },
- { fireImmediately: true }
- );
this._disposers.editorState = reaction(
() => {
- if (!this.dataDoc || !this.layoutDoc) return undefined;
- if (this.dataDoc?.[this.props.fieldKey + "-noTemplate"] || !this.layoutDoc[this.props.fieldKey]) {
- return Cast(this.dataDoc[this.props.fieldKey], RichTextField, null)?.Data;
- }
- return Cast(this.layoutDoc[this.props.fieldKey], RichTextField, null)?.Data;
+ const whichDoc = !this.dataDoc || !this.layoutDoc ? undefined :
+ this.dataDoc?.[this.props.fieldKey + "-noTemplate"] || !this.layoutDoc[this.props.fieldKey] ?
+ this.dataDoc : this.layoutDoc;
+ return !whichDoc ? undefined : { data: Cast(whichDoc[this.props.fieldKey], RichTextField, null), str: StrCast(whichDoc[this.props.fieldKey]) };
},
incomingValue => {
- if (incomingValue !== undefined && this._editorView && !this._applyingChange) {
- const updatedState = JSON.parse(incomingValue);
- if (JSON.stringify(this._editorView.state.toJSON()) !== JSON.stringify(updatedState)) {
- this._editorView.updateState(EditorState.fromJSON(this.config, updatedState));
- this.tryUpdateHeight();
+ if (this._editorView && this._applyingChange !== this.fieldKey) {
+ if (incomingValue?.data) {
+ const updatedState = JSON.parse(incomingValue.data.Data);
+ if (JSON.stringify(this._editorView.state.toJSON()) !== JSON.stringify(updatedState)) {
+ this._editorView.updateState(EditorState.fromJSON(this.config, updatedState));
+ this.tryUpdateHeight();
+ }
+ } else if (incomingValue?.str) {
+ selectAll(this._editorView.state, tx => this._editorView?.dispatch(tx.insertText(incomingValue.str)));
}
}
},
@@ -1006,38 +1021,23 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
);
}
- this._disposers.previewScrollToRegion = reaction(() => StrCast(this.layoutDoc._scrollToPreviewLinkID),
- scrollToLinkID => this.props.renderDepth === -1 && scrollToLinkID && this.scrollToLinkId(scrollToLinkID), { fireImmediately: true }
- );
- this._disposers.scrollToRegion = reaction(() => StrCast(this.layoutDoc.scrollToLinkID),
- scrollToLinkID => scrollToLinkID && this.scrollToLinkId(scrollToLinkID), { fireImmediately: true }
- );
+ var quickScroll: string | undefined = "";
this._disposers.scroll = reaction(() => NumCast(this.layoutDoc._scrollTop),
pos => {
- const durationMiliStr = StrCast(this.Document._viewTransition).match(/([0-9]*)ms/);
- const durationSecStr = StrCast(this.Document._viewTransition).match(/([0-9.]*)s/);
- const duration = durationMiliStr ? Number(durationMiliStr[1]) : durationSecStr ? Number(durationSecStr[1]) * 1000 : 0;
- if (duration) {
- this._animatingScroll++;
- this._scrollRef.current && smoothScroll(duration, this._scrollRef.current, Math.abs(pos || 0));
- setTimeout(() => this._animatingScroll--, duration);
- } else {
- this._scrollRef.current?.scrollTo({ top: pos });
+ if (!this._ignoreScroll && this._scrollRef.current) {
+ const viewTrans = quickScroll ?? StrCast(this.Document._viewTransition);
+ const durationMiliStr = viewTrans.match(/([0-9]*)ms/);
+ const durationSecStr = viewTrans.match(/([0-9.]*)s/);
+ const duration = durationMiliStr ? Number(durationMiliStr[1]) : durationSecStr ? Number(durationSecStr[1]) * 1000 : 0;
+ if (duration) {
+ smoothScroll(duration, this._scrollRef.current, Math.abs(pos || 0));
+ } else {
+ this._scrollRef.current.scrollTo({ top: pos });
+ }
}
}, { fireImmediately: true }
);
- this._disposers.scrollY = reaction(() => Cast(this.layoutDoc._scrollY, "number", null),
- pos => {
- this.Document._scrollY = undefined;
- pos !== undefined && setTimeout(() => this.layoutDoc._scrollTop = pos, this._scrollRef.current ? 0 : 250);
- }, { fireImmediately: true }
- );
- this._disposers.scrollPreviewY = reaction(() => Cast(this.layoutDoc.scrollPreviewY, "number", null),
- pos => {
- this.Document.scrollPreviewY = undefined;
- pos !== undefined && setTimeout(() => this.layoutDoc._scrollTop = pos, this._scrollRef.current ? 0 : 250);
- }, { fireImmediately: true }
- );
+ quickScroll = undefined;
setTimeout(() => this.tryUpdateHeight(NumCast(this.layoutDoc.limitHeight)));
}
@@ -1221,8 +1221,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
handleScrollToSelection: (editorView) => {
const docPos = editorView.coordsAtPos(editorView.state.selection.from);
const viewRect = self._ref.current!.getBoundingClientRect();
- if (docPos.top < viewRect.top || docPos.top > viewRect.bottom) {
- docPos && (self._scrollRef.current!.scrollTop += (docPos.top - viewRect.top) * self.props.ScreenToLocalTransform().Scale);
+ const scrollRef = self._scrollRef.current;
+ if ((docPos.top < viewRect.top || docPos.top > viewRect.bottom) && scrollRef) {
+ const scrollPos = scrollRef.scrollTop + (docPos.top - viewRect.top) * self.props.ScreenToLocalTransform().Scale;
+ if (!LinkDocPreview.LinkInfo) {
+ scrollPos && smoothScroll(500, scrollRef, scrollPos);
+ } else {
+ scrollRef.scrollTo({ top: scrollPos });
+ }
}
return true;
},
@@ -1238,7 +1244,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
clipboardTextSerializer: this.clipboardTextSerializer,
handlePaste: this.handlePaste,
});
- // !Doc.UserDoc().noviceMode && applyDevTools.applyDevTools(this._editorView);
const startupText = !rtfField && this._editorView && Field.toString(this.dataDoc[fieldKey] as Field);
if (startupText) {
const { state: { tr }, dispatch } = this._editorView;
@@ -1247,7 +1252,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
(this._editorView as any).TextView = this;
}
- const selectOnLoad = this.rootDoc[Id] === FormattedTextBox.SelectOnLoad;
+ const selectOnLoad = this.rootDoc[Id] === FormattedTextBox.SelectOnLoad && (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this.props.docViewPath()));
if (selectOnLoad && !this.props.dontRegisterView && !this.props.dontSelectOnLoad && this.isActiveTab(this.ProseRef)) {
FormattedTextBox.SelectOnLoad = "";
this.props.select(false);
@@ -1341,7 +1346,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
onPointerUp = (e: React.PointerEvent): void => {
if (!this._downEvent) return;
this._downEvent = false;
- if (!(e.nativeEvent as any).formattedHandled) {
+ if (!(e.nativeEvent as any).formattedHandled && this.active(true)) {
const editor = this._editorView!;
FormattedTextBoxComment.textBox = this;
const pcords = editor.posAtCoords({ left: e.clientX, top: e.clientY });
@@ -1369,13 +1374,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
e.preventDefault();
}
FormattedTextBoxComment.Hide();
- if (FormattedTextBoxComment.linkDoc) {
- if (FormattedTextBoxComment.linkDoc.type !== DocumentType.LINK) {
- this.props.addDocTab(FormattedTextBoxComment.linkDoc, e.ctrlKey ? "add" : "add:right");
- } else {
- LinkManager.FollowLink(FormattedTextBoxComment.linkDoc, this.props.Document, this.props, false);
- }
- }
(e.nativeEvent as any).formattedHandled = true;
@@ -1388,6 +1386,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
onFocused = (e: React.FocusEvent): void => {
FormattedTextBox.FocusedBox = this;
this.tryUpdateHeight();
+ //applyDevTools.applyDevTools(this._editorView);
// see if we need to preserve the insertion point
const prosediv = this.ProseRef?.children?.[0] as any;
@@ -1416,6 +1415,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
}
+ static _highlightStyleSheet: any = addStyleSheet();
static _bulletStyleSheet: any = addStyleSheet();
static _userStyleSheet: any = addStyleSheet();
_forceUncollapse = true; // if the cursor doesn't move between clicks, then the selection will disappear for some reason. This flags the 2nd click as happening on a selection which allows bullet points to toggle
@@ -1541,6 +1541,19 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
FormattedTextBox.LiveTextUndo?.end();
FormattedTextBox.LiveTextUndo = undefined;
+
+ const state = this._editorView!.state;
+ const curText = state.doc.textBetween(0, state.doc.content.size, " \n");
+ if (this.layoutDoc.sidebarViewType === "translation" && !this.fieldKey.includes("translation") && curText.endsWith(" ") && curText !== this._lastText) {
+ try {
+ translateGoogleApi(curText, { from: "en", to: "es", }).then((result1: any) => {
+ setTimeout(() => translateGoogleApi(result1[0], { from: "es", to: "en", }).then((result: any) => {
+ this.dataDoc[this.fieldKey + "-translation"] = result1 + "\r\n\r\n" + result[0];
+ }), 1000);
+ });
+ } catch (e) { console.log(e.message); }
+ this._lastText = curText;
+ }
}
_lastTimedMark: Mark | undefined = undefined;
@@ -1589,9 +1602,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
this._editorView!.dispatch(updateBullets(this._editorView!.state.tr, this._editorView!.state.schema));
eve.stopPropagation(); // drag n drop of text within text note will generate a new note if not caughst, as will dragging in from outside of Dash.
}
- onscrolled = (ev: React.UIEvent) => {
- if (!LinkDocPreview.TargetDoc && !FormattedTextBoxComment.linkDoc && !this._animatingScroll) {
- this.layoutDoc._scrollTop = this._scrollRef.current!.scrollTop;
+ onScroll = (ev: React.UIEvent) => {
+ if (!LinkDocPreview.LinkInfo && this._scrollRef.current) {
+ this._ignoreScroll = true;
+ this.layoutDoc._scrollTop = this._scrollRef.current.scrollTop;
+ this._ignoreScroll = false;
}
}
@@ -1607,8 +1622,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
this.layoutDoc.limitHeight = undefined;
this.layoutDoc._autoHeight = false;
}
- const nh = this.layoutDoc.isTemplateForField ? 0 : NumCast(this.layoutDoc._nativeHeight, 0);
- const dh = NumCast(this.rootDoc._height, 0);
+ const nh = this.layoutDoc.isTemplateForField ? 0 : NumCast(this.layoutDoc._nativeHeight);
+ const dh = NumCast(this.rootDoc._height);
const newHeight = Math.max(10, (nh ? dh / nh * scrollHeight : scrollHeight) + this.titleHeight);
if (this.rootDoc !== this.layoutDoc.doc && !this.layoutDoc.resolvedDataDoc) {
// if we have a template that hasn't been resolved yet, we can't set the height or we'd be setting it on the unresolved template. So set a timeout and hope its arrived...
@@ -1635,8 +1650,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
@computed get sidebarHandle() {
- const annotated = DocListCast(this.dataDoc[this.annotationKey]).filter(d => d?.author).length;
- return !this.props.isSelected() && !(annotated && !this.sidebarWidth()) ? (null) :
+ const annotated = DocListCast(this.dataDoc[this.SidebarKey]).filter(d => d?.author).length;
+ return this.props.noSidebar || (!this.props.isSelected() && !(annotated && !this.sidebarWidth())) ? (null) :
<div className="formattedTextBox-sidebar-handle"
style={{ left: `max(0px, calc(100% - ${this.sidebarWidthPercent} ${this.sidebarWidth() ? "- 5px" : "- 10px"}))`, background: annotated ? "lightblue" : this.props.styleProvider?.(this.props.Document, this.props, StyleProp.WidgetColor) }}
onPointerDown={this.sidebarDown}
@@ -1655,9 +1670,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
xMargin: 0,
yMargin: 0,
chromeStatus: "enabled",
- scaleField: this.annotationKey + "-scale",
+ scaleField: this.SidebarKey + "-scale",
isAnnotationOverlay: true,
- fieldKey: this.annotationKey,
+ fieldKey: this.SidebarKey,
fitContentsToDoc: fitToBox,
select: emptyFunction,
active: this.annotationsActive,
@@ -1670,12 +1685,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
ScreenToLocalTransform: this.sidebarScreenToLocal,
renderDepth: this.props.renderDepth + 1,
};
- return !this.layoutDoc._showSidebar || this.sidebarWidthPercent === "0%" ? (null) :
+ return this.props.noSidebar || !this.layoutDoc._showSidebar || this.sidebarWidthPercent === "0%" ? (null) :
<div className={"formattedTextBox-sidebar" + (Doc.GetSelectedTool() !== InkTool.None ? "-inking" : "")}
style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}>
- {this.layoutDoc.sidebarViewType === CollectionViewType.Freeform ?
- <CollectionFreeFormView {...collectionProps} /> :
- <CollectionStackingView {...collectionProps} />}
+ {this.layoutDoc.sidebarViewType === "translation" ?
+ <FormattedTextBox {...collectionProps} noSidebar={true} fieldKey={`${this.fieldKey}-translation`} /> :
+ this.layoutDoc.sidebarViewType === CollectionViewType.Freeform ?
+ <CollectionFreeFormView {...collectionProps} /> :
+ <CollectionStackingView {...collectionProps} />}
</div>;
}
@@ -1709,7 +1726,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
<div className={`formattedTextBox-cont`} ref={this._ref}
style={{
overflow: this.layoutDoc._autoHeight ? "hidden" : undefined,
- width: "100%",
height: this.props.height || (this.layoutDoc._autoHeight && this.props.renderDepth ? "max-content" : undefined),
background: this.props.background ? this.props.background : StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor)),
color: this.props.color ? this.props.color : StrCast(this.layoutDoc[this.props.fieldKey + "-color"], this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Color)),
@@ -1728,14 +1744,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
onPointerDown={this.onPointerDown}
onMouseUp={this.onMouseUp}
onWheel={this.onPointerWheel}
- onPointerEnter={action(() => this._entered = true)}
- onPointerLeave={action(e => {
- this._entered = false;
- const target = document.elementFromPoint(e.nativeEvent.x, e.nativeEvent.y);
- for (let child: any = target; child; child = child?.parentElement) {
- child === this._ref.current! && (this._entered = true);
- }
- })}
onDoubleClick={this.onDoubleClick}
>
<div className={`formattedTextBox-outer${selected ? "-selected" : ""}`} ref={this._scrollRef}
@@ -1744,7 +1752,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
pointerEvents: !active && !SnappingManager.GetIsDragging() ? "none" : undefined,
overflow: this.layoutDoc._singleLine ? "hidden" : undefined,
}}
- onScroll={this.onscrolled} onDrop={this.ondrop} >
+ onScroll={this.onScroll} onDrop={this.ondrop} >
<div className={minimal ? "formattedTextBox-minimal" : `formattedTextBox-inner${rounded}${selPaddingClass}`} ref={this.createDropTarget}
style={{
padding: this.layoutDoc._textBoxPadding ? StrCast(this.layoutDoc._textBoxPadding) : `${padding}px`,
diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.scss b/src/client/views/nodes/formattedText/FormattedTextBoxComment.scss
index 81afba4d7..55b8446e9 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.scss
+++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.scss
@@ -1,6 +1,8 @@
-.FormattedTextBox-tooltip {
+.formattedTextBox-tooltip {
position: absolute;
- pointer-events: none;
+ pointer-events: all;
+ height: 100%;
+ overflow: hidden;
z-index: 20;
background: white;
border: 1px solid silver;
@@ -9,70 +11,16 @@
-webkit-transform: translateX(-50%);
transform: translateX(-50%);
box-shadow: 3px 3px 1.5px grey;
-
- .FormattedTextBoxComment {
- background-color: white;
- border: 8px solid white;
- //width: 200px;
-
- //display: flex;
- .FormattedTextBoxComment-info {
-
- margin-bottom: 37px;
-
- .FormattedTextBoxComment-title {
- padding-right: 4px;
- float: left;
-
- .FormattedTextBoxComment-description {
- text-decoration: none;
- font-style: italic;
- color: rgb(95, 97, 102);
- font-size: 10px;
- }
- }
-
- .FormattedTextBoxComment-button {
- display: inline;
- padding-left: 6px;
- padding-right: 6px;
- padding-top: 2.5px;
- padding-bottom: 2.5px;
- width: 17px;
- height: 17px;
- margin: 0;
- margin-right: 3px;
- border-radius: 50%;
- pointer-events: auto;
- background-color: rgb(0, 0, 0);
- color: rgb(255, 255, 255);
- transition: transform 0.2s;
- text-align: center;
- position: relative;
- font-size: 12px;
-
- &:hover {
- background-color: rgb(77, 77, 77);
- cursor: pointer;
- }
- }
- }
-
- .FormattedTextBoxComment-preview-wrapper {
- //width: 170px;
- height: 170px;
- overflow: hidden;
- //padding-top: 5px;
- margin-top: 10px;
- margin-bottom: 8px;
- align-content: center;
- justify-content: center;
- background-color: rgb(160, 160, 160);
- }
+ max-width: 400;
+ max-height: 235;
+ height:max-content;
+ .formattedTextBox-tooltipText {
+ height: max-content;
+ text-overflow: ellipsis;
}
}
-.FormattedTextBox-tooltip:before {
+.formattedTextBox-tooltip:before {
content: "";
height: 0;
width: 0;
@@ -85,7 +33,7 @@
border-top-color: silver;
}
-.FormattedTextBox-tooltip:after {
+.formattedTextBox-tooltip:after {
content: "";
height: 0;
width: 0;
@@ -96,12 +44,4 @@
border: 5px solid transparent;
border-bottom-width: 0;
border-top-color: white;
-}
-
-.FormattedTextBoxComment-buttons {
- display: none;
- position: absolute;
- top: 50%;
- right: 0;
- transform: translateY(-50%);
} \ No newline at end of file
diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
index 5371bd10a..89df5e246 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
@@ -1,43 +1,17 @@
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { Tooltip } from "@material-ui/core";
-import { action, observable } from "mobx";
import { Mark, ResolvedPos } from "prosemirror-model";
-import { EditorState, Plugin } from "prosemirror-state";
+import { EditorState } from "prosemirror-state";
import { EditorView } from "prosemirror-view";
-import * as ReactDOM from 'react-dom';
-import wiki from "wikijs";
-import { Doc, DocCastAsync, DocListCast, Opt } from "../../../../fields/Doc";
-import { Cast, FieldValue, NumCast, StrCast } from "../../../../fields/Types";
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, Utils } from "../../../../Utils";
-import { DocServer } from "../../../DocServer";
-import { Docs } from "../../../documents/Documents";
-import { DocumentType } from "../../../documents/DocumentTypes";
-import { LinkManager } from "../../../util/LinkManager";
-import { Transform } from "../../../util/Transform";
-import { undoBatch } from "../../../util/UndoManager";
-import { DocumentLinksButton } from "../DocumentLinksButton";
-import { DocumentView } from "../DocumentView";
+import { Doc } from "../../../../fields/Doc";
import { LinkDocPreview } from "../LinkDocPreview";
import { FormattedTextBox } from "./FormattedTextBox";
import './FormattedTextBoxComment.scss';
import { schema } from "./schema_rts";
-import React = require("react");
-export let formattedTextBoxCommentPlugin = new Plugin({
- view(editorView) { return new FormattedTextBoxComment(editorView); }
-});
-export function findOtherUserMark(marks: Mark[]): Mark | undefined {
- return marks.find(m => m.attrs.userid && m.attrs.userid !== Doc.CurrentUserEmail);
-}
-export function findUserMark(marks: Mark[]): Mark | undefined {
- return marks.find(m => m.attrs.userid);
-}
-export function findLinkMark(marks: Mark[]): Mark | undefined {
- return marks.find(m => m.type === schema.marks.linkAnchor);
-}
+export function findOtherUserMark(marks: Mark[]): Mark | undefined { return marks.find(m => m.attrs.userid && m.attrs.userid !== Doc.CurrentUserEmail); }
+export function findUserMark(marks: Mark[]): Mark | undefined { return marks.find(m => m.attrs.userid); }
+export function findLinkMark(marks: Mark[]): Mark | undefined { return marks.find(m => m.type === schema.marks.linkAnchor); }
export function findStartOfMark(rpos: ResolvedPos, view: EditorView, finder: (marks: Mark[]) => Mark | undefined) {
- let before = 0;
- let nbef = rpos.nodeBefore;
+ let before = 0, nbef = rpos.nodeBefore;
while (nbef && finder(nbef.marks)) {
before += nbef.nodeSize;
rpos = view.state.doc.resolve(rpos.pos - nbef.nodeSize);
@@ -46,8 +20,7 @@ export function findStartOfMark(rpos: ResolvedPos, view: EditorView, finder: (ma
return before;
}
export function findEndOfMark(rpos: ResolvedPos, view: EditorView, finder: (marks: Mark[]) => Mark | undefined) {
- let after = 0;
- let naft = rpos.nodeAfter;
+ let after = 0, naft = rpos.nodeAfter;
while (naft && finder(naft.marks)) {
after += naft.nodeSize;
rpos = view.state.doc.resolve(rpos.pos + naft.nodeSize);
@@ -59,283 +32,93 @@ export function findEndOfMark(rpos: ResolvedPos, view: EditorView, finder: (mark
// this view appears when clicking on text that has a hyperlink which is configured to show a preview of its target.
// this will also display metadata information about text when the view is configured to display things like other people who authored text.
//
-
export class FormattedTextBoxComment {
static tooltip: HTMLElement;
static tooltipText: HTMLElement;
- static tooltipInput: HTMLInputElement;
- static start: number;
- static end: number;
- static mark: Mark;
+ static startUserMarkRegion: number;
+ static endUserMarkRegion: number;
+ static userMark: Mark;
static textBox: FormattedTextBox | undefined;
- static linkDoc: Doc | undefined;
-
- static _deleteRef: Opt<HTMLDivElement | null>;
- static _followRef: Opt<HTMLDivElement | null>;
- static _nextRef: Opt<HTMLDivElement | null>;
-
- static _lastState?: EditorState;
- static _lastView?: EditorView;
-
- @observable static _hrefInd = 0;
- static _hrefs: string[] | undefined = [];
constructor(view: any) {
if (!FormattedTextBoxComment.tooltip) {
- const root = document.getElementById("root");
- FormattedTextBoxComment.tooltipInput = document.createElement("input");
- FormattedTextBoxComment.tooltipInput.type = "checkbox";
- FormattedTextBoxComment.tooltip = document.createElement("div");
- FormattedTextBoxComment.tooltipText = document.createElement("div");
- //FormattedTextBoxComment.tooltipText.style.width = "100%";
- FormattedTextBoxComment.tooltipText.style.height = "100%";
- FormattedTextBoxComment.tooltipText.style.textOverflow = "ellipsis";
- FormattedTextBoxComment.tooltip.appendChild(FormattedTextBoxComment.tooltipText);
- FormattedTextBoxComment.tooltip.className = "FormattedTextBox-tooltip";
- FormattedTextBoxComment.tooltip.style.pointerEvents = "all";
- FormattedTextBoxComment.tooltip.style.maxWidth = "400px";
- FormattedTextBoxComment.tooltip.style.maxHeight = "235px";
- //FormattedTextBoxComment.tooltip.style.width = "100%";
- FormattedTextBoxComment.tooltip.style.height = "100%";
- FormattedTextBoxComment.tooltip.style.overflow = "hidden";
- FormattedTextBoxComment.tooltip.style.display = "none";
- // FormattedTextBoxComment.tooltip.appendChild(FormattedTextBoxComment.tooltipInput);
- FormattedTextBoxComment.tooltip.onpointerdown = async (e: PointerEvent) => {
- const keep = e.target && (e.target as any).type === "checkbox" ? true : false;
- const textBox = FormattedTextBoxComment.textBox;
- const linkDoc = FormattedTextBoxComment.linkDoc;
- if (linkDoc && !keep && textBox) {
- if (linkDoc.author) {
- if (FormattedTextBoxComment._deleteRef?.contains(e.target as any)) {
- this.deleteLink();
- } else if (FormattedTextBoxComment._nextRef?.contains(e.target as any)) {
- FormattedTextBoxComment.showPreview(FormattedTextBoxComment._lastView!, FormattedTextBoxComment._lastState, FormattedTextBoxComment._hrefs?.[(++FormattedTextBoxComment._hrefInd) % FormattedTextBoxComment._hrefs?.length]);
- } else {
- FormattedTextBoxComment.linkDoc = undefined;
- if (linkDoc.type !== DocumentType.LINK) {
- textBox.props.addDocTab(linkDoc, e.ctrlKey ? "add" : "add:right");
- } else {
- const target = LinkManager.getOppositeAnchor(linkDoc, textBox.dataDoc);
- target && LinkManager.FollowLink(linkDoc, textBox.dataDoc, textBox.props, e.altKey);
- }
- }
- }
- } else if (textBox && (FormattedTextBoxComment.tooltipText as any).href) {
- textBox.props.addDocTab(Docs.Create.WebDocument((FormattedTextBoxComment.tooltipText as any).href, { title: (FormattedTextBoxComment.tooltipText as any).href, _width: 200, _height: 400, useCors: true }), "add:right");
- }
- keep && textBox && FormattedTextBoxComment.start !== undefined && textBox.adoptAnnotation(
- FormattedTextBoxComment.start, FormattedTextBoxComment.end, FormattedTextBoxComment.mark);
+ const tooltip = FormattedTextBoxComment.tooltip = document.createElement("div");
+ const tooltipText = FormattedTextBoxComment.tooltipText = document.createElement("div");
+ tooltip.className = "FormattedTextBox-tooltip";
+ tooltipText.className = "FormattedTextBox-tooltipText";
+ tooltip.style.display = "none";
+ tooltip.appendChild(tooltipText);
+ tooltip.onpointerdown = (e: PointerEvent) => {
+ const { textBox, startUserMarkRegion, endUserMarkRegion, userMark } = FormattedTextBoxComment;
+ false && startUserMarkRegion !== undefined && textBox?.adoptAnnotation(startUserMarkRegion, endUserMarkRegion, userMark);
e.stopPropagation();
e.preventDefault();
};
- root?.appendChild(FormattedTextBoxComment.tooltip);
+ document.getElementById("root")?.appendChild(tooltip);
}
}
-
- @undoBatch
- deleteLink = action(() => {
- FormattedTextBoxComment.linkDoc ? LinkManager.Instance.deleteLink(FormattedTextBoxComment.linkDoc) : null;
- LinkDocPreview.LinkInfo = undefined;
- DocumentLinksButton.EditLink = undefined;
- FormattedTextBoxComment.Hide();
- });
-
public static Hide() {
FormattedTextBoxComment.textBox = undefined;
- FormattedTextBoxComment.linkDoc = undefined;
- FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = "none");
- try {
- ReactDOM.unmountComponentAtNode(FormattedTextBoxComment.tooltipText);
- FormattedTextBoxComment.tooltip.removeChild(FormattedTextBoxComment.tooltipText);
- } catch (e) { }
+ FormattedTextBoxComment.tooltip.style.display = "none";
}
- public static SetState(textBox: any, start: number, end: number, mark: Mark) {
+ public static saveMarkRegion(textBox: any, start: number, end: number, mark: Mark) {
FormattedTextBoxComment.textBox = textBox;
- FormattedTextBoxComment.start = start;
- FormattedTextBoxComment.end = end;
- FormattedTextBoxComment.mark = mark;
- FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = "");
+ FormattedTextBoxComment.startUserMarkRegion = start;
+ FormattedTextBoxComment.endUserMarkRegion = end;
+ FormattedTextBoxComment.userMark = mark;
+ FormattedTextBoxComment.tooltip.style.display = "";
}
- static showCommentbox(set: string, view: EditorView, nbef: number) {
+ static showCommentbox(view: EditorView, nbef: number) {
const state = view.state;
- if (set !== "none") {
- // These are in screen coordinates
- // let start = view.coordsAtPos(state.selection.from), end = view.coordsAtPos(state.selection.to);
- const start = view.coordsAtPos(state.selection.from - nbef), end = view.coordsAtPos(state.selection.from - nbef);
- // The box in which the tooltip is positioned, to use as base
- const box = (document.getElementsByClassName("mainView-container") as any)[0].getBoundingClientRect();
- // Find a center-ish x position from the selection endpoints (when
- // crossing lines, end may be more to the left)
- const left = Math.max((start.left + end.left) / 2, start.left + 3);
- FormattedTextBoxComment.tooltip.style.left = (left - box.left) + "px";
- FormattedTextBoxComment.tooltip.style.bottom = (box.bottom - start.top) + "px";
- }
- FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = set);
+ // These are in screen coordinates
+ const start = view.coordsAtPos(state.selection.from - nbef), end = view.coordsAtPos(state.selection.from - nbef);
+ // The box in which the tooltip is positioned, to use as base
+ const box = (document.getElementsByClassName("mainView-container") as any)[0].getBoundingClientRect();
+ // Find a center-ish x position from the selection endpoints (when crossing lines, end may be more to the left)
+ const left = Math.max((start.left + end.left) / 2, start.left + 3);
+ FormattedTextBoxComment.tooltip.style.left = (left - box.left) + "px";
+ FormattedTextBoxComment.tooltip.style.bottom = (box.bottom - start.top) + "px";
+ FormattedTextBoxComment.tooltip.style.display = "";
}
- static update(view: EditorView, lastState?: EditorState, forceUrl: string = "") {
- // Don't do anything if the document/selection didn't change
- if (!forceUrl && lastState?.doc.eq(view.state.doc) && lastState?.selection.eq(view.state.selection)) {
- return;
+ static update(view: EditorView, lastState?: EditorState, hrefs: string = "") {
+ if (FormattedTextBoxComment.textBox && (hrefs || !lastState?.doc.eq(view.state.doc) || !lastState?.selection.eq(view.state.selection))) {
+ FormattedTextBoxComment.setupPreview(view, FormattedTextBoxComment.textBox, hrefs ? hrefs.trim().split(" ") : undefined);
}
- FormattedTextBoxComment._lastState = lastState;
- FormattedTextBoxComment._lastView = view;
- FormattedTextBoxComment._hrefs = forceUrl ? forceUrl.trim().split(" ") : undefined;
- FormattedTextBoxComment._hrefInd = 0;
- FormattedTextBoxComment.linkDoc = undefined;
- FormattedTextBoxComment.showPreview(view, lastState, FormattedTextBoxComment._hrefs?.[FormattedTextBoxComment._hrefInd]);
}
- static showPreview(view: EditorView, lastState?: EditorState, forceUrl: string = "") {
+ static setupPreview(view: EditorView, textBox: FormattedTextBox, hrefs?: string[]) {
const state = view.state;
- const textBox = FormattedTextBoxComment.textBox;
- if (!textBox || !textBox.props) {
- return;
- }
- let set = "none";
- let nbef = 0;
- FormattedTextBoxComment.tooltipInput.style.display = "none";
- FormattedTextBoxComment.tooltip.style.width = "";
- FormattedTextBoxComment.tooltip.style.height = "";
- (FormattedTextBoxComment.tooltipText as any).href = "";
- FormattedTextBoxComment.tooltipText.style.whiteSpace = "";
- FormattedTextBoxComment.tooltipText.style.overflow = "";
// this section checks to see if the insertion point is over text entered by a different user. If so, it sets ths comment text to indicate the user and the modification date
if (state.selection.$from) {
- nbef = findStartOfMark(state.selection.$from, view, findOtherUserMark);
+ const nbef = findStartOfMark(state.selection.$from, view, findOtherUserMark);
const naft = findEndOfMark(state.selection.$from, view, findOtherUserMark);
- const noselection = view.state.selection.$from === view.state.selection.$to;
+ const noselection = state.selection.$from === state.selection.$to;
let child: any = null;
state.doc.nodesBetween(state.selection.from, state.selection.to, (node: any, pos: number, parent: any) => !child && node.marks.length && (child = node));
const mark = child && findOtherUserMark(child.marks);
if (mark && child && (nbef || naft) && (!mark.attrs.opened || noselection)) {
- FormattedTextBoxComment.SetState(FormattedTextBoxComment.textBox, state.selection.$from.pos - nbef, state.selection.$from.pos + naft, mark);
+ FormattedTextBoxComment.saveMarkRegion(textBox, state.selection.$from.pos - nbef, state.selection.$from.pos + naft, mark);
}
if (mark && child && ((nbef && naft) || !noselection)) {
FormattedTextBoxComment.tooltipText.textContent = mark.attrs.userid + " on " + (new Date(mark.attrs.modified * 1000)).toLocaleString();
- set = "";
- FormattedTextBoxComment.tooltipInput.style.display = "";
- }
+ FormattedTextBoxComment.showCommentbox(view, nbef);
+ } else FormattedTextBoxComment.Hide();
}
+
// this checks if the selection is a hyperlink. If so, it displays the target doc's text for internal links, and the url of the target for external links.
- if (set === "none" && state.selection.$from) {
- nbef = findStartOfMark(state.selection.$from, view, findLinkMark);
+ if (state.selection.$from && hrefs) {
+ const nbef = findStartOfMark(state.selection.$from, view, findLinkMark);
const naft = findEndOfMark(state.selection.$from, view, findLinkMark) || nbef;
- let child: any = null;
- state.doc.nodesBetween(state.selection.from, state.selection.to, (node: any, pos: number, parent: any) => !child && node.marks.length && (child = node));
- child = child || (nbef && state.selection.$from.nodeBefore);
- const mark = child ? findLinkMark(child.marks) : undefined;
- const href = forceUrl || (!mark?.attrs.docref || naft === nbef) && mark?.attrs.allLinks.find((item: { href: string }) => item.href)?.href;
- if (forceUrl || (href && child && nbef && naft && mark?.attrs.showPreview)) {
- try {
- ReactDOM.unmountComponentAtNode(FormattedTextBoxComment.tooltipText);
- FormattedTextBoxComment.tooltip.removeChild(FormattedTextBoxComment.tooltipText);
- } catch (e) { }
- FormattedTextBoxComment.tooltipText = document.createElement("div");
- FormattedTextBoxComment.tooltipText.className = "FormattedTextBoxComment-toolTipText";
- FormattedTextBoxComment.tooltipText.style.width = "100%";
- FormattedTextBoxComment.tooltipText.style.height = "100%";
- FormattedTextBoxComment.tooltipText.style.textOverflow = "ellipsis";
- FormattedTextBoxComment.tooltipText.style.cursor = "pointer";
- FormattedTextBoxComment.tooltipText.textContent = "URL: " + href;
- (FormattedTextBoxComment.tooltipText as any).href = href;
- FormattedTextBoxComment.tooltip.appendChild(FormattedTextBoxComment.tooltipText);
-
- if (href.startsWith("https://en.wikipedia.org/wiki/")) {
- wiki().page(href.replace("https://en.wikipedia.org/wiki/", "")).then(page => page.summary().then(summary => FormattedTextBoxComment.tooltipText.textContent = summary.substring(0, 500)));
- } else {
- FormattedTextBoxComment.tooltipText.style.whiteSpace = "pre";
- FormattedTextBoxComment.tooltipText.style.overflow = "hidden";
- }
- if (href.indexOf(Utils.prepend("/doc/")) === 0) {
- const docTarget = href.replace(Utils.prepend("/doc/"), "").split("?")[0];
- FormattedTextBoxComment.tooltipText.textContent = "target not found...";
- (FormattedTextBoxComment.tooltipText as any).href = "";
- docTarget && DocServer.GetRefField(docTarget).then(async linkDoc => {
- if (linkDoc instanceof Doc) {
- (FormattedTextBoxComment.tooltipText as any).href = href;
- FormattedTextBoxComment.linkDoc = linkDoc;
- const anchor = FieldValue(Doc.AreProtosEqual(FieldValue(Cast(linkDoc.anchor1, Doc)), textBox.dataDoc) ? Cast(linkDoc.anchor2, Doc) : (Cast(linkDoc.anchor1, Doc)) || linkDoc);
- const target = anchor?.annotationOn ? await DocCastAsync(anchor.annotationOn) : anchor;
- if (anchor !== target && anchor && target) {
- target._scrollPreviewY = NumCast(anchor?.y);
- }
- if (target?.author) {
- FormattedTextBoxComment.showCommentbox("", view, nbef);
-
- const title = StrCast(target.title).length > 16 ? StrCast(target.title).substr(0, 16) + "..." : target.title;
-
- const docPreview = <div className="FormattedTextBoxComment">
- <div className="FormattedTextBoxComment-info">
- <div className="FormattedTextBoxComment-title">
- {title}
- {FormattedTextBoxComment.linkDoc.description === "" ? (null) :
- <p className="FormattedTextBoxComment-description"> {StrCast(FormattedTextBoxComment.linkDoc.description)}</p>}
- </div>
- <div className="wrapper" style={{ float: "right" }}>
- {(FormattedTextBoxComment._hrefs?.length || 0) <= 1 ? (null) : <Tooltip title={<><div className="dash-tooltip">Next Link</div></>} placement="top">
- <div className="FormattedTextBoxComment-button" ref={(r) => this._nextRef = r}>
- <FontAwesomeIcon className="FormattedTextBoxComment-fa-icon" icon="chevron-right" color="white" size="sm" />
- </div>
- </Tooltip>}
-
- <Tooltip title={<><div className="dash-tooltip">Delete Link</div></>} placement="top">
- <div className="FormattedTextBoxComment-button" ref={(r) => this._deleteRef = r}>
- <FontAwesomeIcon className="FormattedTextBoxComment-fa-icon" icon="trash" color="white" size="sm" />
- </div>
- </Tooltip>
-
- <Tooltip title={<><div className="dash-tooltip">Follow Link</div></>} placement="top">
- <div className="FormattedTextBoxComment-button" ref={(r) => this._followRef = r}>
- <FontAwesomeIcon className="FormattedTextBoxComment-fa-icon" icon="arrow-right" color="white" size="sm" />
- </div>
- </Tooltip>
- </div>
- </div>
- <div className="FormattedTextBoxComment-preview-wrapper">
- <DocumentView
- Document={target}
- moveDocument={returnFalse}
- rootSelected={returnFalse}
- ScreenToLocalTransform={Transform.Identity}
- parentActive={returnFalse}
- addDocument={returnFalse}
- removeDocument={returnFalse}
- addDocTab={returnFalse}
- pinToPres={returnFalse}
- dontRegisterView={true}
- docFilters={returnEmptyFilter}
- docRangeFilters={returnEmptyFilter}
- searchFilterDocs={returnEmptyDoclist}
- ContainingCollectionDoc={undefined}
- ContainingCollectionView={undefined}
- renderDepth={-1}
- PanelWidth={() => 175} //Math.min(350, NumCast(target._width, 350))}
- PanelHeight={() => 175} //Math.min(250, NumCast(target._height, 250))}
- focus={emptyFunction}
- whenActiveChanged={returnFalse}
- bringToFront={returnFalse}
- NativeWidth={Doc.NativeWidth(target) ? (() => Doc.NativeWidth(target)) : undefined}
- NativeHeight={Doc.NativeHeight(target) ? (() => Doc.NativeHeight(target)) : undefined}
- />
- </div>
- </div>;
-
- FormattedTextBoxComment.showCommentbox("", view, nbef);
-
- ReactDOM.render(docPreview, FormattedTextBoxComment.tooltipText);
-
- //FormattedTextBoxComment.tooltip.style.width = "100%";
- FormattedTextBoxComment.tooltip.style.height = "100%";
- }
- }
- });
- }
- set = "";
- }
+ nbef && naft && LinkDocPreview.SetLinkInfo({
+ docProps: textBox.props,
+ linkSrc: textBox.rootDoc,
+ location: ((pos) => [pos.left, pos.top + 25])(view.coordsAtPos(state.selection.from - nbef)),
+ hrefs,
+ showHeader: true
+ });
}
- FormattedTextBoxComment.showCommentbox(set, view, nbef);
}
destroy() { }
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index dc630af74..5da868281 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -806,7 +806,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
<p>Linked to:</p>
<input value={link} ref={this._linkToRef} placeholder="Enter URL" onChange={onLinkChange} />
<button className="make-button" onPointerDown={e => this.makeLinkToURL(link, "add:right")}>Apply hyperlink</button>
- <div className="divider"></div>
+ <div className="divider" />
<button className="remove-button" onPointerDown={e => this.deleteLink()}>Remove link</button>
</div>;
@@ -819,7 +819,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
const node = this.view.state.selection.$from.nodeAfter;
const link = node && node.marks.find(m => m.type.name === "link");
if (link) {
- const href = link.attrs.allLinks.length > 0 ? link.attrs.allLinks[0].href : undefined;
+ const href = link.attrs.allAnchors.length > 0 ? link.attrs.allAnchors[0].href : undefined;
if (href) {
if (href.indexOf(Utils.prepend("/doc/")) === 0) {
const linkclicked = href.replace(Utils.prepend("/doc/"), "").split("?")[0];
@@ -851,21 +851,21 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
// TODO: should check for valid URL
@undoBatch
makeLinkToURL = (target: string, lcoation: string) => {
- ((this.view as any)?.TextView as FormattedTextBox).makeLinkToSelection("", target, "onRadd:rightight", "", target);
+ ((this.view as any)?.TextView as FormattedTextBox).makeLinkAnchor(undefined, "onRadd:rightight", target, target);
}
@undoBatch
@action
deleteLink = () => {
if (this.view) {
- const link = this.view.state.selection.$from.nodeAfter?.marks.find(m => m.type === this.view!.state.schema.marks.linkAnchor);
- if (link) {
- const allLinks = link.attrs.allLinks.slice();
- this.TextView.RemoveLinkFromSelection(link.attrs.allLinks);
+ const linkAnchor = this.view.state.selection.$from.nodeAfter?.marks.find(m => m.type === this.view!.state.schema.marks.linkAnchor);
+ if (linkAnchor) {
+ const allAnchors = linkAnchor.attrs.allAnchors.slice();
+ this.TextView.RemoveAnchorFromSelection(allAnchors);
// bcz: Argh ... this will remove the link from the document even it's anchored somewhere else in the text which happens if only part of the anchor text was selected.
- allLinks.filter((aref: any) => aref?.href.indexOf(Utils.prepend("/doc/")) === 0).forEach((aref: any) => {
- const linkId = aref.href.replace(Utils.prepend("/doc/"), "").split("?")[0];
- linkId && DocServer.GetRefField(linkId).then(linkDoc => LinkManager.Instance.deleteLink(linkDoc as Doc));
+ allAnchors.filter((aref: any) => aref?.href.indexOf(Utils.prepend("/doc/")) === 0).forEach((aref: any) => {
+ const anchorId = aref.href.replace(Utils.prepend("/doc/"), "").split("?")[0];
+ anchorId && DocServer.GetRefField(anchorId).then(linkDoc => LinkManager.Instance.deleteLink(linkDoc as Doc));
});
}
}
@@ -877,8 +877,8 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
let startIndex = $start.index();
let endIndex = $start.indexAfter();
- while (startIndex > 0 && $start.parent.child(startIndex - 1).marks.filter(m => m.type === mark && m.attrs.allLinks.find((item: { href: string }) => item.href === href)).length) startIndex--;
- while (endIndex < $start.parent.childCount && $start.parent.child(endIndex).marks.filter(m => m.type === mark && m.attrs.allLinks.find((item: { href: string }) => item.href === href)).length) endIndex++;
+ while (startIndex > 0 && $start.parent.child(startIndex - 1).marks.filter(m => m.type === mark && m.attrs.allAnchors.find((item: { href: string }) => item.href === href)).length) startIndex--;
+ while (endIndex < $start.parent.childCount && $start.parent.child(endIndex).marks.filter(m => m.type === mark && m.attrs.allAnchors.find((item: { href: string }) => item.href === href)).length) endIndex++;
let startPos = $start.start();
let endPos = startPos;
diff --git a/src/client/views/nodes/formattedText/RichTextSchema.tsx b/src/client/views/nodes/formattedText/RichTextSchema.tsx
index abbb89780..2252de3d6 100644
--- a/src/client/views/nodes/formattedText/RichTextSchema.tsx
+++ b/src/client/views/nodes/formattedText/RichTextSchema.tsx
@@ -138,6 +138,7 @@ export class DashDocView {
removeDocument={removeDoc}
layerProvider={this._textBox.props.layerProvider}
styleProvider={this._textBox.props.styleProvider}
+ docViewPath={this._textBox.props.docViewPath}
ScreenToLocalTransform={this.getDocTransform}
addDocTab={this._textBox.props.addDocTab}
pinToPres={returnFalse}
diff --git a/src/client/views/nodes/formattedText/TooltipTextMenu.scss b/src/client/views/nodes/formattedText/TooltipTextMenu.scss
index e2149e9c1..0e4b752ac 100644
--- a/src/client/views/nodes/formattedText/TooltipTextMenu.scss
+++ b/src/client/views/nodes/formattedText/TooltipTextMenu.scss
@@ -103,7 +103,7 @@
}
.tooltipMenu, .basic-tools {
- z-index: 20000;
+ z-index: 3000;
pointer-events: all;
padding: 3px;
padding-bottom: 5px;
diff --git a/src/client/views/nodes/formattedText/marks_rts.ts b/src/client/views/nodes/formattedText/marks_rts.ts
index ea239e4d3..655ee7e44 100644
--- a/src/client/views/nodes/formattedText/marks_rts.ts
+++ b/src/client/views/nodes/formattedText/marks_rts.ts
@@ -22,8 +22,7 @@ export const marks: { [index: string]: MarkSpec } = {
// element.
linkAnchor: {
attrs: {
- allLinks: { default: [] as { href: string, title: string, linkId: string, targetId: string }[] },
- showPreview: { default: true },
+ allAnchors: { default: [] as { href: string, title: string, anchorId: string }[] },
location: { default: null },
title: { default: null },
docref: { default: false } // flags whether the linked text comes from a document within Dash. If so, an attribution label is appended after the text
@@ -31,17 +30,23 @@ export const marks: { [index: string]: MarkSpec } = {
inclusive: false,
parseDOM: [{
tag: "a[href]", getAttrs(dom: any) {
- return { allLinks: [{ href: dom.getAttribute("href"), title: dom.getAttribute("title"), linkId: dom.getAttribute("linkids"), targetId: dom.dataset.targetids }], location: dom.getAttribute("location"), };
+ return {
+ location: dom.getAttribute("location"),
+ title: dom.getAttribute("title")
+ };
}
}],
toDOM(node: any) {
- const targetids = node.attrs.allLinks.reduce((p: string, item: { href: string, title: string, targetId: string, linkId: string }) => p + " " + item.targetId, "");
- const targethrefs = node.attrs.allLinks.reduce((p: string, item: { href: string, title: string, targetId: string, linkId: string }) => p + " " + item.href, "");
- const linkids = node.attrs.allLinks.reduce((p: string, item: { href: string, title: string, targetId: string, linkId: string }) => p + " " + item.linkId, "");
+ const targethrefs = node.attrs.allAnchors.reduce((p: string, item: { href: string, title: string, anchorId: string }) => p ? p + " " + item.href : item.href, "");
+ const anchorids = node.attrs.allAnchors.reduce((p: string, item: { href: string, title: string, anchorId: string }) => p ? p + " " + item.anchorId : item.anchorId, "");
return node.attrs.docref && node.attrs.title ?
- ["div", ["span", `"`], ["span", 0], ["span", `"`], ["br"], ["a", { ...node.attrs, href: node.attrs.allLinks[0].href, class: "prosemirror-attribution" }, node.attrs.title], ["br"]] :
+ ["div", ["span", `"`], ["span", 0], ["span", `"`], ["br"], ["a", {
+ ...node.attrs,
+ class: "prosemirror-attribution",
+ href: node.attrs.allAnchors[0].href,
+ }, node.attrs.title], ["br"]] :
//node.attrs.allLinks.length === 1 ?
- ["a", { ...node.attrs, class: linkids, "data-targetids": targetids, "data-targethrefs": targethrefs, title: `${node.attrs.title}`, href: node.attrs.allLinks[0]?.href, style: `text-decoration: ${linkids === " " ? "underline" : undefined}` }, 0];
+ ["a", { class: anchorids, "data-targethrefs": targethrefs, title: node.attrs.title, location: node.attrs.location, style: `text-decoration: underline` }, 0];
// ["div", { class: "prosemirror-anchor" },
// ["span", { class: "prosemirror-linkBtn" },
// ["a", { ...node.attrs, class: linkids, "data-targetids": targetids, title: `${node.attrs.title}` }, 0],
diff --git a/src/client/views/nodes/formattedText/nodes_rts.ts b/src/client/views/nodes/formattedText/nodes_rts.ts
index 722c0a836..5d9c8b56d 100644
--- a/src/client/views/nodes/formattedText/nodes_rts.ts
+++ b/src/client/views/nodes/formattedText/nodes_rts.ts
@@ -22,6 +22,8 @@ export const nodes: { [index: string]: NodeSpec } = {
content: "block+"
},
+ paragraph: ParagraphNodeSpec,
+
audiotag: {
group: "block",
attrs: {
@@ -64,8 +66,6 @@ export const nodes: { [index: string]: NodeSpec } = {
parseDOM: [{ tag: "footnote" }]
},
- paragraph: ParagraphNodeSpec,
-
// :: NodeSpec A blockquote (`<blockquote>`) wrapping one or more blocks.
blockquote: {
content: "block*",
diff --git a/src/client/views/nodes/formattedText/prosemirrorPatches.js b/src/client/views/nodes/formattedText/prosemirrorPatches.js
index 746c93868..5bc323a4d 100644
--- a/src/client/views/nodes/formattedText/prosemirrorPatches.js
+++ b/src/client/views/nodes/formattedText/prosemirrorPatches.js
@@ -143,17 +143,21 @@ function wrappingInputRule(regexp, nodeType, getAttrs, joinPredicate, customWith
// :: ([Mark]) → ?Mark
// Tests whether there is a mark of this type in the given set.
function isInSetWithAttrs(mark, set, attrs) {
+ var markAllAnchors = attrs.allAnchors !== undefined ? Array.from(attrs.allAnchors.map(al => al.anchorId)) : [];
for (var i = 0; i < set.length; i++) {
if (set[i].type == mark) {
- if (Array.from(Object.keys(attrs)).reduce((p, akey) => {
+ return Array.from(Object.keys(attrs)).reduce((p, akey) => {
if (p && JSON.stringify(set[i].attrs[akey]) === JSON.stringify(attrs[akey])) return true;
- set[i].attrs.allLinks = Array.from(set[i].attrs.allLinks).filter(a => !Array.from(attrs.allLinks.map(al => al.targetId)).includes(a.targetId) || !Array.from(attrs.allLinks.map(al => al.linkId).includes(a.linkId)))
+ // bcz: hack to allow special case of anchors in Dash to be removed from a mark which has multiple
+ if (set[i].attrs.allAnchors !== undefined) {
+ set[i].attrs.allAnchors = set[i].attrs.allAnchors.filter(a => !markAllAnchors.includes(a.anchorId));
+ if (set[i].attrs.allAnchors.length) return undefined;
+ }
return false;
- }, true)) {
- return set[i];
- }
+ }, true);
}
}
+ return undefined;
};
// :: (number, number, ?union<Mark, MarkType>) → this
@@ -170,7 +174,9 @@ function removeMarkWithAttrs(tr, from, to, mark, attrs) {
step++;
var toRemove = null;
if (mark) {
- if (isInSetWithAttrs(mark, node.marks, attrs)) { toRemove = [mark]; }
+ const inset = isInSetWithAttrs(mark, node.marks, attrs);
+ if (inset === true) { toRemove = [mark]; }
+ // if (inset === undefined) { console.log("lightened") } // anchorids were removed from the mark, but the mark wasn't removed since there are still anchorsids left
} else {
toRemove = node.marks;
}
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index fa97bde3f..dd9dfa733 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -23,13 +23,12 @@ import { CollectionFreeFormView } from "../collections/collectionFreeForm/Collec
import { ViewBoxAnnotatableComponent } from "../DocComponent";
import { MarqueeAnnotator } from "../MarqueeAnnotator";
import { FieldViewProps } from "../nodes/FieldView";
-import { FormattedTextBoxComment } from "../nodes/formattedText/FormattedTextBoxComment";
-import { LinkDocPreview } from "../nodes/LinkDocPreview";
import { Annotation } from "./Annotation";
import { AnchorMenu } from "./AnchorMenu";
import "./PDFViewer.scss";
const pdfjs = require('pdfjs-dist/es5/build/pdf.js');
import React = require("react");
+import { DocAfterFocusFunc } from "../nodes/DocumentView";
const PDFJSViewer = require("pdfjs-dist/web/pdf_viewer");
const pdfjsLib = require("pdfjs-dist");
const _global = (window /* browser */ || global /* node */) as any;
@@ -88,6 +87,8 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
private _coverPath: any;
private _lastSearch = false;
private _viewerIsSetup = false;
+ private _ignoreScroll = false;
+ private _smoothScrolling = true;
@computed get allAnnotations() {
return DocUtils.FilterDocs(DocListCast(this.dataDoc[this.props.fieldKey + "-annotations"]), this.props.docFilters(), this.props.docRangeFilters(), undefined);
@@ -119,12 +120,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
}
runInAction(() => this._showWaiting = true);
this.props.startupLive && this.setupPdfJsViewer();
- if (this._mainCont.current) {
- this._mainCont.current.scrollTop = this.layoutDoc._scrollTop || 0;
- const observer = new _global.ResizeObserver(action((entries: any) => this._mainCont.current && (this._mainCont.current.scrollTop = this.layoutDoc._scrollTop || 0)));
- observer.observe(this._mainCont.current);
- this._mainCont.current.addEventListener("scroll", (e) => (e.target as any).scrollLeft = 0);
- }
+ this._mainCont.current?.addEventListener("scroll", e => (e.target as any).scrollLeft = 0);
this._disposers.searchMatch = reaction(() => Doc.IsSearchMatch(this.rootDoc),
m => {
@@ -141,36 +137,6 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
(SelectionManager.Views().length === 1) && this.setupPdfJsViewer();
},
{ fireImmediately: true });
- this._disposers.scrollY = reaction(
- () => this.Document._scrollY,
- (scrollY) => {
- if (scrollY !== undefined) {
- (this._showCover || this._showWaiting) && this.setupPdfJsViewer();
- if (this.props.renderDepth !== -1 && !LinkDocPreview.TargetDoc && !FormattedTextBoxComment.linkDoc) {
- const delay = this._mainCont.current ? 0 : 250; // wait for mainCont and try again to scroll
- const durationStr = StrCast(this.Document._viewTransition).match(/([0-9]*)ms/);
- const duration = durationStr ? Number(durationStr[1]) : 1000;
- setTimeout(() => this._mainCont.current && smoothScroll(duration, this._mainCont.current, Math.abs(scrollY || 0)), delay);
- setTimeout(() => { this.Document._scrollTop = scrollY; this.Document._scrollY = undefined; }, duration + delay);
- }
- }
- },
- { fireImmediately: true }
- );
- this._disposers.scrollPreviewY = reaction(
- () => Cast(this.Document._scrollPreviewY, "number", null),
- (scrollY) => {
- if (scrollY !== undefined) {
- (this._showCover || this._showWaiting) && this.setupPdfJsViewer();
- if (this.props.renderDepth === -1 && scrollY >= 0) {
- if (!this._mainCont.current) setTimeout(() => this._mainCont.current && smoothScroll(1000, this._mainCont.current, scrollY || 0));
- else smoothScroll(1000, this._mainCont.current, scrollY || 0);
- this.Document._scrollPreviewY = undefined;
- }
- }
- },
- { fireImmediately: true }
- );
this._disposers.curPage = reaction(
() => this.Document._curPage,
(page) => page !== undefined && page !== this._pdfViewer?.currentPageNumber && this.gotoPage(page),
@@ -212,6 +178,21 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
}
}
+ // scrolls to focus on a nested annotation document. if this is part a link preview then it will jump to the scroll location,
+ // otherwise it will scroll smoothly.
+ scrollFocus = (doc: Doc, smooth: boolean, afterFocus?: DocAfterFocusFunc) => {
+ const mainCont = this._mainCont.current;
+ if (doc !== this.rootDoc && mainCont) {
+ const scrollTo = Utils.scrollIntoView(NumCast(doc.y), doc[HeightSym](), NumCast(this.layoutDoc._scrollTop), this.props.PanelHeight() / (this.props.scaling?.() || 1));
+ if (scrollTo !== undefined) {
+ if (smooth) smoothScroll(500, mainCont, scrollTo);
+ else mainCont.scrollTop = scrollTo;
+ return afterFocus?.(true);
+ }
+ }
+ afterFocus?.(false);
+ }
+
@action
setupPdfJsViewer = async () => {
if (this._viewerIsSetup) return;
@@ -220,14 +201,6 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
this.props.setPdfViewer(this);
await this.initialLoad();
- this._disposers.scrollTop = reaction(() => Cast(this.layoutDoc._scrollTop, "number", null),
- (stop) => {
- if (stop !== undefined && this.layoutDoc._scrollY === undefined && this._mainCont.current) {
- (this._mainCont.current.scrollTop = stop);
- }
- },
- { fireImmediately: true });
-
this._disposers.filterScript = reaction(
() => Cast(this.Document.filterScript, ScriptField),
action(scriptField => {
@@ -242,13 +215,40 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
this.createPdfViewer();
}
- pagesinit = action(() => {
+ pagesinit = () => {
if (this._pdfViewer._setDocumentViewerElement.offsetParent) {
- this._pdfViewer.currentScaleValue = this._zoomed = 1;
+ runInAction(() => this._pdfViewer.currentScaleValue = this._zoomed = 1);
this.gotoPage(this.Document._curPage || 1);
}
document.removeEventListener("pagesinit", this.pagesinit);
- });
+ var quickScroll: string | undefined = "";
+ this._disposers.scroll = reaction(
+ () => NumCast(this.Document._scrollTop),
+ (pos) => {
+ if (!this._ignoreScroll) {
+ (this._showCover || this._showWaiting) && this.setupPdfJsViewer();
+ const viewTrans = quickScroll ?? StrCast(this.Document._viewTransition);
+ const delay = this._mainCont.current ? 0 : 250; // wait for mainCont and try again to scroll
+ const durationMiliStr = viewTrans.match(/([0-9]*)ms/);
+ const durationSecStr = viewTrans.match(/([0-9.]*)s/);
+ const duration = durationMiliStr ? Number(durationMiliStr[1]) : durationSecStr ? Number(durationSecStr[1]) * 1000 : 0;
+ if (duration) {
+ this._smoothScrolling = true;
+ setTimeout(() => {
+ this._mainCont.current && smoothScroll(duration, this._mainCont.current, Math.abs(pos || 0));
+ setTimeout(() => this._smoothScrolling = false, duration);
+ }, delay);
+ } else {
+ this._smoothScrolling = true;
+ this._mainCont.current?.scrollTo({ top: Math.abs(pos || 0) });
+ this._smoothScrolling = false;
+ }
+ }
+ },
+ { fireImmediately: true }
+ );
+ quickScroll = undefined;
+ }
createPdfViewer() {
if (!this._mainCont.current) { // bcz: I don't think this is ever triggered or needed
@@ -302,29 +302,18 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
}
@action
- scrollToFrame = (duration: number, top: number) => {
- this._mainCont.current && smoothScroll(duration, this._mainCont.current, top);
- }
-
- @action
scrollToAnnotation = (scrollToAnnotation: Doc) => {
if (scrollToAnnotation) {
- const offset = (this.props.PanelHeight() / this.contentScaling) / 2;
- this._mainCont.current && smoothScroll(500, this._mainCont.current, NumCast(scrollToAnnotation.y) - offset);
+ this.scrollFocus(scrollToAnnotation, true);
Doc.linkFollowHighlight(scrollToAnnotation);
}
}
- pageDelay: any;
- @action
onScroll = (e: React.UIEvent<HTMLElement>) => {
- if (!LinkDocPreview.TargetDoc && !FormattedTextBoxComment.linkDoc) {
- this.pageDelay && clearTimeout(this.pageDelay);
- this.pageDelay = setTimeout(() => {
- this.Document._scrollY === undefined && this._mainCont.current && (this.layoutDoc._scrollTop = this._mainCont.current.scrollTop);
- this.pageDelay = undefined;
- //this._pdfViewer && (this.Document._curPage = this._pdfViewer.currentPageNumber);
- }, 1000);
+ if (this._mainCont.current && !this._smoothScrolling) {
+ this._ignoreScroll = true;
+ this.layoutDoc._scrollTop = this._mainCont.current.scrollTop;
+ this._ignoreScroll = false;
}
}
@@ -518,8 +507,8 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
showInfo = action((anno: Opt<Doc>) => this._overlayAnnoInfo = anno);
overlayTransform = () => this.scrollXf().scale(1 / this._zoomed);
- panelWidth = () => (this.Document.scrollHeight || Doc.NativeHeight(this.Document) || 0);
- panelHeight = () => this._pageSizes.length && this._pageSizes[0] ? this._pageSizes[0].width : Doc.NativeWidth(this.Document);
+ panelWidth = () => this.props.PanelWidth() / (this.props.scaling?.() || 1); // (this.Document.scrollHeight || Doc.NativeHeight(this.Document) || 0);
+ panelHeight = () => this.props.PanelHeight() / (this.props.scaling?.() || 1); // () => this._pageSizes.length && this._pageSizes[0] ? this._pageSizes[0].width : Doc.NativeWidth(this.Document);
@computed get overlayLayer() {
return <div className={`pdfViewerDash-overlay${Doc.GetSelectedTool() !== InkTool.None || SnappingManager.GetIsDragging() ? "-inking" : ""}`}
style={{
@@ -531,8 +520,8 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
isAnnotationOverlay={true}
fieldKey={this.annotationKey}
setPreviewCursor={this.setPreviewCursor}
- PanelHeight={this.panelWidth}
- PanelWidth={this.panelHeight}
+ PanelHeight={this.panelHeight}
+ PanelWidth={this.panelWidth}
dropAction={"alias"}
select={emptyFunction}
active={this.annotationsActive}
@@ -576,7 +565,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
{this.overlayInfo}
{this.standinViews}
{!this._marqueeing || !this._mainCont.current || !this._annotationLayer.current ? (null) :
- <MarqueeAnnotator rootDoc={this.rootDoc} down={this._marqueeing} addDocument={this.addDocument} finishMarquee={this.finishMarquee} getPageFromScroll={this.getPageFromScroll} savedAnnotations={this._savedAnnotations} annotationLayer={this._annotationLayer.current} mainCont={this._mainCont.current} />}
+ <MarqueeAnnotator rootDoc={this.rootDoc} scrollTop={0} down={this._marqueeing} addDocument={this.addDocument} finishMarquee={this.finishMarquee} getPageFromScroll={this.getPageFromScroll} savedAnnotations={this._savedAnnotations} annotationLayer={this._annotationLayer.current} mainCont={this._mainCont.current} />}
</div >;
}
} \ No newline at end of file
diff --git a/src/client/views/presentationview/PresElementBox.tsx b/src/client/views/presentationview/PresElementBox.tsx
index c07e137c4..01a55660a 100644
--- a/src/client/views/presentationview/PresElementBox.tsx
+++ b/src/client/views/presentationview/PresElementBox.tsx
@@ -7,7 +7,7 @@ import { documentSchema } from '../../../fields/documentSchemas';
import { Id } from "../../../fields/FieldSymbols";
import { createSchema, makeInterface } from '../../../fields/Schema';
import { Cast, NumCast, StrCast } from "../../../fields/Types";
-import { emptyFunction, returnFalse, returnTrue, setupMoveUpEvents } from "../../../Utils";
+import { emptyFunction, returnFalse, returnTrue, setupMoveUpEvents, emptyPath, returnEmptyDoclist } from "../../../Utils";
import { DocumentType } from "../../documents/DocumentTypes";
import { CurrentUserUtils } from "../../util/CurrentUserUtils";
import { DocumentManager } from "../../util/DocumentManager";
@@ -92,6 +92,8 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc
Document={this.targetDoc}
DataDoc={this.targetDoc[DataSym] !== this.targetDoc && this.targetDoc[DataSym]}
styleProvider={this.styleProvider}
+ layerProvider={this.props.layerProvider}
+ docViewPath={returnEmptyDoclist}
rootSelected={returnTrue}
addDocument={returnFalse}
removeDocument={returnFalse}
diff --git a/src/client/views/search/SearchBox.scss b/src/client/views/search/SearchBox.scss
index 76f9b66eb..4f5b7e41a 100644
--- a/src/client/views/search/SearchBox.scss
+++ b/src/client/views/search/SearchBox.scss
@@ -13,7 +13,7 @@
overflow-x: visible;
background: lightgrey;
overflow: visible;
- z-index: 10000;
+ z-index: 1000;
.searchBox-bar {
height: $searchpanel-height;
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 493dd2ba4..7f6ab57e7 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -958,9 +958,10 @@ export namespace Doc {
return doc;
}
-
- export function LinkOtherAnchor(linkDoc: Doc, anchorDoc: Doc) { return Doc.AreProtosEqual(anchorDoc, Cast(linkDoc.anchor1, Doc) as Doc) ? Cast(linkDoc.anchor2, Doc) as Doc : Cast(linkDoc.anchor1, Doc) as Doc; }
- export function LinkEndpoint(linkDoc: Doc, anchorDoc: Doc) { return Doc.AreProtosEqual(anchorDoc, Cast(linkDoc.anchor1, Doc) as Doc) ? "1" : "2"; }
+ export function LinkEndpoint(linkDoc: Doc, anchorDoc: Doc) {
+ return Doc.AreProtosEqual(anchorDoc, (linkDoc.anchor1 as Doc).annotationOn as Doc) ||
+ Doc.AreProtosEqual(anchorDoc, linkDoc.anchor1 as Doc) ? "1" : "2";
+ }
export function linkFollowUnhighlight() {
Doc.UnhighlightAll();
diff --git a/src/fields/documentSchemas.ts b/src/fields/documentSchemas.ts
index b10c2b015..056243953 100644
--- a/src/fields/documentSchemas.ts
+++ b/src/fields/documentSchemas.ts
@@ -26,10 +26,7 @@ export const documentSchema = createSchema({
y: "number", // y coordinate when in a freeform view
z: "number", // z "coordinate" - non-zero specifies the overlay layer of a freeformview
zIndex: "number", // zIndex of a document in a freeform view
- _scrollY: "number", // "command" to scroll a document to a position on load (the value will be reset to 0 after that )
- _scrollX: "number", // "command" to scroll a document to a position on load (the value will be reset to 0 after that )
- _scrollTop: "number", // scroll position of a scrollable document (pdf, text, web)
- _scrollLeft: "number", // scroll position of a scrollable document (pdf, text, web)
+ _scrollTop: "number", // scroll position of a scrollable document (pdf, text, web)
// appearance properties on the layout
"_backgroundGrid-spacing": "number", // the size of the grid for collection views
@@ -117,7 +114,7 @@ export const collectionSchema = createSchema({
childLayoutTemplate: Doc, // layout template to use to render children of a collecion
childLayoutString: "string", //layout string to use to render children of a collection
childClickedOpenTemplateView: Doc, // layout template to apply to a child when its clicked on in a collection and opened (requires onChildClick or other script to read this value and apply template)
- dontRegisterChildViews: "boolean", // whether views made of this document are registered so that they can be found when drawing links scrollToLinkID: "string", // id of link being traversed. allows this doc to scroll/highlight/etc its link anchor. scrollToLinkID should be set to undefined by this doc after it sets up its scroll,etc.
+ dontRegisterChildViews: "boolean", // whether views made of this document are registered so that they can be found when drawing links
onChildClick: ScriptField, // script to run for each child when its clicked
onChildDoubleClick: ScriptField, // script to run for each child when its clicked
onCheckedClick: ScriptField, // script to run when a checkbox is clicked next to a child in a tree view
diff --git a/src/fields/util.ts b/src/fields/util.ts
index ecb3fb343..b9c5a13c1 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -1,5 +1,5 @@
import { UndoManager } from "../client/util/UndoManager";
-import { Doc, FieldResult, UpdatingFromServer, LayoutSym, AclPrivate, AclEdit, AclReadonly, AclAddonly, AclSym, DataSym, DocListCast, AclAdmin, HeightSym, WidthSym, updateCachedAcls, AclUnset, DocListCastAsync } from "./Doc";
+import { Doc, FieldResult, UpdatingFromServer, LayoutSym, AclPrivate, AclEdit, AclReadonly, AclAddonly, AclSym, DataSym, DocListCast, AclAdmin, HeightSym, WidthSym, updateCachedAcls, AclUnset, DocListCastAsync, ForceServerWrite } from "./Doc";
import { SerializationHelper } from "../client/util/SerializationHelper";
import { ProxyField, PrefetchProxy } from "./Proxy";
import { RefField } from "./RefField";
@@ -96,7 +96,7 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number
} else {
DocServer.registerDocWithCachedUpdate(receiver, prop as string, curValue);
}
- !receiver[UpdatingFromServer] && UndoManager.AddEvent({
+ (!receiver[UpdatingFromServer] || receiver[ForceServerWrite]) && UndoManager.AddEvent({
redo: () => receiver[prop] = value,
undo: () => receiver[prop] = curValue
});
diff --git a/src/mobile/AudioUpload.tsx b/src/mobile/AudioUpload.tsx
index d32e19ee1..cbae71270 100644
--- a/src/mobile/AudioUpload.tsx
+++ b/src/mobile/AudioUpload.tsx
@@ -12,7 +12,7 @@ import { Doc } from '../fields/Doc';
import { listSpec } from '../fields/Schema';
import { Cast, FieldValue } from '../fields/Types';
import { nullAudio } from '../fields/URLField';
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from '../Utils';
+import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, emptyPath } from '../Utils';
import "./ImageUpload.scss";
import { MobileInterface } from './MobileInterface';
import React = require('react');
@@ -94,7 +94,9 @@ export class AudioUpload extends React.Component {
PanelHeight={() => 400}
renderDepth={0}
focus={emptyFunction}
+ layerProvider={undefined}
styleProvider={() => "rgba(0,0,0,0)"}
+ docViewPath={returnEmptyDoclist}
parentActive={returnTrue}
whenActiveChanged={emptyFunction}
bringToFront={emptyFunction}
diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx
index c7e2d02cb..058c61a6d 100644
--- a/src/mobile/MobileInterface.tsx
+++ b/src/mobile/MobileInterface.tsx
@@ -212,6 +212,8 @@ export class MobileInterface extends React.Component {
renderDepth={0}
focus={emptyFunction}
styleProvider={this.whitebackground}
+ layerProvider={undefined}
+ docViewPath={returnEmptyDoclist}
parentActive={returnTrue}
whenActiveChanged={emptyFunction}
bringToFront={emptyFunction}