aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorFawn <fangrui_tong@brown.edu>2019-06-19 22:27:21 -0400
committerFawn <fangrui_tong@brown.edu>2019-06-19 22:27:21 -0400
commitc5e401cb0a7fec2279ceecbc8d1429dcdd2f04b9 (patch)
treed7e3b77890c3c4f3be0dca1da9c4aae71ecaead7 /src
parentf362dbfc237536c6c4a8c6d088c3dc818080f7c2 (diff)
buttons on cut links functional except for when dragged from link menu
Diffstat (limited to 'src')
-rw-r--r--src/client/documents/Documents.ts47
-rw-r--r--src/client/util/DragManager.ts7
-rw-r--r--src/client/util/LinkManager.ts15
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss59
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx2
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkWithProxyView.tsx109
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx42
-rw-r--r--src/client/views/nodes/DocumentContentsView.tsx3
-rw-r--r--src/client/views/nodes/DocumentView.tsx14
-rw-r--r--src/client/views/nodes/FieldView.tsx6
-rw-r--r--src/client/views/nodes/LinkButtonBox.scss18
-rw-r--r--src/client/views/nodes/LinkButtonBox.tsx63
-rw-r--r--src/client/views/nodes/LinkMenu.tsx4
-rw-r--r--src/client/views/nodes/LinkMenuItem.scss (renamed from src/client/views/nodes/LinkBox.scss)0
-rw-r--r--src/client/views/nodes/LinkMenuItem.tsx (renamed from src/client/views/nodes/LinkBox.tsx)6
-rw-r--r--src/new_fields/LinkButtonField.ts35
16 files changed, 368 insertions, 62 deletions
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 79ba433c8..4d4fa2645 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -26,7 +26,7 @@ import { OmitKeys } from "../../Utils";
import { ImageField, VideoField, AudioField, PdfField, WebField } from "../../new_fields/URLField";
import { HtmlField } from "../../new_fields/HtmlField";
import { List } from "../../new_fields/List";
-import { Cast, NumCast } from "../../new_fields/Types";
+import { Cast, NumCast, StrCast } from "../../new_fields/Types";
import { IconField } from "../../new_fields/IconField";
import { listSpec } from "../../new_fields/Schema";
import { DocServer } from "../DocServer";
@@ -36,6 +36,10 @@ import { DateField } from "../../new_fields/DateField";
import { UndoManager } from "../util/UndoManager";
import { RouteStore } from "../../server/RouteStore";
import { LinkManager } from "../util/LinkManager";
+import { LinkButtonBox } from "../views/nodes/LinkButtonBox";
+import { LinkButtonField, LinkButtonData } from "../../new_fields/LinkButtonField";
+import { DocumentManager } from "../util/DocumentManager";
+import { Id } from "../../new_fields/FieldSymbols";
var requestImageSize = require('request-image-size');
var path = require('path');
@@ -102,14 +106,32 @@ export namespace DocUtils {
linkDocProto.context = targetContext;
- let proxy1 = Docs.TextDocument({ width: 300, height: 100, borderRounding: 0 });
- let proxy1Proto = Doc.GetProto(proxy1);
- let proxy2 = Docs.TextDocument({ width: 300, height: 100, borderRounding: 0 });
- let proxy2Proto = Doc.GetProto(proxy2);
+ let sourceViews = DocumentManager.Instance.getDocumentViews(source);
+ let targetViews = DocumentManager.Instance.getDocumentViews(target);
+ sourceViews.forEach(sv => {
+ targetViews.forEach(tv => {
- linkDocProto.proxy1 = proxy1; // src: 1 targ: 2
- linkDocProto.proxy2 = proxy2; // src: 2 targ: 1
+ // TODO: do only for when diff contexts
+ let proxy1 = Docs.LinkButtonDocument(
+ { sourceViewId: StrCast(sv.props.Document[Id]), targetViewId: StrCast(tv.props.Document[Id]) },
+ { width: 200, height: 100, borderRounding: 0 });
+ let proxy1Proto = Doc.GetProto(proxy1);
+ proxy1Proto.sourceViewId = StrCast(sv.props.Document[Id]);
+ proxy1Proto.targetViewId = StrCast(tv.props.Document[Id]);
+ proxy1Proto.isLinkButton = true;
+ let proxy2 = Docs.LinkButtonDocument(
+ { sourceViewId: StrCast(tv.props.Document[Id]), targetViewId: StrCast(sv.props.Document[Id]) },
+ { width: 200, height: 100, borderRounding: 0 });
+ let proxy2Proto = Doc.GetProto(proxy2);
+ proxy2Proto.sourceViewId = StrCast(tv.props.Document[Id]);
+ proxy2Proto.targetViewId = StrCast(sv.props.Document[Id]);
+ proxy2Proto.isLinkButton = true;
+
+ LinkManager.Instance.linkProxies.push(proxy1);
+ LinkManager.Instance.linkProxies.push(proxy2);
+ });
+ });
LinkManager.Instance.allLinks.push(linkDoc);
@@ -131,6 +153,7 @@ export namespace Docs {
let audioProto: Doc;
let pdfProto: Doc;
let iconProto: Doc;
+ let linkProto: Doc;
const textProtoId = "textProto";
const histoProtoId = "histoProto";
const pdfProtoId = "pdfProto";
@@ -141,6 +164,7 @@ export namespace Docs {
const videoProtoId = "videoProto";
const audioProtoId = "audioProto";
const iconProtoId = "iconProto";
+ const linkProtoId = "linkProto";
export function initProtos(): Promise<void> {
return DocServer.GetRefFields([textProtoId, histoProtoId, collProtoId, imageProtoId, webProtoId, kvpProtoId, videoProtoId, audioProtoId, pdfProtoId, iconProtoId]).then(fields => {
@@ -154,6 +178,7 @@ export namespace Docs {
audioProto = fields[audioProtoId] as Doc || CreateAudioPrototype();
pdfProto = fields[pdfProtoId] as Doc || CreatePdfPrototype();
iconProto = fields[iconProtoId] as Doc || CreateIconPrototype();
+ linkProto = fields[linkProtoId] as Doc || CreateLinkPrototype();
});
}
@@ -186,6 +211,11 @@ export namespace Docs {
{ x: 0, y: 0, width: Number(MINIMIZED_ICON_SIZE), height: Number(MINIMIZED_ICON_SIZE) });
return iconProto;
}
+ function CreateLinkPrototype(): Doc {
+ let linkProto = setupPrototypeOptions(linkProtoId, "LINK_PROTO", LinkButtonBox.LayoutString(),
+ { x: 0, y: 0, width: 300 });
+ return linkProto;
+ }
function CreateTextPrototype(): Doc {
let textProto = setupPrototypeOptions(textProtoId, "TEXT_PROTO", FormattedTextBox.LayoutString(),
{ x: 0, y: 0, width: 300, backgroundColor: "#f1efeb" });
@@ -272,6 +302,9 @@ export namespace Docs {
export function IconDocument(icon: string, options: DocumentOptions = {}) {
return CreateInstance(iconProto, new IconField(icon), options);
}
+ export function LinkButtonDocument(data: LinkButtonData, options: DocumentOptions = {}) {
+ return CreateInstance(linkProto, new LinkButtonField(data), options);
+ }
export function PdfDocument(url: string, options: DocumentOptions = {}) {
return CreateInstance(pdfProto, new PdfField(new URL(url)), options);
}
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 4787ac40f..be00778e7 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -7,6 +7,8 @@ import * as globalCssVariables from "../views/globalCssVariables.scss";
import { LinkManager } from "./LinkManager";
import { URLField } from "../../new_fields/URLField";
import { SelectionManager } from "./SelectionManager";
+import { Docs } from "../documents/Documents";
+import { DocumentManager } from "./DocumentManager";
export type dropActionType = "alias" | "copy" | undefined;
export function SetupDrag(_reference: React.RefObject<HTMLElement>, docFunc: () => Doc | Promise<Doc>, moveFunc?: DragManager.MoveFunction, dropAction?: dropActionType, options?: any, dontHideOnDrop?: boolean) {
@@ -223,6 +225,11 @@ export namespace DragManager {
StartDrag([ele], dragData, downX, downY, options);
}
+ export function StartLinkProxyDrag(ele: HTMLElement, dragData: DocumentDragData, downX: number, downY: number, options?: DragOptions) {
+ runInAction(() => StartDragFunctions.map(func => func()));
+ StartDrag([ele], dragData, downX, downY, options);
+ }
+
export let AbortDrag: () => void = emptyFunction;
function StartDrag(eles: HTMLElement[], dragData: { [id: string]: any }, downX: number, downY: number, options?: DragOptions, finishDrag?: (dropData: { [id: string]: any }) => void) {
diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index 23ba9d2e4..544f2edda 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -1,4 +1,4 @@
-import { observable } from "mobx";
+import { observable, action } from "mobx";
import { StrCast, Cast } from "../../new_fields/Types";
import { Doc, DocListCast } from "../../new_fields/Doc";
import { listSpec } from "../../new_fields/Schema";
@@ -32,6 +32,7 @@ export class LinkManager {
@observable public allLinks: Array<Doc> = []; // list of link docs
@observable public groupMetadataKeys: Map<string, Array<string>> = new Map();
// map of group type to list of its metadata keys; serves as a dictionary of groups to what kind of metadata it hodls
+ @observable public linkProxies: Array<Doc> = []; // list of linkbutton docs - used to visualize link when an anchors are not in the same context
// finds all links that contain the given anchor
public findAllRelatedLinks(anchor: Doc): Array<Doc> {
@@ -134,4 +135,16 @@ export class LinkManager {
}
}
+ @action
+ public addLinkProxy(proxy: Doc) {
+ LinkManager.Instance.linkProxies.push(proxy);
+ }
+
+ public findLinkProxy(sourceViewId: string, targetViewId: string): Doc | undefined {
+ let index = LinkManager.Instance.linkProxies.findIndex(p => {
+ return StrCast(p.sourceViewId) === sourceViewId && StrCast(p.targetViewId) === targetViewId;
+ });
+ return index > -1 ? LinkManager.Instance.linkProxies[index] : undefined;
+ }
+
} \ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss
index fc5212edd..d8d518147 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss
@@ -1,19 +1,50 @@
-.collectionfreeformlinkview-linkLine {
- stroke: black;
+// .collectionfreeformlinkview-linkLine {
+// stroke: black;
+// transform: translate(10000px,10000px);
+// opacity: 0.5;
+// pointer-events: all;
+// }
+// .collectionfreeformlinkview-linkCircle {
+// stroke: rgb(0,0,0);
+// opacity: 0.5;
+// transform: translate(10000px,10000px);
+// pointer-events: all;
+// cursor: pointer;
+// }
+// .collectionfreeformlinkview-linkText {
+// stroke: rgb(0,0,0);
+// opacity: 0.5;
+// transform: translate(10000px,10000px);
+// pointer-events: all;
+// }
+
+.linkview-ele {
transform: translate(10000px,10000px);
- opacity: 0.5;
pointer-events: all;
+
+ &.linkview-line {
+ stroke: black;
+ stroke-width: 2px;
+ opacity: 0.5;
+ }
}
-.collectionfreeformlinkview-linkCircle {
- stroke: rgb(0,0,0);
- opacity: 0.5;
- transform: translate(10000px,10000px);
- pointer-events: all;
+
+.linkview-button {
+ width: 200px;
+ height: 100px;
+ border-radius: 5px;
+ padding: 10px;
+ position: relative;
+ background-color: black;
cursor: pointer;
-}
-.collectionfreeformlinkview-linkText {
- stroke: rgb(0,0,0);
- opacity: 0.5;
- transform: translate(10000px,10000px);
- pointer-events: all;
+
+ p {
+ width: calc(100% - 20px);
+ color: white;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ }
+
}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
index f995a35e3..13b5dc575 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -57,7 +57,7 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
return (
<>
- <line className="collectionfreeformlinkview-linkLine"
+ <line className="linkview-line linkview-ele"
style={{ strokeWidth: `${2 * 1 / 2}` }}
x1={`${x1}`} y1={`${y1}`}
x2={`${x2}`} y2={`${y2}`} />
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkWithProxyView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkWithProxyView.tsx
index 81a00ba95..a4d122af2 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkWithProxyView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkWithProxyView.tsx
@@ -7,13 +7,17 @@ import React = require("react");
import v5 = require("uuid/v5");
import { DocumentView } from "../../nodes/DocumentView";
import { Docs } from "../../../documents/Documents";
-import { observable } from "mobx";
+import { observable, action } from "mobx";
+import { CollectionDockingView } from "../CollectionDockingView";
+import { dropActionType, DragManager } from "../../../util/DragManager";
+import { emptyFunction } from "../../../../Utils";
+import { DocumentManager } from "../../../util/DocumentManager";
export interface CollectionFreeFormLinkViewProps {
sourceView: DocumentView;
targetView: DocumentView;
proxyDoc: Doc;
- addDocTab: (document: Doc, where: string) => void;
+ // addDocTab: (document: Doc, where: string) => void;
}
@observer
@@ -21,37 +25,104 @@ export class CollectionFreeFormLinkWithProxyView extends React.Component<Collect
// @observable private _proxyX: number = NumCast(this.props.proxyDoc.x);
// @observable private _proxyY: number = NumCast(this.props.proxyDoc.y);
+ private _ref = React.createRef<HTMLDivElement>();
+ private _downX: number = 0;
+ private _downY: number = 0;
+ @observable _x: number = 0;
+ @observable _y: number = 0;
+ // @observable private _proxyDoc: Doc = Docs.TextDocument(); // used for positioning
+
+ @action
+ componentDidMount() {
+ let a2 = this.props.proxyDoc;
+ this._x = NumCast(a2.x) + (BoolCast(a2.isMinimized, false) ? 5 : NumCast(a2.width) / NumCast(a2.zoomBasis, 1) / 2);
+ this._y = NumCast(a2.y) + (BoolCast(a2.isMinimized, false) ? 5 : NumCast(a2.height) / NumCast(a2.zoomBasis, 1) / 2);
+ }
+
followButton = (e: React.PointerEvent): void => {
- // TODO: would be nicer to open docview in context
e.stopPropagation();
- console.log("follow");
- this.props.addDocTab(this.props.targetView.props.Document, "onRight");
+ let open = this.props.targetView.props.ContainingCollectionView ? this.props.targetView.props.ContainingCollectionView.props.Document : this.props.targetView.props.Document;
+ CollectionDockingView.Instance.AddRightSplit(open);
+ DocumentManager.Instance.jumpToDocument(this.props.targetView.props.Document, e.altKey);
+ }
+
+ @action
+ setPosition(x: number, y: number) {
+ this._x = x;
+ this._y = y;
+ }
+
+ startDragging(x: number, y: number) {
+ if (this._ref.current) {
+ let dragData = new DragManager.DocumentDragData([this.props.proxyDoc]);
+
+ DragManager.StartLinkProxyDrag(this._ref.current, dragData, x, y, {
+ handlers: {
+ dragComplete: action(() => {
+ let a2 = this.props.proxyDoc;
+ let offset = NumCast(a2.width) / NumCast(a2.zoomBasis, 1) / 2;
+ let x = NumCast(a2.x);// + NumCast(a2.width) / NumCast(a2.zoomBasis, 1) / 2;
+ let y = NumCast(a2.y);// + NumCast(a2.height) / NumCast(a2.zoomBasis, 1) / 2;
+ this.setPosition(x, y);
+
+ // this is a hack :'( theres prob a better way to make the input doc not render
+ let views = DocumentManager.Instance.getDocumentViews(this.props.proxyDoc);
+ views.forEach(dv => {
+ dv.props.removeDocument && dv.props.removeDocument(dv.props.Document);
+ });
+ }),
+ },
+ hideSource: true //?
+ });
+ }
+ }
+
+ onPointerDown = (e: React.PointerEvent): void => {
+ this._downX = e.clientX;
+ this._downY = e.clientY;
+
+ e.stopPropagation();
+ document.removeEventListener("pointermove", this.onPointerMove);
+ document.addEventListener("pointermove", this.onPointerMove);
+ document.removeEventListener("pointerup", this.onPointerUp);
+ document.addEventListener("pointerup", this.onPointerUp);
+ }
+
+ onPointerMove = (e: PointerEvent): void => {
+ if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3) {
+ document.removeEventListener("pointermove", this.onPointerMove);
+ document.removeEventListener("pointerup", this.onPointerUp);
+ this.startDragging(this._downX, this._downY);
+ }
+ e.stopPropagation();
+ e.preventDefault();
+ }
+ onPointerUp = (e: PointerEvent): void => {
+ document.removeEventListener("pointermove", this.onPointerMove);
+ document.removeEventListener("pointerup", this.onPointerUp);
}
render() {
let a1 = this.props.sourceView;
- let a2 = this.props.proxyDoc;
let x1 = NumCast(a1.Document.x) + (BoolCast(a1.Document.isMinimized, false) ? 5 : NumCast(a1.Document.width) / NumCast(a1.Document.zoomBasis, 1) / 2);
let y1 = NumCast(a1.Document.y) + (BoolCast(a1.Document.isMinimized, false) ? 5 : NumCast(a1.Document.height) / NumCast(a1.Document.zoomBasis, 1) / 2);
- let x2 = NumCast(a2.x) + (BoolCast(a2.isMinimized, false) ? 5 : NumCast(a2.width) / NumCast(a2.zoomBasis, 1) / 2);
- let y2 = NumCast(a2.y) + (BoolCast(a2.isMinimized, false) ? 5 : NumCast(a2.height) / NumCast(a2.zoomBasis, 1) / 2);
-
- // let containing = "";
- // if (this.props.targetView.props.ContainingCollectionView) {
- // containing = StrCast(this.props.targetView.props.ContainingCollectionView.props.Document.title);
- // }
+ let context = this.props.targetView.props.ContainingCollectionView ?
+ (" in the context of " + StrCast(this.props.targetView.props.ContainingCollectionView.props.Document.title)) : "";
+ let text = "link to " + StrCast(this.props.targetView.props.Document.title) + context;
- // let text = "link to " + StrCast(this.props.targetView.props.Document.title) + (containing === "" ? "" : (" in the context of " + containing));
return (
<>
- <line className="collectionfreeformlinkview-linkLine"
- style={{ strokeWidth: `${2 * 1 / 2}` }}
+ <line className="linkview-line linkview-ele"
+ // style={{ strokeWidth: `${2 * 1 / 2}` }}
x1={`${x1}`} y1={`${y1}`}
- x2={`${x2}`} y2={`${y2}`} />
- {/* <circle className="collectionfreeformlinkview-linkCircle" cx={x2} cy={y2} r={20} ></circle>
- <text textAnchor="middle" className="collectionfreeformlinkview-linkText" x={`${x2}`} y={`${y2}`}> {text}</text> */}
+ x2={`${this._x}`} y2={`${this._y}`} />
+ <foreignObject className="linkview-button-wrapper linkview-ele" width={200} height={100} x={this._x - 100} y={this._y - 50}>
+ <div className="linkview-button" onPointerDown={this.onPointerDown} onPointerUp={this.followButton} ref={this._ref}>
+ <p>{text}</p>
+ </div>
+ </foreignObject>
</>
);
}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
index eaef1f32a..bde68001b 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
@@ -1,4 +1,4 @@
-import { computed, IReactionDisposer, reaction } from "mobx";
+import { computed, IReactionDisposer, reaction, action } from "mobx";
import { observer } from "mobx-react";
import { Doc, DocListCast } from "../../../../new_fields/Doc";
import { Id } from "../../../../new_fields/FieldSymbols";
@@ -13,6 +13,8 @@ import { CollectionFreeFormLinkView } from "./CollectionFreeFormLinkView";
import React = require("react");
import { CollectionFreeFormLinkWithProxyView } from "./CollectionFreeFormLinkWithProxyView";
import { Docs } from "../../../documents/Documents";
+import { LinkButtonField } from "../../../../new_fields/LinkButtonField";
+import { LinkManager } from "../../../util/LinkManager";
@observer
export class CollectionFreeFormLinksView extends React.Component<CollectionViewProps> {
@@ -195,18 +197,32 @@ export class CollectionFreeFormLinksView extends React.Component<CollectionViewP
if (sameContext) {
uniqueList.push(<CollectionFreeFormLinkView key={key} sourceView={u.sourceView} targetView={u.targetView} />);
} else {
- let proxyKey = Doc.AreProtosEqual(u.sourceView.Document, Cast(u.linkDoc.anchor1, Doc, new Doc)) ? "proxy1" : "proxy2";
- let proxy = Cast(u.linkDoc[proxyKey], Doc, new Doc);
-
- let context = u.targetView.props.ContainingCollectionView ? (" in the context of " + StrCast(u.targetView.props.ContainingCollectionView.props.Document.title)) : "";
- let text = proxyKey + " link to " + StrCast(u.targetView.props.Document.title) + context;
-
- let proxyProto = Doc.GetProto(proxy);
- proxyProto.data = text;
-
- this.props.addDocument(proxy, false);
- uniqueList.push(<CollectionFreeFormLinkWithProxyView key={key} sourceView={u.sourceView} targetView={u.targetView}
- proxyDoc={proxy} addDocTab={this.props.addDocTab} />);
+ let proxy = LinkManager.Instance.findLinkProxy(StrCast(u.sourceView.props.Document[Id]), StrCast(u.targetView.props.Document[Id]));
+ if (!proxy) {
+ proxy = Docs.LinkButtonDocument(
+ { sourceViewId: StrCast(u.sourceView.props.Document[Id]), targetViewId: StrCast(u.targetView.props.Document[Id]) },
+ { width: 200, height: 100, borderRounding: 0 });
+ let proxy1Proto = Doc.GetProto(proxy);
+ proxy1Proto.sourceViewId = StrCast(u.sourceView.props.Document[Id]);
+ proxy1Proto.targetViewId = StrCast(u.targetView.props.Document[Id]);
+ proxy1Proto.isLinkButton = true;
+
+ // LinkManager.Instance.linkProxies.push(proxy);
+ LinkManager.Instance.addLinkProxy(proxy);
+ }
+ uniqueList.push(<CollectionFreeFormLinkWithProxyView key={key} sourceView={u.sourceView} targetView={u.targetView} proxyDoc={proxy} />);
+
+ // let proxy = LinkManager.Instance.findLinkProxy(StrCast(u.sourceView.props.Document[Id]), StrCast(u.targetView.props.Document[Id]));
+ // if (proxy) {
+ // this.props.addDocument(proxy, false);
+ // uniqueList.push(<CollectionFreeFormLinkWithProxyView key={key} sourceView={u.sourceView} targetView={u.targetView} />);
+ // }
+ // let proxyKey = Doc.AreProtosEqual(u.sourceView.Document, Cast(u.linkDoc.anchor1, Doc, new Doc)) ? "proxy1" : "proxy2";
+ // let proxy = Cast(u.linkDoc[proxyKey], Doc, new Doc);
+ // this.props.addDocument(proxy, false);
+
+ // uniqueList.push(<CollectionFreeFormLinkWithProxyView key={key} sourceView={u.sourceView} targetView={u.targetView}
+ // proxyDoc={proxy} addDocTab={this.props.addDocTab} />);
}
}
});
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 02396c3af..940b00a90 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -12,6 +12,7 @@ import "./DocumentView.scss";
import { FormattedTextBox } from "./FormattedTextBox";
import { ImageBox } from "./ImageBox";
import { IconBox } from "./IconBox";
+import { LinkButtonBox } from "./LinkButtonBox";
import { KeyValueBox } from "./KeyValueBox";
import { PDFBox } from "./PDFBox";
import { VideoBox } from "./VideoBox";
@@ -103,7 +104,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
render() {
if (!this.layout && (this.props.layoutKey !== "overlayLayout" || !this.templates.length)) return (null);
return <ObserverJsxParser
- components={{ FormattedTextBox, ImageBox, IconBox, FieldView, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox, HistogramBox }}
+ components={{ FormattedTextBox, ImageBox, IconBox, FieldView, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox, HistogramBox, LinkButtonBox }}
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 e98392a18..c424384a4 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -462,6 +462,17 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
var scaling = this.props.ContentScaling();
var nativeHeight = this.nativeHeight > 0 ? `${this.nativeHeight}px` : "100%";
var nativeWidth = this.nativeWidth > 0 ? `${this.nativeWidth}px` : "100%";
+
+ // // for linkbutton docs
+ // let isLinkButton = BoolCast(this.props.Document.isLinkButton);
+ // let activeDvs = DocumentManager.Instance.DocumentViews.filter(dv => dv.isSelected() || BoolCast(dv.props.Document.libraryBrush, false));
+ // let display = isLinkButton ? activeDvs.reduce((found, dv) => {
+ // let matchSv = this.props.Document.sourceViewId === StrCast(dv.props.Document[Id]);
+ // let matchTv = this.props.Document.targetViewId === StrCast(dv.props.Document[Id]);
+ // let match = matchSv || matchTv;
+ // return match || found;
+ // }, false) : true;
+
return (
<div className={`documentView-node${this.props.isTopMost ? "-topmost" : ""}`}
ref={this._mainCont}
@@ -476,7 +487,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
background: this.Document.backgroundColor || "",
width: nativeWidth,
height: nativeHeight,
- transform: `scale(${scaling}, ${scaling})`
+ transform: `scale(${scaling}, ${scaling})`,
+ // display: display ? "block" : "none"
}}
onDrop={this.onDrop} onContextMenu={this.onContextMenu} onPointerDown={this.onPointerDown} onClick={this.onClick}
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 5a83de8e3..6fecb34d7 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -18,6 +18,8 @@ import { FormattedTextBox } from "./FormattedTextBox";
import { IconBox } from "./IconBox";
import { ImageBox } from "./ImageBox";
import { VideoBox } from "./VideoBox";
+import { LinkButtonBox } from "./LinkButtonBox";
+import { LinkButtonField } from "../../../new_fields/LinkButtonField";
//
@@ -49,6 +51,7 @@ export interface FieldViewProps {
@observer
export class FieldView extends React.Component<FieldViewProps> {
public static LayoutString(fieldType: { name: string }, fieldStr: string = "data") {
+ console.log("LAYOUT STRING", fieldType.name, fieldStr);
return `<${fieldType.name} {...props} fieldKey={"${fieldStr}"} />`;
}
@@ -74,6 +77,9 @@ export class FieldView extends React.Component<FieldViewProps> {
else if (field instanceof IconField) {
return <IconBox {...this.props} />;
}
+ else if (field instanceof LinkButtonField) {
+ return <LinkButtonBox {...this.props} />;
+ }
else if (field instanceof VideoField) {
return <VideoBox {...this.props} />;
}
diff --git a/src/client/views/nodes/LinkButtonBox.scss b/src/client/views/nodes/LinkButtonBox.scss
new file mode 100644
index 000000000..24bfd2c9f
--- /dev/null
+++ b/src/client/views/nodes/LinkButtonBox.scss
@@ -0,0 +1,18 @@
+.linkBox-cont {
+ width: 200px;
+ height: 100px;
+ background-color: black;
+ text-align: center;
+ color: white;
+ padding: 10px;
+ border-radius: 5px;
+ position: relative;
+
+ .linkBox-cont-wrapper {
+ width: calc(100% - 20px);
+ position: absolute;
+ left: 50%;
+ top: 50%;
+ transform: translate(-50%, -50%);
+ }
+} \ No newline at end of file
diff --git a/src/client/views/nodes/LinkButtonBox.tsx b/src/client/views/nodes/LinkButtonBox.tsx
new file mode 100644
index 000000000..8a7c1ed8b
--- /dev/null
+++ b/src/client/views/nodes/LinkButtonBox.tsx
@@ -0,0 +1,63 @@
+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 { computed, observable, runInAction } from "mobx";
+import { observer } from "mobx-react";
+import { FieldView, FieldViewProps } from './FieldView';
+import "./LinkButtonBox.scss";
+import { DocumentView } from "./DocumentView";
+import { Doc } from "../../../new_fields/Doc";
+import { LinkButtonField } from "../../../new_fields/LinkButtonField";
+import { Cast, StrCast, BoolCast } from "../../../new_fields/Types";
+import { CollectionDockingView } from "../collections/CollectionDockingView";
+import { DocumentManager } from "../../util/DocumentManager";
+import { Id } from "../../../new_fields/FieldSymbols";
+
+library.add(faCaretUp);
+library.add(faObjectGroup);
+library.add(faStickyNote);
+library.add(faFilePdf);
+library.add(faFilm);
+
+@observer
+export class LinkButtonBox extends React.Component<FieldViewProps> {
+ public static LayoutString() { return FieldView.LayoutString(LinkButtonBox); }
+
+ followLink = (): void => {
+ console.log("follow link???");
+ let field = Cast(this.props.Document[this.props.fieldKey], LinkButtonField, new LinkButtonField({ sourceViewId: "-1", targetViewId: "-1" }));
+ let targetView = DocumentManager.Instance.getDocumentViewById(field.data.targetViewId);
+ if (targetView && targetView.props.ContainingCollectionView) {
+ CollectionDockingView.Instance.AddRightSplit(targetView.props.ContainingCollectionView.props.Document);
+ }
+ }
+
+ render() {
+
+ let field = Cast(this.props.Document[this.props.fieldKey], LinkButtonField, new LinkButtonField({ sourceViewId: "-1", targetViewId: "-1" }));
+ let targetView = DocumentManager.Instance.getDocumentViewById(field.data.targetViewId);
+
+ let text = "Could not find link";
+ if (targetView) {
+ let context = targetView.props.ContainingCollectionView ? (" in the context of " + StrCast(targetView.props.ContainingCollectionView.props.Document.title)) : "";
+ text = "Link to " + StrCast(targetView.props.Document.title) + context;
+ }
+
+ let activeDvs = DocumentManager.Instance.DocumentViews.filter(dv => dv.isSelected() || BoolCast(dv.props.Document.libraryBrush, false));
+ let display = activeDvs.reduce((found, dv) => {
+ let matchSv = field.data.sourceViewId === StrCast(dv.props.Document[Id]);
+ let matchTv = field.data.targetViewId === StrCast(dv.props.Document[Id]);
+ let match = matchSv || matchTv;
+ return match || found;
+ }, false);
+
+ return (
+ <div className="linkBox-cont" style={{ display: display ? "block" : "none" }}>
+ <div className="linkBox-cont-wrapper">
+ <p>{text}</p>
+ </div>
+ </div >
+ );
+ }
+} \ No newline at end of file
diff --git a/src/client/views/nodes/LinkMenu.tsx b/src/client/views/nodes/LinkMenu.tsx
index aef56df67..7e4c15312 100644
--- a/src/client/views/nodes/LinkMenu.tsx
+++ b/src/client/views/nodes/LinkMenu.tsx
@@ -1,7 +1,7 @@
import { action, observable } from "mobx";
import { observer } from "mobx-react";
import { DocumentView } from "./DocumentView";
-import { LinkBox } from "./LinkBox";
+import { LinkMenuItem } from "./LinkMenuItem";
import { LinkEditor } from "./LinkEditor";
import './LinkMenu.scss';
import React = require("react");
@@ -23,7 +23,7 @@ export class LinkMenu extends React.Component<Props> {
let source = this.props.docView.Document;
return group.map(linkDoc => {
let destination = LinkManager.Instance.findOppositeAnchor(linkDoc, source);
- return <LinkBox key={destination[Id] + source[Id]} groupType={groupType} linkDoc={linkDoc} sourceDoc={source} destinationDoc={destination} showEditor={action(() => this._editingLink = linkDoc)} />;
+ return <LinkMenuItem key={destination[Id] + source[Id]} groupType={groupType} linkDoc={linkDoc} sourceDoc={source} destinationDoc={destination} showEditor={action(() => this._editingLink = linkDoc)} />;
});
}
diff --git a/src/client/views/nodes/LinkBox.scss b/src/client/views/nodes/LinkMenuItem.scss
index 77462f611..77462f611 100644
--- a/src/client/views/nodes/LinkBox.scss
+++ b/src/client/views/nodes/LinkMenuItem.scss
diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkMenuItem.tsx
index 8d07547ed..c68365584 100644
--- a/src/client/views/nodes/LinkBox.tsx
+++ b/src/client/views/nodes/LinkMenuItem.tsx
@@ -4,7 +4,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { observer } from "mobx-react";
import { DocumentManager } from "../../util/DocumentManager";
import { undoBatch } from "../../util/UndoManager";
-import './LinkBox.scss';
+import './LinkMenuItem.scss';
import React = require("react");
import { Doc } from '../../../new_fields/Doc';
import { StrCast, Cast } from '../../../new_fields/Types';
@@ -15,7 +15,7 @@ import { SelectionManager } from '../../util/SelectionManager';
library.add(faEye, faEdit, faTimes, faArrowRight, faChevronDown, faChevronUp);
-interface Props {
+interface LinkMenuItemProps {
groupType: string;
linkDoc: Doc;
sourceDoc: Doc;
@@ -24,7 +24,7 @@ interface Props {
}
@observer
-export class LinkBox extends React.Component<Props> {
+export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
private _drag = React.createRef<HTMLDivElement>();
@observable private _showMore: boolean = false;
@action toggleShowMore() { this._showMore = !this._showMore; }
diff --git a/src/new_fields/LinkButtonField.ts b/src/new_fields/LinkButtonField.ts
new file mode 100644
index 000000000..92e1ed922
--- /dev/null
+++ b/src/new_fields/LinkButtonField.ts
@@ -0,0 +1,35 @@
+import { Deserializable } from "../client/util/SerializationHelper";
+import { serializable, primitive, createSimpleSchema, object } from "serializr";
+import { ObjectField } from "./ObjectField";
+import { Copy, ToScriptString } from "./FieldSymbols";
+import { Doc } from "./Doc";
+import { DocumentView } from "../client/views/nodes/DocumentView";
+
+export type LinkButtonData = {
+ sourceViewId: string,
+ targetViewId: string
+};
+
+const LinkButtonSchema = createSimpleSchema({
+ sourceViewId: true,
+ targetViewId: true
+});
+
+@Deserializable("linkButton")
+export class LinkButtonField extends ObjectField {
+ @serializable(object(LinkButtonSchema))
+ readonly data: LinkButtonData;
+
+ constructor(data: LinkButtonData) {
+ super();
+ this.data = data;
+ }
+
+ [Copy]() {
+ return new LinkButtonField(this.data);
+ }
+
+ [ToScriptString]() {
+ return "invalid";
+ }
+}