aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/client/documents/DocumentTypes.ts1
-rw-r--r--src/client/documents/Documents.ts9
-rw-r--r--src/client/views/DocumentDecorations.tsx3
-rw-r--r--src/client/views/MainView.tsx15
-rw-r--r--src/client/views/PreviewCursor.tsx2
-rw-r--r--src/client/views/SearchItem.tsx67
-rw-r--r--src/client/views/linking/LinkEditor.scss (renamed from src/client/views/nodes/LinkEditor.scss)0
-rw-r--r--src/client/views/linking/LinkEditor.tsx (renamed from src/client/views/nodes/LinkEditor.tsx)0
-rw-r--r--src/client/views/linking/LinkFollowBox.scss81
-rw-r--r--src/client/views/linking/LinkFollowBox.tsx440
-rw-r--r--src/client/views/linking/LinkMenu.scss (renamed from src/client/views/nodes/LinkMenu.scss)0
-rw-r--r--src/client/views/linking/LinkMenu.tsx (renamed from src/client/views/nodes/LinkMenu.tsx)3
-rw-r--r--src/client/views/linking/LinkMenuGroup.tsx (renamed from src/client/views/nodes/LinkMenuGroup.tsx)12
-rw-r--r--src/client/views/linking/LinkMenuItem.tsx295
-rw-r--r--src/client/views/nodes/DocumentContentsView.tsx4
-rw-r--r--src/client/views/nodes/DocumentView.tsx12
-rw-r--r--src/client/views/nodes/LinkMenuItem.tsx139
-rw-r--r--src/client/views/nodes/WebBox.tsx25
-rw-r--r--src/client/views/search/SearchItem.tsx1
-rw-r--r--src/new_fields/Doc.ts55
20 files changed, 929 insertions, 235 deletions
diff --git a/src/client/documents/DocumentTypes.ts b/src/client/documents/DocumentTypes.ts
index 1578e49fe..381981e1b 100644
--- a/src/client/documents/DocumentTypes.ts
+++ b/src/client/documents/DocumentTypes.ts
@@ -19,4 +19,5 @@ export enum DocumentType {
YOUTUBE = "youtube",
DRAGBOX = "dragbox",
PRES = "presentation",
+ LINKFOLLOW = "linkfollow",
} \ No newline at end of file
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 47df17329..e903d1e06 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -45,6 +45,7 @@ import { PresBox } from "../views/nodes/PresBox";
import { ComputedField } from "../../new_fields/ScriptField";
import { ProxyField } from "../../new_fields/Proxy";
import { DocumentType } from "./DocumentTypes";
+import { LinkFollowBox } from "../views/linking/LinkFollowBox";
//import { PresBox } from "../views/nodes/PresBox";
//import { PresField } from "../../new_fields/PresField";
var requestImageSize = require('../util/request-image-size');
@@ -53,6 +54,7 @@ var path = require('path');
export interface DocumentOptions {
x?: number;
y?: number;
+ z?: number;
type?: string;
width?: number;
height?: number;
@@ -169,6 +171,9 @@ export namespace Docs {
[DocumentType.DRAGBOX, {
layout: { view: DragBox },
options: { width: 40, height: 40 },
+ }],
+ [DocumentType.LINKFOLLOW, {
+ layout: { view: LinkFollowBox }
}]
]);
@@ -442,6 +447,10 @@ export namespace Docs {
return InstanceFromProto(Prototypes.get(DocumentType.DRAGBOX), undefined, { ...(options || {}) });
}
+ export function LinkFollowBoxDocument(options?: DocumentOptions) {
+ return InstanceFromProto(Prototypes.get(DocumentType.LINKFOLLOW), undefined, { ...(options || {}) });
+ }
+
export function DockDocument(documents: Array<Doc>, config: string, options: DocumentOptions, id?: string) {
return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { ...options, viewType: CollectionViewType.Docking, dockingConfig: config }, id);
}
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index ee2a57bd3..0282e42de 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -20,7 +20,7 @@ import { DocumentView, PositionDocument } from "./nodes/DocumentView";
import { FieldView } from "./nodes/FieldView";
import { FormattedTextBox, GoogleRef } from "./nodes/FormattedTextBox";
import { IconBox } from "./nodes/IconBox";
-import { LinkMenu } from "./nodes/LinkMenu";
+import { LinkMenu } from "./linking/LinkMenu";
import { TemplateMenu } from "./TemplateMenu";
import { Template, Templates } from "./Templates";
import React = require("react");
@@ -812,6 +812,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
let linkButton = null;
if (SelectionManager.SelectedDocuments().length > 0) {
let selFirst = SelectionManager.SelectedDocuments()[0];
+
let linkCount = LinkManager.Instance.getAllRelatedLinks(selFirst.props.Document).length;
linkButton = (<Flyout
anchorPoint={anchorPoints.RIGHT_TOP}
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index a02214deb..7520dff8c 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -1,5 +1,5 @@
import { IconName, library } from '@fortawesome/fontawesome-svg-core';
-import { faArrowDown, faArrowUp, faBolt, faCaretUp, faCat, faCheck, faClone, faCloudUploadAlt, faCommentAlt, faCut, faExclamation, faFilePdf, faFilm, faFont, faGlobeAsia, faLongArrowAltRight, faMusic, faObjectGroup, faPause, faPenNib, faPlay, faPortrait, faRedoAlt, faThumbtack, faTree, faUndoAlt, faTv } from '@fortawesome/free-solid-svg-icons';
+import { faLink, faArrowDown, faArrowUp, faBolt, faCaretUp, faCat, faCheck, faClone, faCloudUploadAlt, faCommentAlt, faCut, faExclamation, faFilePdf, faFilm, faFont, faGlobeAsia, faLongArrowAltRight, faMusic, faObjectGroup, faPause, faPenNib, faPlay, faPortrait, faRedoAlt, faThumbtack, faTree, faUndoAlt, faTv } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, computed, configure, observable, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
@@ -40,6 +40,7 @@ import { PreviewCursor } from './PreviewCursor';
import { FilterBox } from './search/FilterBox';
import PresModeMenu from './presentationview/PresentationModeMenu';
import { PresBox } from './nodes/PresBox';
+import { LinkFollowBox } from './linking/LinkFollowBox';
@observer
export class MainView extends React.Component {
@@ -55,6 +56,8 @@ export class MainView extends React.Component {
public overlayTimeout: NodeJS.Timeout | undefined;
+ @observable private _linkFollowBox = false;
+
public initiateDictationFade = () => {
let duration = DictationManager.Commands.dictationFadeDuration;
this.overlayTimeout = setTimeout(() => {
@@ -440,6 +443,7 @@ export class MainView extends React.Component {
let imgurl = "https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg";
let addColNode = action(() => Docs.Create.FreeformDocument([], { width: this.pwidth * .7, height: this.pheight, title: "a freeform collection" }));
+ let addLinkFollowBox = action(() => Docs.Create.LinkFollowBoxDocument({ width: 500, height: 350, title: "Link Follower" }));
let addPresNode = action(() => Doc.UserDoc().curPresentation = Docs.Create.PresDocument(new List<Doc>(), { width: 200, height: 500, title: "a presentation trail" }));
let addWebNode = action(() => Docs.Create.WebDocument("https://en.wikipedia.org/wiki/Hedgehog", { width: 300, height: 300, title: "New Webpage" }));
let addDragboxNode = action(() => Docs.Create.DragboxDocument({ width: 40, height: 40, title: "drag collection" }));
@@ -455,6 +459,7 @@ export class MainView extends React.Component {
[React.createRef<HTMLDivElement>(), "globe-asia", "Add Website", addWebNode],
[React.createRef<HTMLDivElement>(), "bolt", "Add Button", addButtonDocument],
[React.createRef<HTMLDivElement>(), "file", "Add Document Dragger", addDragboxNode],
+ [React.createRef<HTMLDivElement>(), "link", "Open Link Follow Box", addLinkFollowBox],
[React.createRef<HTMLDivElement>(), "cloud-upload-alt", "Import Directory", addImportCollectionNode], //remove at some point in favor of addImportCollectionNode
//[React.createRef<HTMLDivElement>(), "play", "Add Youtube Searcher", addYoutubeSearcher],
];
@@ -498,6 +503,12 @@ export class MainView extends React.Component {
this._colorPickerDisplay = close ? false : !this._colorPickerDisplay;
}
+ @action
+ toggleLinkFollowBox = () => {
+ console.log("toggling link editor")
+ this._linkFollowBox = !this._linkFollowBox;
+ }
+
/* @TODO this should really be moved into a moveable toolbar component, but for now let's put it here to meet the deadline */
@computed
get miscButtons() {
@@ -506,6 +517,7 @@ export class MainView extends React.Component {
return [
this.isSearchVisible ? <div className="main-searchDiv" key="search" style={{ top: '34px', right: '1px', position: 'absolute' }} > <FilterBox /> </div> : null,
<div className="main-buttonDiv" key="logout" style={{ bottom: '0px', right: '1px', position: 'absolute' }} ref={logoutRef}>
+ <button onClick={this.toggleLinkFollowBox}>Open Link Editor</button>
<button onClick={() => window.location.assign(Utils.prepend(RouteStore.logout))}>Log Out</button></div>
];
@@ -564,6 +576,7 @@ export class MainView extends React.Component {
<PDFMenu />
<MainOverlayTextBox firstinstance={true} />
<OverlayView />
+ {/* <LinkFollowBox /> */}
</div >
);
}
diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx
index d8e161ab6..1329dc02c 100644
--- a/src/client/views/PreviewCursor.tsx
+++ b/src/client/views/PreviewCursor.tsx
@@ -51,7 +51,7 @@ export class PreviewCursor extends React.Component<{}> {
// tests for URL and makes web document
let re: any = /^https?:\/\//g;
if (re.test(e.clipboardData.getData("text/plain"))) {
- const url = e.clipboardData.getData("text/plain")
+ const url = e.clipboardData.getData("text/plain");
PreviewCursor._addDocument(Docs.Create.WebDocument(url, {
title: url, width: 300, height: 300,
// nativeWidth: 300, nativeHeight: 472.5,
diff --git a/src/client/views/SearchItem.tsx b/src/client/views/SearchItem.tsx
deleted file mode 100644
index fd4b2420d..000000000
--- a/src/client/views/SearchItem.tsx
+++ /dev/null
@@ -1,67 +0,0 @@
-import React = require("react");
-import { library } from '@fortawesome/fontawesome-svg-core';
-import { faCaretUp, faFilePdf, faFilm, faImage, faObjectGroup, faStickyNote } from '@fortawesome/free-solid-svg-icons';
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { Doc } from "../../new_fields/Doc";
-import { DocumentManager } from "../util/DocumentManager";
-import { SetupDrag } from "../util/DragManager";
-
-
-export interface SearchProps {
- doc: Doc;
-}
-
-library.add(faCaretUp);
-library.add(faObjectGroup);
-library.add(faStickyNote);
-library.add(faFilePdf);
-library.add(faFilm);
-
-export class SearchItem extends React.Component<SearchProps> {
-
- onClick = () => {
- DocumentManager.Instance.jumpToDocument(this.props.doc, false);
- }
-
- //needs help
- // @computed get layout(): string { const field = Cast(this.props.doc[fieldKey], IconField); return field ? field.icon : "<p>Error loading icon data</p>"; }
-
-
- public static DocumentIcon(layout: string) {
- let button = layout.indexOf("PDFBox") !== -1 ? faFilePdf :
- layout.indexOf("ImageBox") !== -1 ? faImage :
- layout.indexOf("Formatted") !== -1 ? faStickyNote :
- layout.indexOf("Video") !== -1 ? faFilm :
- layout.indexOf("Collection") !== -1 ? faObjectGroup :
- faCaretUp;
- return <FontAwesomeIcon icon={button} className="documentView-minimizedIcon" />;
- }
- onPointerEnter = (e: React.PointerEvent) => {
- Doc.BrushDoc(this.props.doc);
- }
- onPointerLeave = (e: React.PointerEvent) => {
- Doc.UnBrushDoc(this.props.doc);
- }
-
- collectionRef = React.createRef<HTMLDivElement>();
- startDocDrag = () => {
- let doc = this.props.doc;
- const isProto = Doc.GetT(doc, "isPrototype", "boolean", true);
- if (isProto) {
- return Doc.MakeDelegate(doc);
- } else {
- return Doc.MakeAlias(doc);
- }
- }
- render() {
- return (
- <div className="search-item" ref={this.collectionRef} id="result"
- onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}
- onClick={this.onClick} onPointerDown={SetupDrag(this.collectionRef, this.startDocDrag)} >
- <div className="search-title" id="result" >title: {this.props.doc.title}</div>
- {/* <div className="search-type" id="result" >Type: {this.props.doc.layout}</div> */}
- {/* <div className="search-type" >{SearchItem.DocumentIcon(this.layout)}</div> */}
- </div>
- );
- }
-} \ No newline at end of file
diff --git a/src/client/views/nodes/LinkEditor.scss b/src/client/views/linking/LinkEditor.scss
index fc5f2410c..fc5f2410c 100644
--- a/src/client/views/nodes/LinkEditor.scss
+++ b/src/client/views/linking/LinkEditor.scss
diff --git a/src/client/views/nodes/LinkEditor.tsx b/src/client/views/linking/LinkEditor.tsx
index ecb3e9db4..ecb3e9db4 100644
--- a/src/client/views/nodes/LinkEditor.tsx
+++ b/src/client/views/linking/LinkEditor.tsx
diff --git a/src/client/views/linking/LinkFollowBox.scss b/src/client/views/linking/LinkFollowBox.scss
new file mode 100644
index 000000000..aedbfdea4
--- /dev/null
+++ b/src/client/views/linking/LinkFollowBox.scss
@@ -0,0 +1,81 @@
+@import "../globalCssVariables";
+
+.linkFollowBox-main {
+ position: absolute;
+ background: whitesmoke;
+ color: grey;
+ border-radius: 15px;
+ box-shadow: $intermediate-color 0.2vw 0.2vw 0.4vw;
+ border: solid #BBBBBBBB 5px;
+ pointer-events: all;
+ // overflow: hidden;
+
+ .linkFollowBox-header {
+ height: 30px;
+ text-align: center;
+ text-transform: uppercase;
+ line-height: 30px;
+ letter-spacing: 2px;
+ font-size: 16px;
+ }
+
+ .linkFollowBox-footer {
+ height: 50px;
+ text-align: center;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+
+ button {
+ background-color: $darker-alt-accent;
+ // height: 30px;
+ width: 30%;
+ // font-size: 18px;
+ }
+ }
+
+ .linkFollowBox-content {
+ display: grid;
+ grid-template-columns: 1fr 1fr 1fr;
+ grid-column-gap: 5px;
+ margin-left: 5px;
+ margin-right: 5px;
+
+ .linkFollowBox-item {
+ background-color: $light-color;
+ width: 100%;
+ height: 100%;
+
+ .linkFollowBox-itemContent {
+ padding: 5px;
+ font-size: 12px;
+ // line-height: 40px;
+ overflow: scroll;
+
+
+ input[type=radio] {
+ border: 0px;
+ // width: 100%;
+ // height: 20px;
+ // width: 20px;
+ margin-right: 5px;
+ }
+ }
+
+ .title {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ text-transform: uppercase;
+ color: $light-color;
+ background-color: $lighter-alt-accent;
+ width: 100%;
+ height: 30px;
+ border-bottom: solid $darker-alt-accent 5px;
+ font-size: 12px;
+ text-align: center;
+ }
+ }
+ }
+
+} \ No newline at end of file
diff --git a/src/client/views/linking/LinkFollowBox.tsx b/src/client/views/linking/LinkFollowBox.tsx
new file mode 100644
index 000000000..6d4f3633e
--- /dev/null
+++ b/src/client/views/linking/LinkFollowBox.tsx
@@ -0,0 +1,440 @@
+import { observable, computed, action, trace, ObservableMap, runInAction } from "mobx";
+import React = require("react");
+import { observer } from "mobx-react";
+import { FieldViewProps, FieldView } from "../nodes/FieldView";
+import { Doc } from "../../../new_fields/Doc";
+import { undoBatch } from "../../util/UndoManager";
+import { NumCast, FieldValue, Cast, StrCast } from "../../../new_fields/Types";
+import { CollectionViewType } from "../collections/CollectionBaseView";
+import { CollectionDockingView } from "../collections/CollectionDockingView";
+import { SelectionManager } from "../../util/SelectionManager";
+import { DocumentManager } from "../../util/DocumentManager";
+import { DocumentView } from "../nodes/DocumentView";
+import "./LinkFollowBox.scss";
+import { SearchUtil } from "../../util/SearchUtil";
+import { Id } from "../../../new_fields/FieldSymbols";
+
+enum FollowModes {
+ OPENTAB = "Open in Tab",
+ OPENRIGHT = "Open in Right Split",
+ OPENFULL = "Open Full Screen",
+ PAN = "Pan to Document",
+ INPLACE = "Open In Place"
+}
+
+enum FollowOptions {
+ ZOOM = "zoom",
+ NOZOOM = "no zoom",
+}
+
+// @observer
+// export class SelectorContextMenu extends React.Component {
+// @observable private _docs: { col: Doc, target: Doc }[] = [];
+// @observable private _otherDocs: { col: Doc, target: Doc }[] = [];
+
+// constructor(props: any) {
+// super(props);
+// this.fetchDocuments();
+// }
+
+// async fetchDocuments() {
+// let aliases = (await SearchUtil.GetViewsOfDocument(this.props.doc)).filter(doc => doc !== this.props.doc);
+// const { docs } = await SearchUtil.Search("", true, { fq: `data_l:"${this.props.doc[Id]}"` });
+// const map: Map<Doc, Doc> = new Map;
+// const allDocs = await Promise.all(aliases.map(doc => SearchUtil.Search("", true, { fq: `data_l:"${doc[Id]}"` }).then(result => result.docs)));
+// allDocs.forEach((docs, index) => docs.forEach(doc => map.set(doc, aliases[index])));
+// docs.forEach(doc => map.delete(doc));
+// runInAction(() => {
+// this._docs = docs.filter(doc => !Doc.AreProtosEqual(doc, CollectionDockingView.Instance.props.Document)).map(doc => ({ col: doc, target: this.props.doc }));
+// this._otherDocs = Array.from(map.entries()).filter(entry => !Doc.AreProtosEqual(entry[0], CollectionDockingView.Instance.props.Document)).map(([col, target]) => ({ col, target }));
+// });
+// }
+
+// getOnClick({ col, target }: { col: Doc, target: Doc }) {
+// return () => {
+// col = Doc.IsPrototype(col) ? Doc.MakeDelegate(col) : col;
+// if (NumCast(col.viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) {
+// const newPanX = NumCast(target.x) + NumCast(target.width) / NumCast(target.zoomBasis, 1) / 2;
+// const newPanY = NumCast(target.y) + NumCast(target.height) / NumCast(target.zoomBasis, 1) / 2;
+// col.panX = newPanX;
+// col.panY = newPanY;
+// }
+// CollectionDockingView.Instance.AddRightSplit(col, undefined);
+// };
+// }
+// render() {
+// return (
+// <div className="parents">
+// <p className="contexts">Contexts:</p>
+// {[...this._docs, ...this._otherDocs].map(doc => {
+// let item = React.createRef<HTMLDivElement>();
+// return <div className="collection" key={doc.col[Id] + doc.target[Id]} ref={item}>
+// <div className="collection-item" onPointerDown={
+// SetupDrag(item, () => doc.col, undefined, undefined, undefined, undefined, () => SearchBox.Instance.closeSearch())}>
+// <FontAwesomeIcon icon={faStickyNote} />
+// </div>
+// <a onClick={this.getOnClick(doc)}>{doc.col.title}</a>
+// </div>;
+// })}
+// </div>
+// );
+// }
+// }
+
+@observer
+export class LinkFollowBox extends React.Component<FieldViewProps> {
+
+ public static LayoutString() { return FieldView.LayoutString(LinkFollowBox); }
+ public static Instance: LinkFollowBox;
+ @observable static linkDoc: Doc | undefined = undefined;
+ @observable static destinationDoc: Doc | undefined = undefined;
+ @observable static sourceDoc: Doc | undefined = undefined;
+ @observable selectedMode: string = "";
+ @observable selectedContext: any = undefined;
+ @observable selectedOption: string = "";
+ @observable selectedContextString: string = "";
+
+ @observable private _docs: { col: Doc, target: Doc }[] = [];
+ @observable private _otherDocs: { col: Doc, target: Doc }[] = [];
+
+ constructor(props: FieldViewProps) {
+ super(props);
+ LinkFollowBox.Instance = this;
+ }
+
+ async fetchDocuments() {
+ if (LinkFollowBox.destinationDoc) {
+ let dest: Doc = LinkFollowBox.destinationDoc;
+ let aliases = await SearchUtil.GetViewsOfDocument(Doc.GetProto(dest));
+ const { docs } = await SearchUtil.Search("", true, { fq: `data_l:"${dest[Id]}"` });
+ const map: Map<Doc, Doc> = new Map;
+ const allDocs = await Promise.all(aliases.map(doc => SearchUtil.Search("", true, { fq: `data_l:"${doc[Id]}"` }).then(result => result.docs)));
+ allDocs.forEach((docs, index) => docs.forEach(doc => map.set(doc, aliases[index])));
+ docs.forEach(doc => map.delete(doc));
+ runInAction(() => {
+ this._docs = docs.filter(doc => !Doc.AreProtosEqual(doc, CollectionDockingView.Instance.props.Document)).map(doc => ({ col: doc, target: dest }));
+ this._otherDocs = Array.from(map.entries()).filter(entry => !Doc.AreProtosEqual(entry[0], CollectionDockingView.Instance.props.Document)).map(([col, target]) => ({ col, target }));
+ });
+ }
+ }
+
+ @action
+ setLinkDocs = (linkDoc: Doc, source: Doc, dest: Doc) => {
+ this.selectedContext = undefined;
+ this.selectedContextString = "";
+ this.selectedMode = "";
+ this.selectedOption = "";
+
+ LinkFollowBox.linkDoc = linkDoc;
+ LinkFollowBox.sourceDoc = source;
+ LinkFollowBox.destinationDoc = dest;
+ this.fetchDocuments();
+ }
+
+ unhighlight = () => {
+ Doc.UnhighlightAll();
+ document.removeEventListener("pointerdown", this.unhighlight);
+ }
+
+ @action
+ highlightDoc = (destinationDoc: Doc) => {
+ document.removeEventListener("pointerdown", this.unhighlight);
+ Doc.HighlightDoc(destinationDoc);
+ window.setTimeout(() => {
+ document.addEventListener("pointerdown", this.unhighlight);
+ }, 10000);
+ }
+
+ @undoBatch
+ openFullScreen = (destinationDoc: Doc) => {
+ let view: DocumentView | null = DocumentManager.Instance.getDocumentView(destinationDoc);
+ view && CollectionDockingView.Instance && CollectionDockingView.Instance.OpenFullScreen(view);
+ SelectionManager.DeselectAll();
+ }
+
+ // should container be a doc or documentview or what? This one needs work and is more long term
+ @undoBatch
+ openInContainer = (destinationDoc: Doc, options: { container: Doc }) => {
+
+ }
+
+ // NOT TESTED
+ @undoBatch
+ openLinkColRight = (destinationDoc: Doc, options: { context: Doc }) => {
+ options.context = Doc.IsPrototype(options.context) ? Doc.MakeDelegate(options.context) : options.context;
+ if (NumCast(options.context.viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) {
+ const newPanX = NumCast(destinationDoc.x) + NumCast(destinationDoc.width) / NumCast(destinationDoc.zoomBasis, 1) / 2;
+ const newPanY = NumCast(destinationDoc.y) + NumCast(destinationDoc.height) / NumCast(destinationDoc.zoomBasis, 1) / 2;
+ options.context.panX = newPanX;
+ options.context.panY = newPanY;
+ }
+ CollectionDockingView.Instance.AddRightSplit(options.context, undefined);
+
+ this.highlightDoc(destinationDoc);
+ SelectionManager.DeselectAll();
+ }
+
+ @undoBatch
+ openLinkRight = (destinationDoc: Doc) => {
+ let alias = Doc.MakeAlias(destinationDoc);
+ CollectionDockingView.Instance.AddRightSplit(alias, undefined);
+ this.highlightDoc(destinationDoc);
+ SelectionManager.DeselectAll();
+
+ }
+
+ @undoBatch
+ jumpToLink = async (destinationDoc: Doc, options: { shouldZoom: boolean, linkDoc: Doc }) => {
+ let jumpToDoc = destinationDoc;
+ let pdfDoc = FieldValue(Cast(destinationDoc, Doc));
+ if (pdfDoc) {
+ jumpToDoc = pdfDoc;
+ }
+ let proto = Doc.GetProto(options.linkDoc);
+ let targetContext = await Cast(proto.targetContext, Doc);
+ let sourceContext = await Cast(proto.sourceContext, Doc);
+
+ let dockingFunc = (document: Doc) => { this.props.addDocTab(document, undefined, "inTab"); SelectionManager.DeselectAll(); };
+
+ if (destinationDoc === options.linkDoc.anchor2 && targetContext) {
+ DocumentManager.Instance.jumpToDocument(jumpToDoc, options.shouldZoom, false, async document => dockingFunc(document), undefined, targetContext);
+ }
+ else if (destinationDoc === options.linkDoc.anchor1 && sourceContext) {
+ DocumentManager.Instance.jumpToDocument(jumpToDoc, options.shouldZoom, false, document => dockingFunc(sourceContext!));
+ }
+ else if (DocumentManager.Instance.getDocumentView(jumpToDoc)) {
+ DocumentManager.Instance.jumpToDocument(jumpToDoc, options.shouldZoom, undefined, undefined, NumCast((destinationDoc === options.linkDoc.anchor2 ? options.linkDoc.anchor2Page : options.linkDoc.anchor1Page)));
+
+ }
+ else {
+ DocumentManager.Instance.jumpToDocument(jumpToDoc, options.shouldZoom, false, dockingFunc);
+ }
+
+ this.highlightDoc(destinationDoc);
+ SelectionManager.DeselectAll();
+ }
+
+ @undoBatch
+ openLinkTab = (destinationDoc: Doc) => {
+ let fullScreenAlias = Doc.MakeAlias(destinationDoc);
+ this.props.addDocTab(fullScreenAlias, undefined, "inTab");
+
+ this.highlightDoc(destinationDoc);
+ SelectionManager.DeselectAll();
+ }
+
+ // NOT TESTED
+ @undoBatch
+ openLinkColTab = (destinationDoc: Doc, options: { context: Doc }) => {
+ options.context = Doc.IsPrototype(options.context) ? Doc.MakeDelegate(options.context) : options.context;
+ if (NumCast(options.context.viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) {
+ const newPanX = NumCast(destinationDoc.x) + NumCast(destinationDoc.width) / NumCast(destinationDoc.zoomBasis, 1) / 2;
+ const newPanY = NumCast(destinationDoc.y) + NumCast(destinationDoc.height) / NumCast(destinationDoc.zoomBasis, 1) / 2;
+ options.context.panX = newPanX;
+ options.context.panY = newPanY;
+ }
+ this.props.addDocTab(options.context, undefined, "inTab");
+
+ this.highlightDoc(destinationDoc);
+ SelectionManager.DeselectAll();
+ }
+
+ @undoBatch
+ openLinkInPlace = (destinationDoc: Doc, options: { sourceDoc: Doc, linkDoc: Doc }) => {
+
+ let alias = Doc.MakeAlias(destinationDoc);
+ let y = NumCast(options.sourceDoc.y);
+ let x = NumCast(options.sourceDoc.x);
+
+ let width = NumCast(options.sourceDoc.width);
+ let height = NumCast(options.sourceDoc.height);
+
+ alias.x = x + width + 30;
+ alias.y = y;
+ alias.width = width;
+ alias.height = height;
+
+ SelectionManager.SelectedDocuments().map(dv => {
+ if (dv.props.Document === options.sourceDoc) {
+ dv.props.addDocument && dv.props.addDocument(alias, false);
+ }
+ });
+
+ this.jumpToLink(destinationDoc, { shouldZoom: false, linkDoc: options.linkDoc });
+
+ this.highlightDoc(destinationDoc);
+ SelectionManager.DeselectAll();
+ }
+
+ //set this to be the default link behavior, can be any of the above
+ private defaultLinkBehavior: (destinationDoc: Doc, options?: any) => void = this.openLinkInPlace;
+ // private currentLinkBehavior: (destinationDoc: Doc, options?: any) => void = this.defaultLinkBehavior;
+
+ @computed
+ get LinkFollowTitle(): string {
+ if (LinkFollowBox.linkDoc) {
+ return StrCast(LinkFollowBox.linkDoc.title);
+ }
+ return "No Link Selected";
+ }
+
+ @action
+ currentLinkBehavior = () => {
+ if (this.selectedMode === FollowModes.INPLACE) {
+
+ }
+ else if (this.selectedMode === FollowModes.OPENFULL) {
+
+ }
+ else if (this.selectedMode === FollowModes.OPENRIGHT) {
+
+ }
+ else if (this.selectedMode === FollowModes.OPENTAB) {
+
+ }
+ else if (this.selectedMode === FollowModes.INPLACE) {
+
+ }
+ else if (this.selectedMode === FollowModes.PAN) {
+
+ }
+ else return;
+ }
+
+ @action
+ handleModeChange = (e: React.ChangeEvent) => {
+ let target = e.target as HTMLInputElement;
+ this.selectedMode = target.value;
+ }
+
+ @action
+ handleOptionChange = (e: React.ChangeEvent) => {
+ let target = e.target as HTMLInputElement;
+ this.selectedOption = target.value;
+ }
+
+ @action
+ handleContextChange = (e: React.ChangeEvent) => {
+ let target = e.target as HTMLInputElement;
+ this.selectedContextString = target.value;
+ }
+
+ @computed
+ get availableModes() {
+ return (
+ <div>
+ <label><input
+ type="radio"
+ name="mode"
+ value={FollowModes.OPENRIGHT}
+ checked={this.selectedMode === FollowModes.OPENRIGHT}
+ onChange={this.handleModeChange} />
+ {FollowModes.OPENRIGHT}
+ </label><br />
+ <label><input
+ type="radio"
+ name="mode"
+ value={FollowModes.OPENTAB}
+ checked={this.selectedMode === FollowModes.OPENTAB}
+ onChange={this.handleModeChange} />
+ {FollowModes.OPENTAB}
+ </label><br />
+ <label><input
+ type="radio"
+ name="mode"
+ value={FollowModes.OPENFULL}
+ checked={this.selectedMode === FollowModes.OPENFULL}
+ onChange={this.handleModeChange} />
+ {FollowModes.OPENFULL}
+ </label><br />
+ <label><input
+ type="radio"
+ name="mode"
+ value={FollowModes.PAN}
+ checked={this.selectedMode === FollowModes.PAN}
+ onChange={this.handleModeChange} />
+ {FollowModes.PAN}
+ </label><br />
+ <label><input
+ type="radio"
+ name="mode"
+ value={FollowModes.INPLACE}
+ checked={this.selectedMode === FollowModes.INPLACE}
+ onChange={this.handleModeChange} />
+ {FollowModes.INPLACE}
+ </label><br />
+ </div>
+ );
+ }
+
+ @computed
+ get availableContexts() {
+ return (
+ <div>
+ <label><input
+ type="radio"
+ name="context"
+ value="self"
+ checked={this.selectedContextString === "self"}
+ onChange={this.handleContextChange} />
+ Open Self
+ </label><br />
+ {[...this._docs, ...this._otherDocs].map(doc => {
+ if (doc && doc.target) {
+ return <div key={doc.col[Id] + doc.target[Id]}><label key={doc.col[Id] + doc.target[Id]}>
+ <input
+ type="radio"
+ name="context"
+ value={StrCast(doc.col.title)}
+ checked={this.selectedContextString === StrCast(doc.col.title)}
+ onChange={this.handleContextChange} />
+ {doc.col.title}
+ </label><br /></div>;
+ }
+ })}
+ </div>
+ );
+ }
+
+ @computed
+ get availableOptions() {
+ return (
+ <div>
+
+ </div>
+ );
+ }
+
+ render() {
+ return (
+ <div className="linkFollowBox-main" style={{ height: NumCast(this.props.Document.height), width: NumCast(this.props.Document.width) }}>
+ <div className="linkFollowBox-header">{this.LinkFollowTitle}</div>
+ <div className="linkFollowBox-content" style={{ height: NumCast(this.props.Document.height) - 90 }}>
+ <div className="linkFollowBox-item">
+ <div className="linkFollowBox-item title">Mode</div>
+ <div className="linkFollowBox-itemContent">{this.availableModes}</div>
+ </div>
+ <div className="linkFollowBox-item">
+ <div className="linkFollowBox-item title">Context</div>
+ <div className="linkFollowBox-itemContent">
+ {this.selectedMode !== "" ? this.availableContexts : "Please select a mode to view contexts"}
+ </div>
+ </div>
+ <div className="linkFollowBox-item">
+ <div className="linkFollowBox-item title">Options</div>
+ <div className="linkFollowBox-itemContent">
+ {this.selectedContextString !== "" ? this.availableOptions : "Please select a context to view options"}
+ </div>
+ </div>
+ </div>
+ <div className="linkFollowBox-footer">
+ <button
+ onClick={this.currentLinkBehavior}
+ disabled={(LinkFollowBox.linkDoc) ? false : true}>
+ Follow Link
+ </button>
+ </div>
+ </div>
+ );
+ }
+} \ No newline at end of file
diff --git a/src/client/views/nodes/LinkMenu.scss b/src/client/views/linking/LinkMenu.scss
index a4018bd2d..a4018bd2d 100644
--- a/src/client/views/nodes/LinkMenu.scss
+++ b/src/client/views/linking/LinkMenu.scss
diff --git a/src/client/views/nodes/LinkMenu.tsx b/src/client/views/linking/LinkMenu.tsx
index 1908889e9..842ce45b1 100644
--- a/src/client/views/nodes/LinkMenu.tsx
+++ b/src/client/views/linking/LinkMenu.tsx
@@ -1,6 +1,6 @@
import { action, observable } from "mobx";
import { observer } from "mobx-react";
-import { DocumentView } from "./DocumentView";
+import { DocumentView } from "../nodes/DocumentView";
import { LinkEditor } from "./LinkEditor";
import './LinkMenu.scss';
import React = require("react");
@@ -39,6 +39,7 @@ export class LinkMenu extends React.Component<Props> {
linkItems.push(
<LinkMenuGroup
key={groupType}
+ docView={this.props.docView}
sourceDoc={this.props.docView.props.Document}
group={group}
groupType={groupType}
diff --git a/src/client/views/nodes/LinkMenuGroup.tsx b/src/client/views/linking/LinkMenuGroup.tsx
index e04044266..b6a24b0c8 100644
--- a/src/client/views/nodes/LinkMenuGroup.tsx
+++ b/src/client/views/linking/LinkMenuGroup.tsx
@@ -1,6 +1,6 @@
import { action, observable } from "mobx";
import { observer } from "mobx-react";
-import { DocumentView } from "./DocumentView";
+import { DocumentView } from "../nodes/DocumentView";
import { LinkMenuItem } from "./LinkMenuItem";
import { LinkEditor } from "./LinkEditor";
import './LinkMenu.scss';
@@ -22,6 +22,8 @@ interface LinkMenuGroupProps {
groupType: string;
showEditor: (linkDoc: Doc) => void;
addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => void;
+ docView: DocumentView;
+
}
@observer
@@ -83,9 +85,13 @@ export class LinkMenuGroup extends React.Component<LinkMenuGroupProps> {
let groupItems = this.props.group.map(linkDoc => {
let destination = LinkManager.Instance.getOppositeAnchor(linkDoc, this.props.sourceDoc);
if (destination && this.props.sourceDoc) {
- return <LinkMenuItem key={destination[Id] + this.props.sourceDoc[Id]} groupType={this.props.groupType}
+ return <LinkMenuItem key={destination[Id] + this.props.sourceDoc[Id]}
+ groupType={this.props.groupType}
addDocTab={this.props.addDocTab}
- linkDoc={linkDoc} sourceDoc={this.props.sourceDoc} destinationDoc={destination} showEditor={this.props.showEditor} />;
+ linkDoc={linkDoc}
+ sourceDoc={this.props.sourceDoc}
+ destinationDoc={destination}
+ showEditor={this.props.showEditor} />;
}
});
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
new file mode 100644
index 000000000..406429ebf
--- /dev/null
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -0,0 +1,295 @@
+import { library } from '@fortawesome/fontawesome-svg-core';
+import { faEdit, faEye, faTimes, faArrowRight, faChevronDown, faChevronUp, faGlobeAsia } from '@fortawesome/free-solid-svg-icons';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { observer } from "mobx-react";
+import { DocumentManager } from "../../util/DocumentManager";
+import { undoBatch } from "../../util/UndoManager";
+import './LinkMenu.scss';
+import React = require("react");
+import { Doc, DocListCastAsync, WidthSym } from '../../../new_fields/Doc';
+import { StrCast, Cast, FieldValue, NumCast } from '../../../new_fields/Types';
+import { observable, action } from 'mobx';
+import { LinkManager } from '../../util/LinkManager';
+import { DragLinkAsDocument } from '../../util/DragManager';
+import { CollectionDockingView } from '../collections/CollectionDockingView';
+import { SelectionManager } from '../../util/SelectionManager';
+import { CollectionViewType } from '../collections/CollectionBaseView';
+import { DocumentView } from '../nodes/DocumentView';
+import { SearchUtil } from '../../util/SearchUtil';
+import { LinkFollowBox } from './LinkFollowBox';
+library.add(faEye, faEdit, faTimes, faArrowRight, faChevronDown, faChevronUp);
+
+
+interface LinkMenuItemProps {
+ groupType: string;
+ linkDoc: Doc;
+ sourceDoc: Doc;
+ destinationDoc: Doc;
+ showEditor: (linkDoc: Doc) => void;
+ addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => void;
+}
+
+@observer
+export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
+ private _drag = React.createRef<HTMLDivElement>();
+ @observable private _showMore: boolean = false;
+ @action toggleShowMore() { this._showMore = !this._showMore; }
+
+
+ unhighlight = () => {
+ Doc.UnhighlightAll();
+ document.removeEventListener("pointerdown", this.unhighlight);
+ }
+
+ @action
+ highlightDoc = () => {
+ document.removeEventListener("pointerdown", this.unhighlight);
+ Doc.HighlightDoc(this.props.destinationDoc);
+ window.setTimeout(() => {
+ document.addEventListener("pointerdown", this.unhighlight);
+ }, 10000);
+ }
+
+ // NOT TESTED
+ // col = collection the doc is in
+ // target = the document to center on
+ @undoBatch
+ openLinkColRight = (col: Doc) => {
+ col = Doc.IsPrototype(col) ? Doc.MakeDelegate(col) : col;
+ if (NumCast(col.viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) {
+ const newPanX = NumCast(this.props.destinationDoc.x) + NumCast(this.props.destinationDoc.width) / NumCast(this.props.destinationDoc.zoomBasis, 1) / 2;
+ const newPanY = NumCast(this.props.destinationDoc.y) + NumCast(this.props.destinationDoc.height) / NumCast(this.props.destinationDoc.zoomBasis, 1) / 2;
+ col.panX = newPanX;
+ col.panY = newPanY;
+ }
+ CollectionDockingView.Instance.AddRightSplit(col, undefined);
+ }
+
+ // DONE
+ @undoBatch
+ openFullScreen = () => {
+ let view: DocumentView | null = DocumentManager.Instance.getDocumentView(this.props.destinationDoc);
+ view && CollectionDockingView.Instance && CollectionDockingView.Instance.OpenFullScreen(view);
+ }
+
+ // DONE
+ // this opens the linked doc in a right split, NOT in its collection
+ @undoBatch
+ openLinkRight = () => {
+ this.highlightDoc();
+ let alias = Doc.MakeAlias(this.props.destinationDoc);
+ CollectionDockingView.Instance.AddRightSplit(alias, undefined);
+ SelectionManager.DeselectAll();
+ }
+
+ // DONE
+ // this is the standard "follow link" (jump to document)
+ // taken from follow link
+ @undoBatch
+ jumpToLink = async (shouldZoom: boolean) => {
+ //there is an issue right now so this will be false automatically
+ shouldZoom = false;
+ this.highlightDoc();
+ let jumpToDoc = this.props.destinationDoc;
+ let pdfDoc = FieldValue(Cast(this.props.destinationDoc, Doc));
+ if (pdfDoc) {
+ jumpToDoc = pdfDoc;
+ }
+ let proto = Doc.GetProto(this.props.linkDoc);
+ let targetContext = await Cast(proto.targetContext, Doc);
+ let sourceContext = await Cast(proto.sourceContext, Doc);
+ let self = this;
+
+ let dockingFunc = (document: Doc) => { this.props.addDocTab(document, undefined, "inTab"); SelectionManager.DeselectAll(); };
+
+ if (this.props.destinationDoc === self.props.linkDoc.anchor2 && targetContext) {
+ DocumentManager.Instance.jumpToDocument(jumpToDoc, shouldZoom, false, async document => dockingFunc(document), undefined, targetContext);
+ }
+ else if (this.props.destinationDoc === self.props.linkDoc.anchor1 && sourceContext) {
+ DocumentManager.Instance.jumpToDocument(jumpToDoc, shouldZoom, false, document => dockingFunc(sourceContext!));
+ }
+ else if (DocumentManager.Instance.getDocumentView(jumpToDoc)) {
+ DocumentManager.Instance.jumpToDocument(jumpToDoc, shouldZoom, undefined, undefined, NumCast((this.props.destinationDoc === self.props.linkDoc.anchor2 ? self.props.linkDoc.anchor2Page : self.props.linkDoc.anchor1Page)));
+
+ }
+ else {
+ DocumentManager.Instance.jumpToDocument(jumpToDoc, shouldZoom, false, dockingFunc);
+ }
+ }
+
+ // DONE
+ // opens link in new tab (not in a collection)
+ // this opens it full screen, do we need a separate full screen option?
+ @undoBatch
+ openLinkTab = () => {
+ this.highlightDoc();
+ let fullScreenAlias = Doc.MakeAlias(this.props.destinationDoc);
+ this.props.addDocTab(fullScreenAlias, undefined, "inTab");
+ SelectionManager.DeselectAll();
+ }
+
+ // NOT TESTED
+ // opens link in new tab in collection
+ // col = collection the doc is in
+ // target = the document to center on
+ @undoBatch
+ openLinkColTab = (col: Doc) => {
+ this.highlightDoc();
+ col = Doc.IsPrototype(col) ? Doc.MakeDelegate(col) : col;
+ if (NumCast(col.viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) {
+ const newPanX = NumCast(this.props.destinationDoc.x) + NumCast(this.props.destinationDoc.width) / NumCast(this.props.destinationDoc.zoomBasis, 1) / 2;
+ const newPanY = NumCast(this.props.destinationDoc.y) + NumCast(this.props.destinationDoc.height) / NumCast(this.props.destinationDoc.zoomBasis, 1) / 2;
+ col.panX = newPanX;
+ col.panY = newPanY;
+ }
+ // CollectionDockingView.Instance.AddRightSplit(col, undefined);
+ this.props.addDocTab(col, undefined, "inTab");
+ SelectionManager.DeselectAll();
+ }
+
+ // DONE
+ // this will open a link next to the source doc
+ @undoBatch
+ openLinkInPlace = () => {
+ this.highlightDoc();
+
+ let alias = Doc.MakeAlias(this.props.destinationDoc);
+ let y = NumCast(this.props.sourceDoc.y);
+ let x = NumCast(this.props.sourceDoc.x);
+
+ let width = NumCast(this.props.sourceDoc.width);
+ let height = NumCast(this.props.sourceDoc.height);
+
+ alias.x = x + width + 30;
+ alias.y = y;
+ alias.width = width;
+ alias.height = height;
+
+ SelectionManager.SelectedDocuments().map(dv => {
+ if (dv.props.Document === this.props.sourceDoc) {
+ dv.props.addDocument && dv.props.addDocument(alias, false);
+ }
+ });
+
+ this.jumpToLink(false);
+ }
+
+ //set this to be the default link behavior, can be any of the above
+ // private defaultLinkBehavior: any = LinkFollowBox.computeLinkDocs(this.props.linkDoc);
+
+ onEdit = (e: React.PointerEvent): void => {
+ e.stopPropagation();
+ this.props.showEditor(this.props.linkDoc);
+ SelectionManager.DeselectAll();
+ }
+
+ renderMetadata = (): JSX.Element => {
+ let groups = LinkManager.Instance.getAnchorGroups(this.props.linkDoc, this.props.sourceDoc);
+ let index = groups.findIndex(groupDoc => StrCast(groupDoc.type).toUpperCase() === this.props.groupType.toUpperCase());
+ let groupDoc = index > -1 ? groups[index] : undefined;
+
+ let mdRows: Array<JSX.Element> = [];
+ if (groupDoc) {
+ let mdDoc = Cast(groupDoc.metadata, Doc, null);
+ if (mdDoc) {
+ let keys = LinkManager.Instance.getMetadataKeysInGroup(this.props.groupType);//groupMetadataKeys.get(this.props.groupType);
+ mdRows = keys.map(key => {
+ return (<div key={key} className="link-metadata-row"><b>{key}</b>: {StrCast(mdDoc[key])}</div>);
+ });
+ }
+ }
+
+ return (<div className="link-metadata">{mdRows}</div>);
+ }
+
+ onLinkButtonDown = (e: React.PointerEvent): void => {
+ e.stopPropagation();
+ document.removeEventListener("pointermove", this.onLinkButtonMoved);
+ document.addEventListener("pointermove", this.onLinkButtonMoved);
+ document.removeEventListener("pointerup", this.onLinkButtonUp);
+ document.addEventListener("pointerup", this.onLinkButtonUp);
+ }
+
+ onLinkButtonUp = (e: PointerEvent): void => {
+ document.removeEventListener("pointermove", this.onLinkButtonMoved);
+ document.removeEventListener("pointerup", this.onLinkButtonUp);
+ e.stopPropagation();
+ }
+
+ onLinkButtonMoved = async (e: PointerEvent) => {
+ if (this._drag.current !== null && (e.movementX > 1 || e.movementY > 1)) {
+ document.removeEventListener("pointermove", this.onLinkButtonMoved);
+ document.removeEventListener("pointerup", this.onLinkButtonUp);
+
+ DragLinkAsDocument(this._drag.current, e.x, e.y, this.props.linkDoc, this.props.sourceDoc);
+ }
+ e.stopPropagation();
+ }
+
+ render() {
+
+ let keys = LinkManager.Instance.getMetadataKeysInGroup(this.props.groupType);//groupMetadataKeys.get(this.props.groupType);
+ let canExpand = keys ? keys.length > 0 : false;
+
+ return (
+ <div className="linkMenu-item">
+ <div className={canExpand ? "linkMenu-item-content expand-three" : "linkMenu-item-content expand-two"}>
+ <div className="link-name">
+ <p ref={this._drag} onPointerDown={this.onLinkButtonDown}>{StrCast(this.props.destinationDoc.title)}</p>
+ <div className="linkMenu-item-buttons">
+ {canExpand ? <div title="Show more" className="button" onPointerDown={() => this.toggleShowMore()}>
+ <FontAwesomeIcon className="fa-icon" icon={this._showMore ? "chevron-up" : "chevron-down"} size="sm" /></div> : <></>}
+ <div title="Edit link" className="button" onPointerDown={this.onEdit}><FontAwesomeIcon className="fa-icon" icon="edit" size="sm" /></div>
+ {/* Original */}
+ {/* <div title="Follow link" className="button" onPointerDown={this.onFollowLink}><FontAwesomeIcon className="fa-icon" icon="arrow-right" size="sm" /></div> */}
+ {/* New */}
+ <div title="Follow link" className="button" onClick={() => LinkFollowBox.Instance.setLinkDocs(this.props.linkDoc, this.props.sourceDoc, this.props.destinationDoc)}><FontAwesomeIcon className="fa-icon" icon="arrow-right" size="sm" /></div>
+ </div>
+ </div>
+ {this._showMore ? this.renderMetadata() : <></>}
+ </div>
+
+ </div >
+ );
+ }
+}
+
+ // @undoBatch
+ // onFollowLink = async (e: React.PointerEvent): Promise<void> => {
+ // e.stopPropagation();
+ // e.persist();
+ // let jumpToDoc = this.props.destinationDoc;
+ // let pdfDoc = FieldValue(Cast(this.props.destinationDoc, Doc));
+ // if (pdfDoc) {
+ // jumpToDoc = pdfDoc;
+ // }
+ // let proto = Doc.GetProto(this.props.linkDoc);
+ // let targetContext = await Cast(proto.targetContext, Doc);
+ // let sourceContext = await Cast(proto.sourceContext, Doc);
+ // let self = this;
+
+
+ // let dockingFunc = (document: Doc) => { this.props.addDocTab(document, undefined, "inTab"); SelectionManager.DeselectAll(); };
+ // if (e.ctrlKey) {
+ // dockingFunc = (document: Doc) => CollectionDockingView.Instance.AddRightSplit(document, undefined);
+ // }
+
+ // if (this.props.destinationDoc === self.props.linkDoc.anchor2 && targetContext) {
+ // DocumentManager.Instance.jumpToDocument(jumpToDoc, e.altKey, false, async document => dockingFunc(document), undefined, targetContext!);
+ // console.log("1")
+ // }
+ // else if (this.props.destinationDoc === self.props.linkDoc.anchor1 && sourceContext) {
+ // DocumentManager.Instance.jumpToDocument(jumpToDoc, e.altKey, false, document => dockingFunc(sourceContext!));
+ // console.log("2")
+ // }
+ // else if (DocumentManager.Instance.getDocumentView(jumpToDoc)) {
+ // DocumentManager.Instance.jumpToDocument(jumpToDoc, e.altKey, undefined, undefined, NumCast((this.props.destinationDoc === self.props.linkDoc.anchor2 ? self.props.linkDoc.anchor2Page : self.props.linkDoc.anchor1Page)));
+ // console.log("3")
+
+ // }
+ // else {
+ // DocumentManager.Instance.jumpToDocument(jumpToDoc, e.altKey, false, dockingFunc);
+ // console.log("4")
+
+ // }
+ // } \ No newline at end of file
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index d77662355..d0e117fe4 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -14,6 +14,7 @@ import { ImageBox } from "./ImageBox";
import { DragBox } from "./DragBox";
import { ButtonBox } from "./ButtonBox";
import { PresBox } from "./PresBox";
+import { LinkFollowBox } from "../linking/LinkFollowBox";
import { IconBox } from "./IconBox";
import { KeyValueBox } from "./KeyValueBox";
import { PDFBox } from "./PDFBox";
@@ -30,6 +31,7 @@ import { List } from "../../../new_fields/List";
import { Doc } from "../../../new_fields/Doc";
import DirectoryImportBox from "../../util/Import & Export/DirectoryImportBox";
import { ScriptField } from "../../../new_fields/ScriptField";
+import { fromPromise } from "mobx-utils";
const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
type BindingProps = Without<FieldViewProps, 'fieldKey'>;
@@ -108,7 +110,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
if (!this.layout && (this.props.layoutKey !== "overlayLayout" || !this.templates.length)) return (null);
return <ObserverJsxParser
blacklistedAttrs={[]}
- components={{ FormattedTextBox, ImageBox, IconBox, DirectoryImportBox, DragBox, ButtonBox, FieldView, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox, HistogramBox, PresBox, YoutubeBox }}
+ components={{ FormattedTextBox, ImageBox, IconBox, DirectoryImportBox, DragBox, ButtonBox, FieldView, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox, HistogramBox, PresBox, YoutubeBox, LinkFollowBox }}
bindings={this.CreateBindings()}
jsx={this.finalLayout}
showWarnings={true}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 3cf86b6f9..f682c3d22 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -761,18 +761,20 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
}
let showTextTitle = showTitle && StrCast(this.layoutDoc.layout).startsWith("<FormattedTextBox") ? showTitle : undefined;
let brushDegree = Doc.IsBrushedDegree(this.props.Document);
+ let fullDegree = Doc.isBrushedHighlightedDegree(this.props.Document);
+ // console.log(fullDegree)
let borderRounding = StrCast(Doc.GetProto(this.props.Document).borderRounding);
- let localScale = this.props.ScreenToLocalTransform().Scale * brushDegree;
+ let localScale = this.props.ScreenToLocalTransform().Scale * fullDegree;
return (
<div className={`documentView-node${this.topMost ? "-topmost" : ""}`}
ref={this._mainCont}
style={{
pointerEvents: this.layoutDoc.isBackground && !this.isSelected() ? "none" : "all",
color: foregroundColor,
- outlineColor: ["transparent", "maroon", "maroon"][brushDegree],
- outlineStyle: ["none", "dashed", "solid"][brushDegree],
- outlineWidth: brushDegree && !borderRounding ? `${localScale}px` : "0px",
- border: brushDegree && borderRounding ? `${["none", "dashed", "solid"][brushDegree]} ${["transparent", "maroon", "maroon"][brushDegree]} ${localScale}px` : undefined,
+ outlineColor: ["transparent", "maroon", "maroon", "yellow"][fullDegree],
+ outlineStyle: ["none", "dashed", "solid", "solid"][fullDegree],
+ outlineWidth: fullDegree && !borderRounding ? `${localScale}px` : "0px",
+ border: fullDegree && borderRounding ? `${["none", "dashed", "solid", "solid"][fullDegree]} ${["transparent", "maroon", "maroon", "yellow"][fullDegree]} ${localScale}px` : undefined,
borderRadius: "inherit",
background: backgroundColor,
width: nativeWidth,
diff --git a/src/client/views/nodes/LinkMenuItem.tsx b/src/client/views/nodes/LinkMenuItem.tsx
deleted file mode 100644
index 90b335933..000000000
--- a/src/client/views/nodes/LinkMenuItem.tsx
+++ /dev/null
@@ -1,139 +0,0 @@
-import { library } from '@fortawesome/fontawesome-svg-core';
-import { faEdit, faEye, faTimes, faArrowRight, faChevronDown, faChevronUp } from '@fortawesome/free-solid-svg-icons';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { observer } from "mobx-react";
-import { DocumentManager } from "../../util/DocumentManager";
-import { undoBatch } from "../../util/UndoManager";
-import './LinkMenu.scss';
-import React = require("react");
-import { Doc, DocListCastAsync } from '../../../new_fields/Doc';
-import { StrCast, Cast, FieldValue, NumCast } from '../../../new_fields/Types';
-import { observable, action } from 'mobx';
-import { LinkManager } from '../../util/LinkManager';
-import { DragLinkAsDocument } from '../../util/DragManager';
-import { CollectionDockingView } from '../collections/CollectionDockingView';
-import { SelectionManager } from '../../util/SelectionManager';
-library.add(faEye, faEdit, faTimes, faArrowRight, faChevronDown, faChevronUp);
-
-
-interface LinkMenuItemProps {
- groupType: string;
- linkDoc: Doc;
- sourceDoc: Doc;
- destinationDoc: Doc;
- showEditor: (linkDoc: Doc) => void;
- addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => void;
-}
-
-@observer
-export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
- private _drag = React.createRef<HTMLDivElement>();
- @observable private _showMore: boolean = false;
- @action toggleShowMore() { this._showMore = !this._showMore; }
-
- @undoBatch
- onFollowLink = async (e: React.PointerEvent): Promise<void> => {
- e.stopPropagation();
- e.persist();
- let jumpToDoc = this.props.destinationDoc;
- let pdfDoc = FieldValue(Cast(this.props.destinationDoc, Doc));
- if (pdfDoc) {
- jumpToDoc = pdfDoc;
- }
- let proto = Doc.GetProto(this.props.linkDoc);
- let targetContext = await Cast(proto.targetContext, Doc);
- let sourceContext = await Cast(proto.sourceContext, Doc);
- let self = this;
-
-
- let dockingFunc = (document: Doc) => { this.props.addDocTab(document, undefined, "inTab"); SelectionManager.DeselectAll(); };
- if (e.ctrlKey) {
- dockingFunc = (document: Doc) => CollectionDockingView.Instance.AddRightSplit(document, undefined);
- }
-
- if (this.props.destinationDoc === self.props.linkDoc.anchor2 && targetContext) {
- DocumentManager.Instance.jumpToDocument(jumpToDoc, e.altKey, false, async document => dockingFunc(document), undefined, targetContext);
- }
- else if (this.props.destinationDoc === self.props.linkDoc.anchor1 && sourceContext) {
- DocumentManager.Instance.jumpToDocument(jumpToDoc, e.altKey, false, document => dockingFunc(sourceContext!));
- }
- else if (DocumentManager.Instance.getDocumentView(jumpToDoc)) {
- DocumentManager.Instance.jumpToDocument(jumpToDoc, e.altKey, undefined, undefined, NumCast((this.props.destinationDoc === self.props.linkDoc.anchor2 ? self.props.linkDoc.anchor2Page : self.props.linkDoc.anchor1Page)));
- }
- else {
- DocumentManager.Instance.jumpToDocument(jumpToDoc, e.altKey, false, dockingFunc);
- }
- }
-
- onEdit = (e: React.PointerEvent): void => {
- e.stopPropagation();
- this.props.showEditor(this.props.linkDoc);
- }
-
- renderMetadata = (): JSX.Element => {
- let groups = LinkManager.Instance.getAnchorGroups(this.props.linkDoc, this.props.sourceDoc);
- let index = groups.findIndex(groupDoc => StrCast(groupDoc.type).toUpperCase() === this.props.groupType.toUpperCase());
- let groupDoc = index > -1 ? groups[index] : undefined;
-
- let mdRows: Array<JSX.Element> = [];
- if (groupDoc) {
- let mdDoc = Cast(groupDoc.metadata, Doc, null);
- if (mdDoc) {
- let keys = LinkManager.Instance.getMetadataKeysInGroup(this.props.groupType);//groupMetadataKeys.get(this.props.groupType);
- mdRows = keys.map(key => {
- return (<div key={key} className="link-metadata-row"><b>{key}</b>: {StrCast(mdDoc[key])}</div>);
- });
- }
- }
-
- return (<div className="link-metadata">{mdRows}</div>);
- }
-
- onLinkButtonDown = (e: React.PointerEvent): void => {
- e.stopPropagation();
- document.removeEventListener("pointermove", this.onLinkButtonMoved);
- document.addEventListener("pointermove", this.onLinkButtonMoved);
- document.removeEventListener("pointerup", this.onLinkButtonUp);
- document.addEventListener("pointerup", this.onLinkButtonUp);
- }
-
- onLinkButtonUp = (e: PointerEvent): void => {
- document.removeEventListener("pointermove", this.onLinkButtonMoved);
- document.removeEventListener("pointerup", this.onLinkButtonUp);
- e.stopPropagation();
- }
-
- onLinkButtonMoved = async (e: PointerEvent) => {
- if (this._drag.current !== null && (e.movementX > 1 || e.movementY > 1)) {
- document.removeEventListener("pointermove", this.onLinkButtonMoved);
- document.removeEventListener("pointerup", this.onLinkButtonUp);
-
- DragLinkAsDocument(this._drag.current, e.x, e.y, this.props.linkDoc, this.props.sourceDoc);
- }
- e.stopPropagation();
- }
-
- render() {
-
- let keys = LinkManager.Instance.getMetadataKeysInGroup(this.props.groupType);//groupMetadataKeys.get(this.props.groupType);
- let canExpand = keys ? keys.length > 0 : false;
-
- return (
- <div className="linkMenu-item">
- <div className={canExpand ? "linkMenu-item-content expand-three" : "linkMenu-item-content expand-two"}>
- <div className="link-name">
- <p ref={this._drag} onPointerDown={this.onLinkButtonDown}>{StrCast(this.props.destinationDoc.title)}</p>
- <div className="linkMenu-item-buttons">
- {canExpand ? <div title="Show more" className="button" onPointerDown={() => this.toggleShowMore()}>
- <FontAwesomeIcon className="fa-icon" icon={this._showMore ? "chevron-up" : "chevron-down"} size="sm" /></div> : <></>}
- <div title="Edit link" className="button" onPointerDown={this.onEdit}><FontAwesomeIcon className="fa-icon" icon="edit" size="sm" /></div>
- <div title="Follow link" className="button" onPointerDown={this.onFollowLink}><FontAwesomeIcon className="fa-icon" icon="arrow-right" size="sm" /></div>
- </div>
- </div>
- {this._showMore ? this.renderMetadata() : <></>}
- </div>
-
- </div >
- );
- }
-} \ No newline at end of file
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index f0140d04b..29eef27a0 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -1,24 +1,29 @@
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action, observable } from "mobx";
import { observer } from "mobx-react";
-import { FieldResult, Doc } from "../../../new_fields/Doc";
+import { FieldResult, Doc, Field } from "../../../new_fields/Doc";
import { HtmlField } from "../../../new_fields/HtmlField";
-import { InkTool } from "../../../new_fields/InkField";
-import { Cast, NumCast } from "../../../new_fields/Types";
import { WebField } from "../../../new_fields/URLField";
-import { Utils } from "../../../Utils";
import { DocumentDecorations } from "../DocumentDecorations";
import { InkingControl } from "../InkingControl";
import { FieldView, FieldViewProps } from './FieldView';
-import { KeyValueBox } from "./KeyValueBox";
import "./WebBox.scss";
import React = require("react");
+import { InkTool } from "../../../new_fields/InkField";
+import { Cast, FieldValue, NumCast, StrCast } from "../../../new_fields/Types";
+import { Utils } from "../../../Utils";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { faStickyNote } from '@fortawesome/free-solid-svg-icons';
+import { observable, action, computed } from "mobx";
+import { listSpec } from "../../../new_fields/Schema";
+import { RefField } from "../../../new_fields/RefField";
+import { ObjectField } from "../../../new_fields/ObjectField";
+import { updateSourceFile } from "typescript";
+import { KeyValueBox } from "./KeyValueBox";
+import { setReactionScheduler } from "mobx/lib/internal";
+import { library } from "@fortawesome/fontawesome-svg-core";
import { SelectionManager } from "../../util/SelectionManager";
import { Docs } from "../../documents/Documents";
-import { faStickyNote } from "@fortawesome/free-solid-svg-icons";
-import { library } from "@fortawesome/fontawesome-svg-core";
-library.add(faStickyNote)
+library.add(faStickyNote);
@observer
export class WebBox extends React.Component<FieldViewProps> {
diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx
index 41fc49c2e..567bc6c97 100644
--- a/src/client/views/search/SearchItem.tsx
+++ b/src/client/views/search/SearchItem.tsx
@@ -63,6 +63,7 @@ export class SelectorContextMenu extends React.Component<SearchItemProps> {
runInAction(() => {
this._docs = docs.filter(doc => !Doc.AreProtosEqual(doc, CollectionDockingView.Instance.props.Document)).map(doc => ({ col: doc, target: this.props.doc }));
this._otherDocs = Array.from(map.entries()).filter(entry => !Doc.AreProtosEqual(entry[0], CollectionDockingView.Instance.props.Document)).map(([col, target]) => ({ col, target }));
+
});
}
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 6f1ef38d1..a703f1cef 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -608,6 +608,19 @@ export namespace Doc {
});
}
+ export function isBrushedHighlightedDegree(doc: Doc) {
+ if (Doc.IsHighlighted(doc)) {
+ return 3;
+ }
+ else {
+ return Doc.IsBrushedDegree(doc);
+ }
+ }
+
+ export class DocBrush {
+ @observable BrushedDoc: ObservableMap<Doc, boolean> = new ObservableMap();
+ }
+ const brushManager = new DocBrush();
export class DocData {
@observable _user_doc: Doc = undefined!;
@@ -617,18 +630,48 @@ export namespace Doc {
export function UserDoc(): Doc { return manager._user_doc; }
export function SetUserDoc(doc: Doc) { manager._user_doc = doc; }
export function IsBrushed(doc: Doc) {
- return manager.BrushedDoc.has(doc) || manager.BrushedDoc.has(Doc.GetDataDoc(doc));
+ return brushManager.BrushedDoc.has(doc) || brushManager.BrushedDoc.has(Doc.GetDataDoc(doc));
}
export function IsBrushedDegree(doc: Doc) {
- return manager.BrushedDoc.has(Doc.GetDataDoc(doc)) ? 2 : manager.BrushedDoc.has(doc) ? 1 : 0;
+ return brushManager.BrushedDoc.has(Doc.GetDataDoc(doc)) ? 2 : brushManager.BrushedDoc.has(doc) ? 1 : 0;
}
export function BrushDoc(doc: Doc) {
- manager.BrushedDoc.set(doc, true);
- manager.BrushedDoc.set(Doc.GetDataDoc(doc), true);
+ brushManager.BrushedDoc.set(doc, true);
+ brushManager.BrushedDoc.set(Doc.GetDataDoc(doc), true);
}
export function UnBrushDoc(doc: Doc) {
- manager.BrushedDoc.delete(doc);
- manager.BrushedDoc.delete(Doc.GetDataDoc(doc));
+ brushManager.BrushedDoc.delete(doc);
+ brushManager.BrushedDoc.delete(Doc.GetDataDoc(doc));
+ }
+
+ export class HighlightBrush {
+ @observable HighlightedDoc: Map<Doc, boolean> = new Map();
+ }
+ const highlightManager = new HighlightBrush();
+ export function IsHighlighted(doc: Doc) {
+ let IsHighlighted = highlightManager.HighlightedDoc.get(doc) || highlightManager.HighlightedDoc.get(Doc.GetDataDoc(doc));
+ return IsHighlighted;
+ }
+ export function HighlightDoc(doc: Doc) {
+ runInAction(() => {
+ highlightManager.HighlightedDoc.set(doc, true);
+ highlightManager.HighlightedDoc.set(Doc.GetDataDoc(doc), true);
+ });
+ }
+ export function UnHighlightDoc(doc: Doc) {
+ runInAction(() => {
+ highlightManager.HighlightedDoc.set(doc, false);
+ highlightManager.HighlightedDoc.set(Doc.GetDataDoc(doc), false);
+ });
+ }
+ export function UnhighlightAll() {
+ let mapEntries = highlightManager.HighlightedDoc.keys();
+ let docEntry: IteratorResult<Doc>;
+ while (!(docEntry = mapEntries.next()).done) {
+ let targetDoc = docEntry.value;
+ targetDoc && Doc.UnHighlightDoc(targetDoc);
+ }
+
}
export function UnBrushAllDocs() {
manager.BrushedDoc.clear();