aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/client/documents/Documents.ts47
-rw-r--r--src/client/util/DocumentManager.ts56
-rw-r--r--src/client/util/DragManager.ts87
-rw-r--r--src/client/util/LinkManager.ts238
-rw-r--r--src/client/util/RichTextSchema.tsx81
-rw-r--r--src/client/util/TooltipTextMenu.scss30
-rw-r--r--src/client/util/TooltipTextMenu.tsx150
-rw-r--r--src/client/views/DocumentDecorations.tsx76
-rw-r--r--src/client/views/InkingCanvas.tsx7
-rw-r--r--src/client/views/InkingControl.tsx3
-rw-r--r--src/client/views/Main.scss17
-rw-r--r--src/client/views/MainOverlayTextBox.scss7
-rw-r--r--src/client/views/MainOverlayTextBox.tsx32
-rw-r--r--src/client/views/MainView.tsx20
-rw-r--r--src/client/views/Templates.tsx14
-rw-r--r--src/client/views/_nodeModuleOverrides.scss1
-rw-r--r--src/client/views/collections/CollectionBaseView.tsx89
-rw-r--r--src/client/views/collections/CollectionDockingView.tsx131
-rw-r--r--src/client/views/collections/CollectionSchemaView.tsx107
-rw-r--r--src/client/views/collections/CollectionStackingView.scss7
-rw-r--r--src/client/views/collections/CollectionStackingView.tsx43
-rw-r--r--src/client/views/collections/CollectionSubView.tsx13
-rw-r--r--src/client/views/collections/CollectionTreeView.scss13
-rw-r--r--src/client/views/collections/CollectionTreeView.tsx193
-rw-r--r--src/client/views/collections/CollectionView.tsx16
-rw-r--r--src/client/views/collections/ParentDocumentSelector.tsx4
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss1
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx22
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.scss2
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx1
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx39
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx5
-rw-r--r--src/client/views/nodes/DocumentContentsView.tsx13
-rw-r--r--src/client/views/nodes/DocumentView.tsx110
-rw-r--r--src/client/views/nodes/FieldView.tsx15
-rw-r--r--src/client/views/nodes/FormattedTextBox.scss2
-rw-r--r--src/client/views/nodes/FormattedTextBox.tsx53
-rw-r--r--src/client/views/nodes/ImageBox.tsx43
-rw-r--r--src/client/views/nodes/KeyValuePair.tsx4
-rw-r--r--src/client/views/nodes/LinkBox.scss66
-rw-r--r--src/client/views/nodes/LinkBox.tsx86
-rw-r--r--src/client/views/nodes/LinkEditor.scss149
-rw-r--r--src/client/views/nodes/LinkEditor.tsx342
-rw-r--r--src/client/views/nodes/LinkMenu.scss144
-rw-r--r--src/client/views/nodes/LinkMenu.tsx52
-rw-r--r--src/client/views/nodes/LinkMenuGroup.tsx92
-rw-r--r--src/client/views/nodes/LinkMenuItem.tsx111
-rw-r--r--src/client/views/nodes/PDFBox.scss10
-rw-r--r--src/client/views/nodes/PDFBox.tsx28
-rw-r--r--src/client/views/pdf/PDFMenu.tsx2
-rw-r--r--src/client/views/pdf/PDFViewer.scss9
-rw-r--r--src/client/views/pdf/PDFViewer.tsx20
-rw-r--r--src/client/views/pdf/Page.tsx2
-rw-r--r--src/client/views/presentationview/PresentationElement.tsx10
-rw-r--r--src/client/views/search/Pager.tsx20
-rw-r--r--src/client/views/search/SearchBox.tsx2
-rw-r--r--src/client/views/search/SearchItem.tsx4
-rw-r--r--src/client/views/search/ToggleBar.tsx2
-rw-r--r--src/new_fields/Doc.ts35
-rw-r--r--src/scraping/buxton/scraper.py331
-rw-r--r--src/scraping/buxton/source/Bill_Notes_Bill_Notes_CyKey.docxbin0 -> 1675500 bytes
-rw-r--r--src/scraping/buxton/source/Bill_Notes_Braun_T3.docxbin0 -> 1671968 bytes
-rw-r--r--src/scraping/buxton/source/Bill_Notes_CasioC801.docxbin0 -> 574664 bytes
-rw-r--r--src/scraping/buxton/source/Bill_Notes_Casio_Mini.docxbin0 -> 581069 bytes
-rw-r--r--src/scraping/buxton/source/Bill_Notes_FingerWorks_Prototype.docxbin0 -> 585090 bytes
-rw-r--r--src/scraping/buxton/source/Bill_Notes_Fingerworks_TouchStream.docxbin0 -> 1722555 bytes
-rw-r--r--src/scraping/buxton/source/Bill_Notes_FrogPad.docxbin0 -> 840173 bytes
-rw-r--r--src/scraping/buxton/source/Bill_Notes_Gavilan_SC.docxbin0 -> 1695290 bytes
-rw-r--r--src/scraping/buxton/source/Bill_Notes_Grandjean_Stenotype.docxbin0 -> 2094142 bytes
-rw-r--r--src/scraping/buxton/source/Bill_Notes_Matias.docxbin0 -> 590407 bytes
-rw-r--r--src/scraping/buxton/source/Bill_Notes_MousePen.docxbin0 -> 505322 bytes
-rw-r--r--src/scraping/buxton/source/Bill_Notes_NewO.docxbin0 -> 2264571 bytes
-rw-r--r--src/scraping/buxton/source/Bill_Notes_OLPC.docxbin0 -> 6883659 bytes
-rw-r--r--src/scraping/buxton/source/Bill_Notes_PARCkbd.docxbin0 -> 631959 bytes
-rw-r--r--src/scraping/buxton/source/Bill_Notes_Philco_Mystery_Control.docxbin0 -> 1994439 bytes
-rw-r--r--src/scraping/buxton/source/Bill_Notes_TASA_Kbd.docxbin0 -> 461199 bytes
-rw-r--r--src/scraping/buxton/source/Bill_Notes_The_Tap.docxbin0 -> 711321 bytes
77 files changed, 2438 insertions, 871 deletions
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 6def4d135..6dc98dbbc 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -25,7 +25,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";
@@ -34,6 +34,8 @@ import { dropActionType } from "../util/DragManager";
import { DateField } from "../../new_fields/DateField";
import { UndoManager } from "../util/UndoManager";
import { RouteStore } from "../../server/RouteStore";
+import { LinkManager } from "../util/LinkManager";
+import { DocumentManager } from "../util/DocumentManager";
var requestImageSize = require('../util/request-image-size');
var path = require('path');
@@ -84,28 +86,30 @@ const delegateKeys = ["x", "y", "width", "height", "panX", "panY"];
export namespace DocUtils {
export function MakeLink(source: Doc, target: Doc, targetContext?: Doc, title: string = "", description: string = "", tags: string = "Default") {
- let protoSrc = source.proto ? source.proto : source;
- let protoTarg = target.proto ? target.proto : target;
+ if (LinkManager.Instance.doesLinkExist(source, target)) return;
+ let sv = DocumentManager.Instance.getDocumentView(source);
+ if (sv && sv.props.ContainingCollectionView && sv.props.ContainingCollectionView.props.Document === target) return;
+ if (target === CurrentUserUtils.UserDocument) return;
+
UndoManager.RunInBatch(() => {
- let linkDoc = Docs.TextDocument({ width: 100, height: 30, borderRounding: -1});
+ let linkDoc = Docs.TextDocument({ width: 100, height: 30, borderRounding: -1 });
linkDoc.type = DocTypes.LINK;
let linkDocProto = Doc.GetProto(linkDoc);
- linkDocProto.title = title === "" ? source.title + " to " + target.title : title;
+
+ linkDocProto.context = targetContext;
+ linkDocProto.title = title; //=== "" ? source.title + " to " + target.title : title;
linkDocProto.linkDescription = description;
linkDocProto.linkTags = tags;
- linkDocProto.linkedTo = target;
- linkDocProto.linkedFrom = source;
- linkDocProto.linkedToPage = target.curPage;
- linkDocProto.linkedFromPage = source.curPage;
- linkDocProto.linkedToContext = targetContext;
+ linkDocProto.anchor1 = source;
+ linkDocProto.anchor1Page = source.curPage;
+ linkDocProto.anchor1Groups = new List<Doc>([]);
+ linkDocProto.anchor2 = target;
+ linkDocProto.anchor2Page = target.curPage;
+ linkDocProto.anchor2Groups = new List<Doc>([]);
+
+ LinkManager.Instance.addLink(linkDoc);
- let linkedFrom = Cast(protoTarg.linkedFromDocs, listSpec(Doc));
- let linkedTo = Cast(protoSrc.linkedToDocs, listSpec(Doc));
- !linkedFrom && (protoTarg.linkedFromDocs = linkedFrom = new List<Doc>());
- !linkedTo && (protoSrc.linkedToDocs = linkedTo = new List<Doc>());
- linkedFrom.push(linkDoc);
- linkedTo.push(linkDoc);
return linkDoc;
}, "make link");
}
@@ -122,6 +126,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";
@@ -132,6 +137,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 => {
@@ -263,6 +269,7 @@ export namespace Docs {
export function IconDocument(icon: string, options: DocumentOptions = {}) {
return CreateInstance(iconProto, new IconField(icon), options);
}
+
export function PdfDocument(url: string, options: DocumentOptions = {}) {
return CreateInstance(pdfProto, new PdfField(new URL(url)), options);
}
@@ -359,7 +366,7 @@ export namespace Docs {
{layout}
</div>
<div style="height:(100% + 25px); width:100%; position:absolute">
- <FormattedTextBox doc={Document} DocumentViewForField={DocumentView} bindings={bindings} fieldKey={"caption"} isSelected={isSelected} select={select} selectOnLoad={SelectOnLoad} isTopMost={isTopMost}/>
+ <FormattedTextBox doc={Document} DocumentViewForField={DocumentView} bindings={bindings} fieldKey={"caption"} isSelected={isSelected} select={select} selectOnLoad={SelectOnLoad} renderDepth={renderDepth}/>
</div>
</div>
`);
@@ -371,14 +378,14 @@ export namespace Docs {
{layout}
</div>
<div style="height:25px; width:100%; position:absolute">
- <FormattedTextBox doc={Document} DocumentViewForField={DocumentView} bindings={bindings} fieldKey={"caption"} isSelected={isSelected} select={select} selectOnLoad={SelectOnLoad} isTopMost={isTopMost}/>
+ <FormattedTextBox doc={Document} DocumentViewForField={DocumentView} bindings={bindings} fieldKey={"caption"} isSelected={isSelected} select={select} selectOnLoad={SelectOnLoad} renderDepth={renderDepth}/>
</div>
</div>
`);
}
/*
-
+
this template requires an additional style setting on the collectionView-cont to make the layout relative
.collectionView-cont {
@@ -394,7 +401,7 @@ export namespace Docs {
{layout}
</div>
<div style="height:15%; width:100%; position:absolute">
- <FormattedTextBox doc={Document} DocumentViewForField={DocumentView} bindings={bindings} fieldKey={"caption"} isSelected={isSelected} select={select} selectOnLoad={SelectOnLoad} isTopMost={isTopMost}/>
+ <FormattedTextBox doc={Document} DocumentViewForField={DocumentView} bindings={bindings} fieldKey={"caption"} isSelected={isSelected} select={select} selectOnLoad={SelectOnLoad} renderDepth={renderDepth}/>
</div>
</div>
`);
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index bc9724b98..d0014dbf3 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -1,7 +1,7 @@
import { computed, observable, action } from 'mobx';
import { DocumentView } from '../views/nodes/DocumentView';
import { Doc, DocListCast, Opt } from '../../new_fields/Doc';
-import { FieldValue, Cast, NumCast, BoolCast } from '../../new_fields/Types';
+import { FieldValue, Cast, NumCast, BoolCast, StrCast } from '../../new_fields/Types';
import { listSpec } from '../../new_fields/Schema';
import { undoBatch } from './UndoManager';
import { CollectionDockingView } from '../views/collections/CollectionDockingView';
@@ -9,6 +9,8 @@ import { CollectionView } from '../views/collections/CollectionView';
import { CollectionPDFView } from '../views/collections/CollectionPDFView';
import { CollectionVideoView } from '../views/collections/CollectionVideoView';
import { Id } from '../../new_fields/FieldSymbols';
+import { LinkManager } from './LinkManager';
+import { CurrentUserUtils } from '../../server/authentication/models/current_user_utils';
export class DocumentManager {
@@ -90,13 +92,11 @@ export class DocumentManager {
//gets document view that is in a freeform canvas collection
DocumentManager.Instance.DocumentViews.map(view => {
let doc = view.props.Document;
- // if (view.props.ContainingCollectionView instanceof CollectionFreeFormView) {
if (doc === toFind) {
toReturn.push(view);
} else {
- let docSrc = FieldValue(doc.proto);
- if (docSrc && Object.is(docSrc, toFind)) {
+ if (Doc.AreProtosEqual(doc, toFind)) {
toReturn.push(view);
}
}
@@ -107,37 +107,25 @@ export class DocumentManager {
@computed
public get LinkedDocumentViews() {
- return DocumentManager.Instance.DocumentViews.filter(dv => dv.isSelected() || BoolCast(dv.props.Document.libraryBrush, false)).reduce((pairs, dv) => {
- let linksList = DocListCast(dv.props.Document.linkedToDocs);
- if (linksList && linksList.length) {
- pairs.push(...linksList.reduce((pairs, link) => {
- if (link) {
- let linkToDoc = FieldValue(Cast(link.linkedTo, Doc));
- if (linkToDoc) {
- DocumentManager.Instance.getDocumentViews(linkToDoc).map(docView1 =>
- pairs.push({ a: dv, b: docView1, l: link }));
- }
- }
- return pairs;
- }, [] as { a: DocumentView, b: DocumentView, l: Doc }[]));
- }
- linksList = DocListCast(dv.props.Document.linkedFromDocs);
- if (linksList && linksList.length) {
- pairs.push(...linksList.reduce((pairs, link) => {
- if (link) {
- let linkFromDoc = FieldValue(Cast(link.linkedFrom, Doc));
- if (linkFromDoc) {
- DocumentManager.Instance.getDocumentViews(linkFromDoc).map(docView1 =>
- pairs.push({ a: dv, b: docView1, l: link }));
- }
- }
- return pairs;
- }, pairs));
- }
+ let pairs = DocumentManager.Instance.DocumentViews.filter(dv => dv.isSelected() || BoolCast(dv.props.Document.libraryBrush, false)).reduce((pairs, dv) => {
+ let linksList = LinkManager.Instance.getAllRelatedLinks(dv.props.Document);
+ pairs.push(...linksList.reduce((pairs, link) => {
+ if (link) {
+ let linkToDoc = LinkManager.Instance.getOppositeAnchor(link, dv.props.Document);
+ DocumentManager.Instance.getDocumentViews(linkToDoc).map(docView1 => {
+ pairs.push({ a: dv, b: docView1, l: link });
+ });
+ }
+ return pairs;
+ }, [] as { a: DocumentView, b: DocumentView, l: Doc }[]));
+ // }
return pairs;
}, [] as { a: DocumentView, b: DocumentView, l: Doc }[]);
+
+ return pairs;
}
+
@undoBatch
public jumpToDocument = async (docDelegate: Doc, willZoom: boolean, forceDockFunc: boolean = false, dockFunc?: (doc: Doc) => void, linkPage?: number, docContext?: Doc): Promise<void> => {
let doc = Doc.GetProto(docDelegate);
@@ -162,7 +150,7 @@ export class DocumentManager {
docContext.panTransformType = "Ease";
targetContextView.props.focus(docDelegate, willZoom);
} else {
- (dockFunc || CollectionDockingView.Instance.AddRightSplit)(docContext);
+ (dockFunc || CollectionDockingView.Instance.AddRightSplit)(docContext, docContext);
setTimeout(() => {
this.jumpToDocument(docDelegate, willZoom, forceDockFunc, dockFunc, linkPage);
}, 10);
@@ -171,7 +159,7 @@ export class DocumentManager {
const actualDoc = Doc.MakeAlias(docDelegate);
actualDoc.libraryBrush = true;
if (linkPage !== undefined) actualDoc.curPage = linkPage;
- (dockFunc || CollectionDockingView.Instance.AddRightSplit)(actualDoc);
+ (dockFunc || CollectionDockingView.Instance.AddRightSplit)(actualDoc, actualDoc);
}
} else {
let contextView: DocumentView | null;
@@ -180,7 +168,7 @@ export class DocumentManager {
contextDoc.panTransformType = "Ease";
contextView.props.focus(docDelegate, willZoom);
} else {
- (dockFunc || CollectionDockingView.Instance.AddRightSplit)(contextDoc);
+ (dockFunc || CollectionDockingView.Instance.AddRightSplit)(contextDoc, contextDoc);
setTimeout(() => {
this.jumpToDocument(docDelegate, willZoom, forceDockFunc, dockFunc, linkPage);
}, 10);
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index b707dbe57..a6bba3656 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -1,11 +1,15 @@
import { action, runInAction, observable } from "mobx";
import { Doc, DocListCastAsync } from "../../new_fields/Doc";
-import { Cast } from "../../new_fields/Types";
+import { Cast, StrCast } from "../../new_fields/Types";
import { emptyFunction } from "../../Utils";
import { CollectionDockingView } from "../views/collections/CollectionDockingView";
import * as globalCssVariables from "../views/globalCssVariables.scss";
+import { LinkManager } from "./LinkManager";
import { URLField } from "../../new_fields/URLField";
import { SelectionManager } from "./SelectionManager";
+import { Docs, DocUtils } from "../documents/Documents";
+import { DocumentManager } from "./DocumentManager";
+import { Id } from "../../new_fields/FieldSymbols";
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) {
@@ -15,7 +19,8 @@ export function SetupDrag(_reference: React.RefObject<HTMLElement>, docFunc: ()
document.removeEventListener("pointermove", onRowMove);
document.removeEventListener('pointerup', onRowUp);
- var dragData = new DragManager.DocumentDragData([await docFunc()]);
+ let doc = await docFunc();
+ var dragData = new DragManager.DocumentDragData([doc], [doc]);
dragData.dropAction = dropAction;
dragData.moveDocument = moveFunc;
dragData.options = options;
@@ -27,40 +32,55 @@ export function SetupDrag(_reference: React.RefObject<HTMLElement>, docFunc: ()
document.removeEventListener('pointerup', onRowUp);
};
let onItemDown = async (e: React.PointerEvent) => {
- // if (this.props.isSelected() || this.props.isTopMost) {
if (e.button === 0) {
e.stopPropagation();
if (e.shiftKey && CollectionDockingView.Instance) {
- CollectionDockingView.Instance.StartOtherDrag([await docFunc()], e);
+ CollectionDockingView.Instance.StartOtherDrag(e, [await docFunc()]);
} else {
document.addEventListener("pointermove", onRowMove);
document.addEventListener("pointerup", onRowUp);
}
}
- //}
};
return onItemDown;
}
+export async function DragLinkAsDocument(dragEle: HTMLElement, x: number, y: number, linkDoc: Doc, sourceDoc: Doc) {
+ let draggeddoc = LinkManager.Instance.getOppositeAnchor(linkDoc, sourceDoc);
+
+ let moddrag = await Cast(draggeddoc.annotationOn, Doc);
+ let dragdocs = moddrag ? [moddrag] : [draggeddoc];
+ let dragData = new DragManager.DocumentDragData(dragdocs, dragdocs);
+ dragData.dropAction = "alias" as dropActionType;
+ DragManager.StartLinkedDocumentDrag([dragEle], sourceDoc, dragData, x, y, {
+ handlers: {
+ dragComplete: action(emptyFunction),
+ },
+ hideSource: false
+ });
+}
+
export async function DragLinksAsDocuments(dragEle: HTMLElement, x: number, y: number, sourceDoc: Doc) {
let srcTarg = sourceDoc.proto;
let draggedDocs: Doc[] = [];
- let draggedFromDocs: Doc[] = [];
+
if (srcTarg) {
- let linkToDocs = await DocListCastAsync(srcTarg.linkedToDocs);
- let linkFromDocs = await DocListCastAsync(srcTarg.linkedFromDocs);
- if (linkToDocs) draggedDocs = linkToDocs.map(linkDoc => Cast(linkDoc.linkedTo, Doc) as Doc);
- if (linkFromDocs) draggedFromDocs = linkFromDocs.map(linkDoc => Cast(linkDoc.linkedFrom, Doc) as Doc);
+ let linkDocs = LinkManager.Instance.getAllRelatedLinks(srcTarg);
+ if (linkDocs) {
+ draggedDocs = linkDocs.map(link => {
+ return LinkManager.Instance.getOppositeAnchor(link, sourceDoc);
+ });
+ }
}
- draggedDocs.push(...draggedFromDocs);
if (draggedDocs.length) {
let moddrag: Doc[] = [];
for (const draggedDoc of draggedDocs) {
let doc = await Cast(draggedDoc.annotationOn, Doc);
if (doc) moddrag.push(doc);
}
- let dragData = new DragManager.DocumentDragData(moddrag.length ? moddrag : draggedDocs);
- DragManager.StartDocumentDrag([dragEle], dragData, x, y, {
+ let dragdocs = moddrag.length ? moddrag : draggedDocs;
+ let dragData = new DragManager.DocumentDragData(dragdocs, dragdocs);
+ DragManager.StartLinkedDocumentDrag([dragEle], sourceDoc, dragData, x, y, {
handlers: {
dragComplete: action(emptyFunction),
},
@@ -143,13 +163,15 @@ export namespace DragManager {
export type MoveFunction = (document: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean;
export class DocumentDragData {
- constructor(dragDoc: Doc[]) {
+ constructor(dragDoc: Doc[], dragDataDocs: (Doc | undefined)[]) {
this.draggedDocuments = dragDoc;
+ this.draggedDataDocs = dragDataDocs;
this.droppedDocuments = dragDoc;
this.xOffset = 0;
this.yOffset = 0;
}
draggedDocuments: Doc[];
+ draggedDataDocs: (Doc | undefined)[];
droppedDocuments: Doc[];
xOffset: number;
yOffset: number;
@@ -180,12 +202,39 @@ export namespace DragManager {
export function StartDocumentDrag(eles: HTMLElement[], dragData: DocumentDragData, downX: number, downY: number, options?: DragOptions) {
runInAction(() => StartDragFunctions.map(func => func()));
StartDrag(eles, dragData, downX, downY, options,
- (dropData: { [id: string]: any }) =>
+ (dropData: { [id: string]: any }) => {
(dropData.droppedDocuments = dragData.userDropAction === "alias" || (!dragData.userDropAction && dragData.dropAction === "alias") ?
dragData.draggedDocuments.map(d => Doc.MakeAlias(d)) :
dragData.userDropAction === "copy" || (!dragData.userDropAction && dragData.dropAction === "copy") ?
dragData.draggedDocuments.map(d => Doc.MakeCopy(d, true)) :
- dragData.draggedDocuments));
+ dragData.draggedDocuments
+ );
+ });
+ }
+
+ export function StartLinkedDocumentDrag(eles: HTMLElement[], sourceDoc: Doc, dragData: DocumentDragData, downX: number, downY: number, options?: DragOptions) {
+
+ runInAction(() => StartDragFunctions.map(func => func()));
+ StartDrag(eles, dragData, downX, downY, options,
+ (dropData: { [id: string]: any }) => {
+ // dropData.droppedDocuments =
+ let droppedDocuments: Doc[] = dragData.draggedDocuments.reduce((droppedDocs: Doc[], d) => {
+ let dvs = DocumentManager.Instance.getDocumentViews(d);
+
+ if (dvs.length) {
+ let inContext = dvs.filter(dv => dv.props.ContainingCollectionView === SelectionManager.SelectedDocuments()[0].props.ContainingCollectionView);
+ if (inContext.length) {
+ inContext.forEach(dv => droppedDocs.push(dv.props.Document));
+ } else {
+ droppedDocs.push(Doc.MakeAlias(d));
+ }
+ } else {
+ droppedDocs.push(Doc.MakeAlias(d));
+ }
+ return droppedDocs;
+ }, []);
+ dropData.droppedDocuments = droppedDocuments;
+ });
}
export function StartAnnotationDrag(eles: HTMLElement[], dragData: AnnotationDragData, downX: number, downY: number, options?: DragOptions) {
@@ -240,6 +289,8 @@ export namespace DragManager {
const docs: Doc[] =
dragData instanceof DocumentDragData ? dragData.draggedDocuments : dragData instanceof AnnotationDragData ? [dragData.dragDocument] : [];
+ const datadocs: (Doc | undefined)[] =
+ dragData instanceof DocumentDragData ? dragData.draggedDataDocs : dragData instanceof AnnotationDragData ? [dragData.dragDocument] : [];
let dragElements = eles.map(ele => {
const w = ele.offsetWidth,
h = ele.offsetHeight;
@@ -316,12 +367,12 @@ export namespace DragManager {
}
if (((options && !options.withoutShiftDrag) || !options) && e.shiftKey && CollectionDockingView.Instance) {
AbortDrag();
- CollectionDockingView.Instance.StartOtherDrag(docs, {
+ CollectionDockingView.Instance.StartOtherDrag({
pageX: e.pageX,
pageY: e.pageY,
preventDefault: emptyFunction,
button: 0
- });
+ }, docs, datadocs);
}
//TODO: Why can't we use e.movementX and e.movementY?
let moveX = e.pageX - lastX;
diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
new file mode 100644
index 000000000..f2f3e51dd
--- /dev/null
+++ b/src/client/util/LinkManager.ts
@@ -0,0 +1,238 @@
+import { observable, action } from "mobx";
+import { StrCast, Cast, FieldValue } from "../../new_fields/Types";
+import { Doc, DocListCast } from "../../new_fields/Doc";
+import { listSpec } from "../../new_fields/Schema";
+import { List } from "../../new_fields/List";
+import { Id } from "../../new_fields/FieldSymbols";
+import { CurrentUserUtils } from "../../server/authentication/models/current_user_utils";
+
+
+/*
+ * link doc:
+ * - anchor1: doc
+ * - anchor1page: number
+ * - anchor1groups: list of group docs representing the groups anchor1 categorizes this link/anchor2 in
+ * - anchor2: doc
+ * - anchor2page: number
+ * - anchor2groups: list of group docs representing the groups anchor2 categorizes this link/anchor1 in
+ *
+ * group doc:
+ * - type: string representing the group type/name/category
+ * - metadata: doc representing the metadata kvps
+ *
+ * metadata doc:
+ * - user defined kvps
+ */
+export class LinkManager {
+
+ private static _instance: LinkManager;
+ public static get Instance(): LinkManager {
+ return this._instance || (this._instance = new this());
+ }
+ private constructor() {
+ }
+
+ // the linkmanagerdoc stores a list of docs representing all linkdocs in 'allLinks' and a list of strings representing all group types in 'allGroupTypes'
+ // lists of strings representing the metadata keys for each group type is stored under a key that is the same as the group type
+ public get LinkManagerDoc(): Doc | undefined {
+ return FieldValue(Cast(CurrentUserUtils.UserDocument.linkManagerDoc, Doc));
+ }
+
+ public getAllLinks(): Doc[] {
+ return LinkManager.Instance.LinkManagerDoc ? LinkManager.Instance.LinkManagerDoc.allLinks ? DocListCast(LinkManager.Instance.LinkManagerDoc.allLinks) : [] : [];
+ }
+
+ public addLink(linkDoc: Doc): boolean {
+ let linkList = LinkManager.Instance.getAllLinks();
+ linkList.push(linkDoc);
+ if (LinkManager.Instance.LinkManagerDoc) {
+ LinkManager.Instance.LinkManagerDoc.allLinks = new List<Doc>(linkList);
+ return true;
+ }
+ return false;
+ }
+
+ public deleteLink(linkDoc: Doc): boolean {
+ let linkList = LinkManager.Instance.getAllLinks();
+ let index = LinkManager.Instance.getAllLinks().indexOf(linkDoc);
+ if (index > -1) {
+ linkList.splice(index, 1);
+ if (LinkManager.Instance.LinkManagerDoc) {
+ LinkManager.Instance.LinkManagerDoc.allLinks = new List<Doc>(linkList);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ // finds all links that contain the given anchor
+ public getAllRelatedLinks(anchor: Doc): Doc[] {//List<Doc> {
+ let related = LinkManager.Instance.getAllLinks().filter(link => {
+ let protomatch1 = Doc.AreProtosEqual(anchor, Cast(link.anchor1, Doc, new Doc));
+ let protomatch2 = Doc.AreProtosEqual(anchor, Cast(link.anchor2, Doc, new Doc));
+ return protomatch1 || protomatch2;
+ });
+ return related;
+ }
+
+ public deleteAllLinksOnAnchor(anchor: Doc) {
+ let related = LinkManager.Instance.getAllRelatedLinks(anchor);
+ related.forEach(linkDoc => LinkManager.Instance.deleteLink(linkDoc));
+ }
+
+ public addGroupType(groupType: string): boolean {
+ if (LinkManager.Instance.LinkManagerDoc) {
+ LinkManager.Instance.LinkManagerDoc[groupType] = new List<string>([]);
+ let groupTypes = LinkManager.Instance.getAllGroupTypes();
+ groupTypes.push(groupType);
+ LinkManager.Instance.LinkManagerDoc.allGroupTypes = new List<string>(groupTypes);
+ return true;
+ }
+ return false;
+ }
+
+ // removes all group docs from all links with the given group type
+ public deleteGroupType(groupType: string): boolean {
+ if (LinkManager.Instance.LinkManagerDoc) {
+ if (LinkManager.Instance.LinkManagerDoc[groupType]) {
+ let groupTypes = LinkManager.Instance.getAllGroupTypes();
+ let index = groupTypes.findIndex(type => type.toUpperCase() === groupType.toUpperCase());
+ if (index > -1) groupTypes.splice(index, 1);
+ LinkManager.Instance.LinkManagerDoc.allGroupTypes = new List<string>(groupTypes);
+ LinkManager.Instance.LinkManagerDoc[groupType] = undefined;
+ LinkManager.Instance.getAllLinks().forEach(linkDoc => {
+ LinkManager.Instance.removeGroupFromAnchor(linkDoc, Cast(linkDoc.anchor1, Doc, new Doc), groupType);
+ LinkManager.Instance.removeGroupFromAnchor(linkDoc, Cast(linkDoc.anchor2, Doc, new Doc), groupType);
+ });
+ }
+ return true;
+ } else return false;
+ }
+
+ public getAllGroupTypes(): string[] {
+ if (LinkManager.Instance.LinkManagerDoc) {
+ if (LinkManager.Instance.LinkManagerDoc.allGroupTypes) {
+ return Cast(LinkManager.Instance.LinkManagerDoc.allGroupTypes, listSpec("string"), []);
+ } else {
+ LinkManager.Instance.LinkManagerDoc.allGroupTypes = new List<string>([]);
+ return [];
+ }
+ }
+ return [];
+ }
+
+ // gets the groups associates with an anchor in a link
+ public getAnchorGroups(linkDoc: Doc, anchor: Doc): Array<Doc> {
+ if (Doc.AreProtosEqual(anchor, Cast(linkDoc.anchor1, Doc, new Doc))) {
+ return DocListCast(linkDoc.anchor1Groups);
+ } else {
+ return DocListCast(linkDoc.anchor2Groups);
+ }
+ }
+
+ // sets the groups of the given anchor in the given link
+ public setAnchorGroups(linkDoc: Doc, anchor: Doc, groups: Doc[]) {
+ if (Doc.AreProtosEqual(anchor, Cast(linkDoc.anchor1, Doc, new Doc))) {
+ linkDoc.anchor1Groups = new List<Doc>(groups);
+ } else {
+ linkDoc.anchor2Groups = new List<Doc>(groups);
+ }
+ }
+
+ public addGroupToAnchor(linkDoc: Doc, anchor: Doc, groupDoc: Doc, replace: boolean = false) {
+ let groups = LinkManager.Instance.getAnchorGroups(linkDoc, anchor);
+ let index = groups.findIndex(gDoc => {
+ return StrCast(groupDoc.type).toUpperCase() === StrCast(gDoc.type).toUpperCase();
+ });
+ if (index > -1 && replace) {
+ groups[index] = groupDoc;
+ }
+ if (index === -1) {
+ groups.push(groupDoc);
+ }
+ LinkManager.Instance.setAnchorGroups(linkDoc, anchor, groups);
+ }
+
+ // removes group doc of given group type only from given anchor on given link
+ public removeGroupFromAnchor(linkDoc: Doc, anchor: Doc, groupType: string) {
+ let groups = LinkManager.Instance.getAnchorGroups(linkDoc, anchor);
+ let newGroups = groups.filter(groupDoc => StrCast(groupDoc.type).toUpperCase() !== groupType.toUpperCase());
+ LinkManager.Instance.setAnchorGroups(linkDoc, anchor, newGroups);
+ }
+
+ // returns map of group type to anchor's links in that group type
+ public getRelatedGroupedLinks(anchor: Doc): Map<string, Array<Doc>> {
+ let related = this.getAllRelatedLinks(anchor);
+ let anchorGroups = new Map<string, Array<Doc>>();
+ related.forEach(link => {
+ let groups = LinkManager.Instance.getAnchorGroups(link, anchor);
+
+ if (groups.length > 0) {
+ groups.forEach(groupDoc => {
+ let groupType = StrCast(groupDoc.type);
+ if (groupType === "") {
+ let group = anchorGroups.get("*");
+ anchorGroups.set("*", group ? [...group, link] : [link]);
+ } else {
+ let group = anchorGroups.get(groupType);
+ anchorGroups.set(groupType, group ? [...group, link] : [link]);
+ }
+ });
+ } else {
+ // if link is in no groups then put it in default group
+ let group = anchorGroups.get("*");
+ anchorGroups.set("*", group ? [...group, link] : [link]);
+ }
+
+ });
+ return anchorGroups;
+ }
+
+ // gets a list of strings representing the keys of the metadata associated with the given group type
+ public getMetadataKeysInGroup(groupType: string): string[] {
+ if (LinkManager.Instance.LinkManagerDoc) {
+ return LinkManager.Instance.LinkManagerDoc[groupType] ? Cast(LinkManager.Instance.LinkManagerDoc[groupType], listSpec("string"), []) : [];
+ }
+ return [];
+ }
+
+ public setMetadataKeysForGroup(groupType: string, keys: string[]): boolean {
+ if (LinkManager.Instance.LinkManagerDoc) {
+ LinkManager.Instance.LinkManagerDoc[groupType] = new List<string>(keys);
+ return true;
+ }
+ return false;
+ }
+
+ // returns a list of all metadata docs associated with the given group type
+ public getAllMetadataDocsInGroup(groupType: string): Array<Doc> {
+ let md: Doc[] = [];
+ let allLinks = LinkManager.Instance.getAllLinks();
+ allLinks.forEach(linkDoc => {
+ let anchor1Groups = LinkManager.Instance.getAnchorGroups(linkDoc, Cast(linkDoc.anchor1, Doc, new Doc));
+ let anchor2Groups = LinkManager.Instance.getAnchorGroups(linkDoc, Cast(linkDoc.anchor2, Doc, new Doc));
+ anchor1Groups.forEach(groupDoc => { if (StrCast(groupDoc.type).toUpperCase() === groupType.toUpperCase()) md.push(Cast(groupDoc.metadata, Doc, new Doc)); });
+ anchor2Groups.forEach(groupDoc => { if (StrCast(groupDoc.type).toUpperCase() === groupType.toUpperCase()) md.push(Cast(groupDoc.metadata, Doc, new Doc)); });
+ });
+ return md;
+ }
+
+ // checks if a link with the given anchors exists
+ public doesLinkExist(anchor1: Doc, anchor2: Doc): boolean {
+ let allLinks = LinkManager.Instance.getAllLinks();
+ let index = allLinks.findIndex(linkDoc => {
+ return (Doc.AreProtosEqual(Cast(linkDoc.anchor1, Doc, new Doc), anchor1) && Doc.AreProtosEqual(Cast(linkDoc.anchor2, Doc, new Doc), anchor2)) ||
+ (Doc.AreProtosEqual(Cast(linkDoc.anchor1, Doc, new Doc), anchor2) && Doc.AreProtosEqual(Cast(linkDoc.anchor2, Doc, new Doc), anchor1));
+ });
+ return index !== -1;
+ }
+
+ // finds the opposite anchor of a given anchor in a link
+ public getOppositeAnchor(linkDoc: Doc, anchor: Doc): Doc {
+ if (Doc.AreProtosEqual(anchor, Cast(linkDoc.anchor1, Doc, new Doc))) {
+ return Cast(linkDoc.anchor2, Doc, new Doc);
+ } else {
+ return Cast(linkDoc.anchor1, Doc, new Doc);
+ }
+ }
+} \ No newline at end of file
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index 943cdb4d1..f3f6655af 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -1,10 +1,11 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { Schema, NodeSpec, MarkSpec, DOMOutputSpecArray, NodeType, Slice } from "prosemirror-model";
+import { Schema, NodeSpec, MarkSpec, DOMOutputSpecArray, NodeType, Slice, Mark, Node } from "prosemirror-model";
import { joinUp, lift, setBlockType, toggleMark, wrapIn, selectNodeForward, deleteSelection } from 'prosemirror-commands';
import { redo, undo } from 'prosemirror-history';
import { orderedList, bulletList, listItem, } from 'prosemirror-schema-list';
import { EditorState, Transaction, NodeSelection, TextSelection, Selection, } from "prosemirror-state";
import { EditorView, } from "prosemirror-view";
+import { View } from '@react-pdf/renderer';
const pDOM: DOMOutputSpecArray = ["p", 0], blockquoteDOM: DOMOutputSpecArray = ["blockquote", 0], hrDOM: DOMOutputSpecArray = ["hr"],
preDOM: DOMOutputSpecArray = ["pre", ["code", 0]], brDOM: DOMOutputSpecArray = ["br"], ulDOM: DOMOutputSpecArray = ["ul", 0];
@@ -89,7 +90,7 @@ export const nodes: { [index: string]: NodeSpec } = {
inline: true,
attrs: {
visibility: { default: false },
- oldtext: { default: undefined },
+ text: { default: undefined },
oldtextslice: { default: undefined },
oldtextlen: { default: 0 }
@@ -280,8 +281,8 @@ export const marks: { [index: string]: MarkSpec } = {
toDOM: () => ['sup']
},
- collapse: {
- parseDOM: [{ style: 'color: blue' }],
+ highlight: {
+ parseDOM: [{ style: 'background: #d9dbdd' }],
toDOM() {
return ['span', {
style: 'color: blue'
@@ -349,6 +350,16 @@ export const marks: { [index: string]: MarkSpec } = {
/** FONT SIZES */
+ pFontSize: {
+ attrs: {
+ fontSize: { default: 10 }
+ },
+ inclusive: false,
+ parseDOM: [{ style: 'font-size: 10px;' }],
+ toDOM: (node) => ['span', {
+ style: `font-size: ${node.attrs.fontSize}px;`
+ }]
+ },
p10: {
parseDOM: [{ style: 'font-size: 10px;' }],
@@ -479,6 +490,7 @@ export class ImageResizeView {
export class SummarizedView {
// TODO: highlight text that is summarized. to find end of region, walk along mark
_collapsed: HTMLElement;
+ _view: any;
constructor(node: any, view: any, getPos: any) {
this._collapsed = document.createElement("span");
this._collapsed.textContent = "㊉";
@@ -487,24 +499,32 @@ export class SummarizedView {
this._collapsed.style.position = "relative";
this._collapsed.style.width = "40px";
this._collapsed.style.height = "20px";
+ let self = this;
+ this._view = view;
this._collapsed.onpointerdown = function (e: any) {
console.log("star pressed!");
if (node.attrs.visibility) {
node.attrs.visibility = !node.attrs.visibility;
console.log("content is visible");
let y = getPos();
- view.dispatch(view.state.tr.setSelection(TextSelection.create(view.state.doc, y + 1, y + 1 + node.attrs.oldtextlen)));
- view.dispatch(view.state.tr.deleteSelection(view.state, () => { }));
- //this._collapsed.textContent = "㊀";
+ let { from, to } = self.updateSummarizedText(y + 1, view.state.schema.marks.highlight);
+ let length = to - from;
+ let newSelection = TextSelection.create(view.state.doc, y + 1, y + 1 + length);
+ node.attrs.text = newSelection.content();
+ view.dispatch(view.state.tr.setSelection(newSelection).deleteSelection(view.state, () => { }));
+ self._collapsed.textContent = "㊉";
} else {
node.attrs.visibility = !node.attrs.visibility;
console.log("content is invisible");
let y = getPos();
- let mark = view.state.schema.mark(view.state.schema.marks.underline);
- console.log("PASTING " + node.attrs.oldtext.toString());
+ console.log(y);
+ let mark = view.state.schema.mark(view.state.schema.marks.highlight);
+ console.log("PASTING " + node.attrs.text.toString());
view.dispatch(view.state.tr.setSelection(TextSelection.create(view.state.doc, y + 1, y + 1)));
- view.dispatch(view.state.tr.replaceSelection(node.attrs.oldtext).addMark(view.state.selection.from, view.state.selection.from + node.attrs.oldtextlen, mark));
- //this._collapsed.textContent = "㊉";
+ const from = view.state.selection.from;
+ let size = node.attrs.text.size;
+ view.dispatch(view.state.tr.replaceSelection(node.attrs.text).addMark(from, from + size, mark).removeStoredMark(mark));
+ self._collapsed.textContent = "㊀";
}
e.preventDefault();
e.stopPropagation();
@@ -515,6 +535,45 @@ export class SummarizedView {
selectNode() {
}
+ updateSummarizedText(start?: any, mark?: any) {
+ let $start = this._view.state.doc.resolve(start);
+ let endPos = start;
+ //let first_startPos = $start.start(), endPos = first_startPos;
+ // let startIndex = $start.index(), endIndex = $start.indexAfter();
+ // let nodeAfter = $start.nodeAfter;
+ // while (startIndex > 0 && mark.isInSet($start.parent.child(startIndex - 1).marks)) startIndex--;
+ // while (endIndex < $start.parent.childCount && mark.isInSet($start.parent.child(endIndex).marks)) endIndex++;
+ // let startPos = $start.start(), endPos = startPos;
+ // for (let i = 0; i < endIndex; i++) {
+ // let size = $start.parent.child(i).nodeSize;
+ // console.log($start.parent.child(i).childCount);
+ // if (i < startIndex) startPos += size;
+ // endPos += size;
+ // }
+ let _mark = this._view.state.schema.mark(this._view.state.schema.marks.highlight);
+ // first_startPos = start;
+ // endPos = first_startPos;
+ let visited = new Set();
+ for (let i: number = start + 1; i < this._view.state.doc.nodeSize - 1; i++) {
+ console.log("ITER:", i);
+ let skip = false;
+ this._view.state.doc.nodesBetween(start, i, (node: Node, pos: number, parent: Node, index: number) => {
+ if (node.isLeaf && !visited.has(node) && !skip) {
+ if (node.marks.includes(_mark)) {
+ visited.add(node);
+ //endPos += node.nodeSize + 1;
+ endPos = i + node.nodeSize - 1;
+ console.log("node contains mark!");
+ }
+ else skip = true;
+ }
+ });
+ }
+ console.log(start);
+ console.log(endPos);
+ return { from: start, to: endPos };
+ }
+
deselectNode() {
}
}
diff --git a/src/client/util/TooltipTextMenu.scss b/src/client/util/TooltipTextMenu.scss
index 4d4eb386d..b10573b3e 100644
--- a/src/client/util/TooltipTextMenu.scss
+++ b/src/client/util/TooltipTextMenu.scss
@@ -18,6 +18,7 @@
.ProseMirror-menuitem {
margin-right: 3px;
display: inline-block;
+ z-index: 100000;
}
.ProseMirror-menuseparator {
@@ -59,7 +60,6 @@
}
.ProseMirror-menu-dropdown-menu, .ProseMirror-menu-submenu {
- position: absolute;
background: $dark-color;
color:white;
border: 1px solid rgb(223, 223, 223);
@@ -67,9 +67,10 @@
}
.ProseMirror-menu-dropdown-menu {
- z-index: 15;
+ z-index: 100000;
min-width: 6em;
background: white;
+ position: absolute;
}
.linking {
@@ -80,6 +81,7 @@
cursor: pointer;
padding: 2px 8px 2px 4px;
width: auto;
+ z-index: 100000;
}
.ProseMirror-menu-dropdown-item:hover {
@@ -233,19 +235,20 @@
}
.tooltipMenu {
- position: absolute;
- z-index: 50;
- background: whitesmoke;
+ position: relative;
+ z-index: 2000;
+ background: #121721;
border: 1px solid silver;
border-radius: 15px;
- padding: 2px 10px;
- //margin-bottom: 100px;
+ //height: 60px;
+ //padding: 2px 10px;
+ //margin-top: 100px;
//-webkit-transform: translateX(-50%);
//transform: translateX(-50%);
- transform: translateY(-50%);
+ transform: translateY(-85px);
pointer-events: all;
- height: auto;
- width:inherit;
+ height: 30px;
+ width:500px;
.ProseMirror-example-setup-style hr {
padding: 2px 10px;
border: none;
@@ -306,3 +309,10 @@
font-size: 12px;
padding-right: 0px;
}
+ .summarize{
+ //margin-left: 15px;
+ color: white;
+ height: 20px;
+ // background-color: white;
+ text-align: center;
+ } \ No newline at end of file
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index cbabadd64..1c96bb1d3 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -7,6 +7,7 @@ import { EditorState, Transaction, NodeSelection, TextSelection } from "prosemir
import { EditorView } from "prosemirror-view";
import { schema } from "./RichTextSchema";
import { Schema, NodeType, MarkType, Mark } from "prosemirror-model";
+import { Node as ProsNode } from "prosemirror-model";
import React = require("react");
import "./TooltipTextMenu.scss";
const { toggleMark, setBlockType, wrapIn } = require("prosemirror-commands");
@@ -29,6 +30,8 @@ import { CollectionDockingView } from "../views/collections/CollectionDockingVie
import { DocumentManager } from "./DocumentManager";
import { Id } from "../../new_fields/FieldSymbols";
import { Utils } from "../../Utils";
+import { FormattedTextBoxProps } from "../views/nodes/FormattedTextBox";
+import { text } from "body-parser";
// import { wrap } from "module";
const SVG = "http://www.w3.org/2000/svg";
@@ -42,7 +45,7 @@ export class TooltipTextMenu {
private fontStyles: MarkType[];
private fontSizes: MarkType[];
private listTypes: NodeType[];
- private editorProps: FieldViewProps;
+ private editorProps: FieldViewProps & FormattedTextBoxProps;
private state: EditorState;
private fontSizeToNum: Map<MarkType, number>;
private fontStylesToName: Map<MarkType, string>;
@@ -58,7 +61,7 @@ export class TooltipTextMenu {
private fontStyleDom?: Node;
private listTypeBtnDom?: Node;
- constructor(view: EditorView, editorProps: FieldViewProps) {
+ constructor(view: EditorView, editorProps: FieldViewProps & FormattedTextBoxProps) {
this.view = view;
this.state = view.state;
this.editorProps = editorProps;
@@ -66,7 +69,7 @@ export class TooltipTextMenu {
this.tooltip.className = "tooltipMenu";
//add the div which is the tooltip
- view.dom.parentNode!.parentNode!.appendChild(this.tooltip);
+ //view.dom.parentNode!.parentNode!.appendChild(this.tooltip);
//add additional icons
library.add(faListUl);
@@ -131,11 +134,21 @@ export class TooltipTextMenu {
this.link = document.createElement("a");
this.link.target = "_blank";
this.link.style.color = "white";
- this.tooltip.appendChild(this.link);
+ //this.tooltip.appendChild(this.link);
this.tooltip.appendChild(this.createLink().render(this.view).dom);
+ this.tooltip.appendChild(this.createStar().render(this.view).dom);
+
this.update(view, undefined);
+
+ //view.dom.parentNode!.parentNode!.insertBefore(this.tooltip, view.dom.parentNode);
+
+ // quick and dirty null check
+ const outer_div = this.editorProps.outer_div;
+ outer_div && outer_div(this.tooltip);
+
+ console.log("hi");
}
//label of dropdown will change to given label
@@ -189,13 +202,13 @@ export class TooltipTextMenu {
this.linkText.style.whiteSpace = "nowrap";
this.linkText.style.width = "150px";
this.linkText.style.overflow = "hidden";
- this.linkText.style.color = "black";
+ this.linkText.style.color = "white";
this.linkText.onpointerdown = (e: PointerEvent) => { e.stopPropagation(); };
let linkBtn = document.createElement("div");
linkBtn.textContent = ">>";
linkBtn.style.width = "10px";
linkBtn.style.height = "10px";
- linkBtn.style.color = "black";
+ linkBtn.style.color = "white";
linkBtn.style.cssFloat = "left";
linkBtn.onpointerdown = (e: PointerEvent) => {
let node = this.view.state.selection.$from.nodeAfter;
@@ -209,7 +222,7 @@ export class TooltipTextMenu {
if (DocumentManager.Instance.getDocumentView(f)) {
DocumentManager.Instance.getDocumentView(f)!.props.focus(f, false);
}
- else if (CollectionDockingView.Instance) CollectionDockingView.Instance.AddRightSplit(f);
+ else if (CollectionDockingView.Instance) CollectionDockingView.Instance.AddRightSplit(f, f);
}
}));
}
@@ -239,26 +252,10 @@ export class TooltipTextMenu {
hideSource: false
});
};
- this.linkEditor.appendChild(this.linkDrag);
- this.linkEditor.appendChild(this.linkText);
- this.linkEditor.appendChild(linkBtn);
- this.tooltip.appendChild(this.linkEditor);
-
- let starButton = document.createElement("span");
- // starButton.style.width = '10px';
- // starButton.style.height = '10px';
- starButton.style.marginLeft = '10px';
- starButton.textContent = "Summarize";
- starButton.style.color = 'black';
- starButton.style.height = '20px';
- starButton.style.backgroundColor = 'white';
- starButton.style.textAlign = 'center';
- starButton.onclick = () => {
- let state = this.view.state;
- this.insertStar(state, this.view.dispatch);
- };
-
- this.tooltip.appendChild(starButton);
+ // this.linkEditor.appendChild(this.linkDrag);
+ // this.linkEditor.appendChild(this.linkText);
+ // this.linkEditor.appendChild(linkBtn);
+ //this.tooltip.appendChild(this.linkEditor);
}
let node = this.view.state.selection.$from.nodeAfter;
@@ -286,16 +283,16 @@ export class TooltipTextMenu {
insertStar(state: EditorState<any>, dispatch: any) {
console.log("creating star...");
- let newNode = schema.nodes.star.create({ visibility: false, oldtext: state.selection.content(), oldtextslice: state.selection.content().toJSON(), oldtextlen: state.selection.to - state.selection.from });
+ let newNode = schema.nodes.star.create({ visibility: false, text: state.selection.content(), oldtextslice: state.selection.content().toJSON(), oldtextlen: state.selection.to - state.selection.from });
if (dispatch) {
- console.log(newNode.attrs.oldtext.toString());
+ //console.log(newNode.attrs.text.toString());
dispatch(state.tr.replaceSelectionWith(newNode));
}
return true;
}
//will display a remove-list-type button if selection is in list, otherwise will show list type dropdown
- updateListItemDropdown(label: string, listTypeBtn: Node) {
+ updateListItemDropdown(label: string, listTypeBtn: any) {
//remove old btn
if (listTypeBtn) { this.tooltip.removeChild(listTypeBtn); }
@@ -373,6 +370,21 @@ export class TooltipTextMenu {
});
}
+ createStar() {
+ return new MenuItem({
+ title: "Summarize",
+ label: "Summarize",
+ icon: icons.join,
+ css: "color:white;",
+ class: "summarize",
+ execEvent: "",
+ run: (state, dispatch, view) => {
+ this.insertStar(state, dispatch);
+ }
+
+ });
+ }
+
createLink() {
let markType = schema.marks.link;
return new MenuItem({
@@ -438,7 +450,7 @@ export class TooltipTextMenu {
span.className = name + " menuicon";
span.title = title;
span.textContent = text;
- span.style.color = "black";
+ span.style.color = "white";
return span;
}
@@ -502,34 +514,34 @@ export class TooltipTextMenu {
// Hide the tooltip if the selection is empty
if (state.selection.empty) {
- this.tooltip.style.display = "none";
- return;
+ //this.tooltip.style.display = "none";
+ //return;
}
- let linksInSelection = this.activeMarksOnSelection([schema.marks.link]);
- if (linksInSelection.length > 0) {
- let attributes = this.getMarksInSelection(this.view.state, [schema.marks.link])[0].attrs;
- this.link.href = attributes.href;
- this.link.textContent = attributes.title;
- this.link.style.visibility = "visible";
- } else this.link.style.visibility = "hidden";
+ //let linksInSelection = this.activeMarksOnSelection([schema.marks.link]);
+ // if (linksInSelection.length > 0) {
+ // let attributes = this.getMarksInSelection(this.view.state, [schema.marks.link])[0].attrs;
+ // this.link.href = attributes.href;
+ // this.link.textContent = attributes.title;
+ // this.link.style.visibility = "visible";
+ // } else this.link.style.visibility = "hidden";
// Otherwise, reposition it and update its content
- this.tooltip.style.display = "";
+ //this.tooltip.style.display = "";
let { from, to } = state.selection;
let start = view.coordsAtPos(from), end = view.coordsAtPos(to);
// The box in which the tooltip is positioned, to use as base
- let box = this.tooltip.offsetParent!.getBoundingClientRect();
+ //let box = this.tooltip.offsetParent!.getBoundingClientRect();
// Find a center-ish x position from the selection endpoints (when
// crossing lines, end may be more to the left)
let left = Math.max((start.left + end.left) / 2, start.left + 3);
- this.tooltip.style.left = (left - box.left) * this.editorProps.ScreenToLocalTransform().Scale + "px";
+ //this.tooltip.style.left = (left - box.left) * this.editorProps.ScreenToLocalTransform().Scale + "px";
let width = Math.abs(start.left - end.left) / 2 * this.editorProps.ScreenToLocalTransform().Scale;
let mid = Math.min(start.left, end.left) + width;
//this.tooltip.style.width = 225 + "px";
- this.tooltip.style.bottom = (box.bottom - start.top) * this.editorProps.ScreenToLocalTransform().Scale + "px";
- this.tooltip.style.top = "-100px";
+ // this.tooltip.style.bottom = (box.bottom - start.top) * this.editorProps.ScreenToLocalTransform().Scale + "px";
+ // this.tooltip.style.top = "-100px";
//this.tooltip.style.height = "100px";
// let transform = this.editorProps.ScreenToLocalTransform();
@@ -573,17 +585,47 @@ export class TooltipTextMenu {
let { empty, $cursor, ranges } = this.view.state.selection as TextSelection;
let state = this.view.state;
let dispatch = this.view.dispatch;
-
- let activeMarks = markGroup.filter(mark => {
- if (dispatch) {
- let has = false, tr = state.tr;
- for (let i = 0; !has && i < ranges.length; i++) {
- let { $from, $to } = ranges[i];
- return state.doc.rangeHasMark($from.pos, $to.pos, mark);
+ let activeMarks: MarkType[];
+ if (!empty) {
+ activeMarks = markGroup.filter(mark => {
+ if (dispatch) {
+ let has = false, tr = state.tr;
+ for (let i = 0; !has && i < ranges.length; i++) {
+ let { $from, $to } = ranges[i];
+ return state.doc.rangeHasMark($from.pos, $to.pos, mark);
+ }
}
+ return false;
+ });
+ }
+ else {
+ let pos = this.view.state.selection.$from;
+ let ref_node: ProsNode;
+ if (pos.nodeAfter !== null && pos.nodeAfter !== undefined) {
+ ref_node = pos.nodeAfter;
}
- return false;
- });
+ else if (pos.nodeBefore !== null && pos.nodeBefore !== undefined) {
+ ref_node = pos.nodeBefore;
+ }
+ else {
+ return [];
+ }
+ let text_node_type: NodeType;
+ if (ref_node.isText) {
+ text_node_type = ref_node.type;
+ }
+ else {
+ return [];
+ }
+
+ activeMarks = markGroup.filter(mark_type => {
+ if (dispatch) {
+ let mark = state.schema.mark(mark_type);
+ return ref_node.marks.includes(mark);
+ }
+ return false;
+ });
+ }
return activeMarks;
}
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 2c0e18bbb..0a77ae515 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -5,8 +5,8 @@ import { action, computed, observable, reaction, runInAction } from "mobx";
import { observer } from "mobx-react";
import { Doc } from "../../new_fields/Doc";
import { List } from "../../new_fields/List";
-import { listSpec } from "../../new_fields/Schema";
-import { Cast, NumCast, StrCast, BoolCast } from "../../new_fields/Types";
+import { BoolCast, Cast, NumCast, StrCast } from "../../new_fields/Types";
+import { URLField } from '../../new_fields/URLField';
import { emptyFunction, Utils } from "../../Utils";
import { Docs } from "../documents/Documents";
import { DocumentManager } from "../util/DocumentManager";
@@ -24,7 +24,7 @@ import { LinkMenu } from "./nodes/LinkMenu";
import { TemplateMenu } from "./TemplateMenu";
import { Template, Templates } from "./Templates";
import React = require("react");
-import { URLField } from '../../new_fields/URLField';
+import { LinkManager } from '../util/LinkManager';
const higflyout = require("@hig/flyout");
export const { anchorPoints } = higflyout;
export const Flyout = higflyout.default;
@@ -73,6 +73,33 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
if (text[0] === '#') {
this._fieldKey = text.slice(1, text.length);
this._title = this.selectionTitle;
+ } else if (text.startsWith(">")) {
+ let field = SelectionManager.SelectedDocuments()[0];
+ let collection = field.props.ContainingCollectionView!.props.Document;
+
+ let collectionKey = field.props.ContainingCollectionView!.props.fieldKey;
+ let collectionKeyProp = `fieldKey={"${collectionKey}"}`;
+ let metaKey = text.slice(1, text.length);
+ let metaKeyProp = `fieldKey={"${metaKey}"}`;
+
+ let template = Doc.MakeAlias(field.props.Document);
+ template.proto = collection;
+ template.title = metaKey;
+ template.nativeWidth = Cast(field.nativeWidth, "number");
+ template.nativeHeight = Cast(field.nativeHeight, "number");
+ template.embed = true;
+ template.isTemplate = true;
+ template.templates = new List<string>([Templates.TitleBar(metaKey)]);
+ if (field.props.Document.backgroundLayout) {
+ let metaAnoKeyProp = `fieldKey={"${metaKey}"} fieldExt={"annotations"}`;
+ let collectionAnoKeyProp = `fieldKey={"annotations"}`;
+ template.layout = StrCast(field.props.Document.layout).replace(collectionAnoKeyProp, metaAnoKeyProp);
+ template.backgroundLayout = StrCast(field.props.Document.backgroundLayout).replace(collectionKeyProp, metaKeyProp);
+ } else {
+ template.layout = StrCast(field.props.Document.layout).replace(collectionKeyProp, metaKeyProp);
+ }
+ Doc.AddDocToList(collection, collectionKey, template);
+ SelectionManager.SelectedDocuments().map(dv => dv.props.removeDocument && dv.props.removeDocument(dv.props.Document));
}
else {
if (SelectionManager.SelectedDocuments().length > 0) {
@@ -122,7 +149,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
@computed
get Bounds(): { x: number, y: number, b: number, r: number } {
return SelectionManager.SelectedDocuments().reduce((bounds, documentView) => {
- if (documentView.props.isTopMost) {
+ if (documentView.props.renderDepth === 0) {
return bounds;
}
let transform = (documentView.props.ScreenToLocalTransform().scale(documentView.props.ContentScaling())).inverse();
@@ -149,7 +176,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
let dragDocView = SelectionManager.SelectedDocuments()[0];
const [left, top] = dragDocView.props.ScreenToLocalTransform().scale(dragDocView.props.ContentScaling()).inverse().transformPoint(0, 0);
const [xoff, yoff] = dragDocView.props.ScreenToLocalTransform().scale(dragDocView.props.ContentScaling()).transformDirection(e.x - left, e.y - top);
- let dragData = new DragManager.DocumentDragData(SelectionManager.SelectedDocuments().map(dv => dv.props.Document));
+ let dragData = new DragManager.DocumentDragData(SelectionManager.SelectedDocuments().map(dv => dv.props.Document), SelectionManager.SelectedDocuments().map(dv => dv.props.DataDoc ? dv.props.DataDoc : dv.props.Document));
dragData.xOffset = xoff;
dragData.yOffset = yoff;
dragData.moveDocument = SelectionManager.SelectedDocuments()[0].props.moveDocument;
@@ -468,9 +495,8 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
let doc = PositionDocument(element.props.Document);
let nwidth = doc.nativeWidth || 0;
let nheight = doc.nativeHeight || 0;
- let zoomBasis = NumCast(doc.zoomBasis, 1);
- let width = (doc.width || 0) / zoomBasis;
- let height = (doc.height || (nheight / nwidth * width)) / zoomBasis;
+ let width = (doc.width || 0);
+ let height = (doc.height || (nheight / nwidth * width));
let scale = element.props.ScreenToLocalTransform().Scale;
let actualdW = Math.max(width + (dW * scale), 20);
let actualdH = Math.max(height + (dH * scale), 20);
@@ -485,25 +511,27 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
}
if (nwidth > 0 && nheight > 0) {
if (Math.abs(dW) > Math.abs(dH)) {
- if (!fixedAspect) proto.nativeWidth = zoomBasis * actualdW / (doc.width || 1) * NumCast(proto.nativeWidth);
- doc.width = zoomBasis * actualdW;
- // doc.zoomBasis = zoomBasis * width / actualdW;
+ if (!fixedAspect) {
+ Doc.SetInPlace(element.props.Document, "nativeWidth", actualdW / (doc.width || 1) * (doc.nativeWidth || 0), true);
+ }
+ doc.width = actualdW;
if (fixedAspect) doc.height = nheight / nwidth * doc.width;
- else doc.height = zoomBasis * actualdH;
- proto.nativeHeight = (doc.height || 0) / doc.width * NumCast(proto.nativeWidth);
+ else doc.height = actualdH;
+ Doc.SetInPlace(element.props.Document, "nativeHeight", (doc.height || 0) / doc.width * (doc.nativeWidth || 0), true);
}
else {
- if (!fixedAspect) proto.nativeHeight = zoomBasis * actualdH / (doc.height || 1) * NumCast(proto.nativeHeight);
- doc.height = zoomBasis * actualdH;
- //doc.zoomBasis = zoomBasis * height / actualdH;
+ if (!fixedAspect) {
+ Doc.SetInPlace(element.props.Document, "nativeHeight", actualdH / (doc.height || 1) * (doc.nativeHeight || 0), true);
+ }
+ doc.height = actualdH;
if (fixedAspect) doc.width = nwidth / nheight * doc.height;
- else doc.width = zoomBasis * actualdW;
- proto.nativeWidth = (doc.width || 0) / doc.height * NumCast(proto.nativeHeight);
+ else doc.width = actualdW;
+ Doc.SetInPlace(element.props.Document, "nativeWidth", (doc.width || 0) / doc.height * (doc.nativeHeight || 0), true);
}
} else {
- dW && (doc.width = zoomBasis * actualdW);
- dH && (doc.height = zoomBasis * actualdH);
- proto.autoHeight = undefined;
+ dW && (doc.width = actualdW);
+ dH && (doc.height = actualdH);
+ Doc.SetInPlace(element.props.Document, "autoHeight", undefined, true);
}
}
});
@@ -572,9 +600,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
let linkButton = null;
if (SelectionManager.SelectedDocuments().length > 0) {
let selFirst = SelectionManager.SelectedDocuments()[0];
- let linkToSize = Cast(selFirst.props.Document.linkedToDocs, listSpec(Doc), []).length;
- let linkFromSize = Cast(selFirst.props.Document.linkedFromDocs, listSpec(Doc), []).length;
- let linkCount = linkToSize + linkFromSize;
+ let linkCount = LinkManager.Instance.getAllRelatedLinks(selFirst.props.Document).length;
linkButton = (<Flyout
anchorPoint={anchorPoints.RIGHT_TOP}
content={<LinkMenu docView={selFirst}
@@ -617,7 +643,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
left: bounds.x - this._resizeBorderWidth / 2,
top: bounds.y - this._resizeBorderWidth / 2,
pointerEvents: this.Interacting ? "none" : "all",
- zIndex: SelectionManager.SelectedDocuments().length > 1 ? 1000 : 0,
+ zIndex: SelectionManager.SelectedDocuments().length > 1 ? 900 : 0,
}} onPointerDown={this.onBackgroundDown} onContextMenu={(e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); }} >
</div>
<div className="documentDecorations-container" style={{
diff --git a/src/client/views/InkingCanvas.tsx b/src/client/views/InkingCanvas.tsx
index 5d4ea76cd..fd7e5b07d 100644
--- a/src/client/views/InkingCanvas.tsx
+++ b/src/client/views/InkingCanvas.tsx
@@ -14,6 +14,7 @@ import { Cast, PromiseValue, NumCast } from "../../new_fields/Types";
interface InkCanvasProps {
getScreenTransform: () => Transform;
Document: Doc;
+ inkFieldKey: string;
children: () => JSX.Element[];
}
@@ -40,7 +41,7 @@ export class InkingCanvas extends React.Component<InkCanvasProps> {
}
componentDidMount() {
- PromiseValue(Cast(this.props.Document.ink, InkField)).then(ink => runInAction(() => {
+ PromiseValue(Cast(this.props.Document[this.props.inkFieldKey], InkField)).then(ink => runInAction(() => {
if (ink) {
let bounds = Array.from(ink.inkData).reduce(([mix, max, miy, may], [id, strokeData]) =>
strokeData.pathData.reduce(([mix, max, miy, may], p) =>
@@ -55,12 +56,12 @@ export class InkingCanvas extends React.Component<InkCanvasProps> {
@computed
get inkData(): Map<string, StrokeData> {
- let map = Cast(this.props.Document.ink, InkField);
+ let map = Cast(this.props.Document[this.props.inkFieldKey], InkField);
return !map ? new Map : new Map(map.inkData);
}
set inkData(value: Map<string, StrokeData>) {
- Doc.GetProto(this.props.Document).ink = new InkField(value);
+ Doc.GetProto(this.props.Document)[this.props.inkFieldKey] = new InkField(value);
}
@action
diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx
index 0837e07a9..18128f72c 100644
--- a/src/client/views/InkingControl.tsx
+++ b/src/client/views/InkingControl.tsx
@@ -8,6 +8,7 @@ import { faPen, faHighlighter, faEraser, faBan } from '@fortawesome/free-solid-s
import { SelectionManager } from "../util/SelectionManager";
import { InkTool } from "../../new_fields/InkField";
import { Doc } from "../../new_fields/Doc";
+import { InkingCanvas } from "./InkingCanvas";
library.add(faPen, faHighlighter, faEraser, faBan);
@@ -39,7 +40,7 @@ export class InkingControl extends React.Component {
@action
switchColor = (color: ColorResult): void => {
this._selectedColor = color.hex + (color.rgb.a !== undefined ? this.decimalToHexString(Math.round(color.rgb.a * 255)) : "ff");
- SelectionManager.SelectedDocuments().forEach(doc => Doc.GetProto(doc.props.Document).backgroundColor = this._selectedColor);
+ if (InkingControl.Instance.selectedTool === InkTool.None) SelectionManager.SelectedDocuments().forEach(doc => (doc.props.Document.isTemplate ? doc.props.Document : Doc.GetProto(doc.props.Document)).backgroundColor = this._selectedColor);
}
@action
diff --git a/src/client/views/Main.scss b/src/client/views/Main.scss
index 690139341..0271edcd2 100644
--- a/src/client/views/Main.scss
+++ b/src/client/views/Main.scss
@@ -20,15 +20,7 @@ div {
-ms-user-select: none;
}
-#dash-title {
- position: absolute;
- right: 46.5%;
- letter-spacing: 3px;
- top: 9px;
- font-size: 12px;
- color: $alt-accent;
- z-index: 9999;
-}
+
.jsx-parser {
width: 100%;
@@ -43,8 +35,8 @@ p {
::-webkit-scrollbar {
-webkit-appearance: none;
- height: 10px;
- width: 10px;
+ height: 8px;
+ width: 8px;
}
::-webkit-scrollbar-thumb {
@@ -200,7 +192,7 @@ button:hover {
position: absolute;
top: 0;
left: 0;
- overflow: scroll;
+ overflow: auto;
z-index: 1;
}
@@ -210,6 +202,7 @@ button:hover {
position: absolute;
top: 0;
left: 0;
+ overflow: hidden;
}
#add-options-content {
diff --git a/src/client/views/MainOverlayTextBox.scss b/src/client/views/MainOverlayTextBox.scss
index f6a746e63..f636ca070 100644
--- a/src/client/views/MainOverlayTextBox.scss
+++ b/src/client/views/MainOverlayTextBox.scss
@@ -1,7 +1,7 @@
@import "globalCssVariables";
.mainOverlayTextBox-textInput {
background-color: rgba(248, 6, 6, 0.001);
- width: 200px;
+ width: 400px;
height: 200px;
position:absolute;
overflow: visible;
@@ -17,4 +17,9 @@
top: 0;
left: 0;
}
+}
+.mainOverlayTextBox-.unscaled_div{
+ z-index: 10000;
+ position: absolute;
+ pointer-events: none;
} \ No newline at end of file
diff --git a/src/client/views/MainOverlayTextBox.tsx b/src/client/views/MainOverlayTextBox.tsx
index b4ad5f4d7..b5680bd68 100644
--- a/src/client/views/MainOverlayTextBox.tsx
+++ b/src/client/views/MainOverlayTextBox.tsx
@@ -24,9 +24,11 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps>
private _textHideOnLeave?: boolean;
private _textTargetDiv: HTMLDivElement | undefined;
private _textProxyDiv: React.RefObject<HTMLDivElement>;
+ private _outerdiv = (dominus: HTMLElement | null) => this._dominus && dominus && dominus.appendChild(this._dominus);
private _textBottom: boolean | undefined;
private _textAutoHeight: boolean | undefined;
private _textBox: FormattedTextBox | undefined;
+ private _dominus?: HTMLElement;
@observable public TextDoc?: Doc;
constructor(props: MainOverlayTextBoxProps) {
@@ -37,7 +39,7 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps>
(box?: FormattedTextBox) => {
this._textBox = box;
if (box) {
- this.TextDoc = box.props.Document;
+ this.TextDoc = box.props.DataDoc;
let sxf = Utils.GetScreenTransform(box ? box.CurrentDiv : undefined);
let xf = () => { box.props.ScreenToLocalTransform(); return new Transform(-sxf.translateX, -sxf.translateY, 1 / sxf.scale); };
this.setTextDoc(box.props.fieldKey, box.CurrentDiv, xf, BoolCast(box.props.Document.autoHeight, false) || box.props.height === "min-content");
@@ -82,10 +84,10 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps>
}
@action
textBoxMove = (e: PointerEvent) => {
- if (e.movementX > 1 || e.movementY > 1) {
+ if ((e.movementX > 1 || e.movementY > 1) && FormattedTextBox.InputBoxOverlay) {
document.removeEventListener("pointermove", this.textBoxMove);
document.removeEventListener('pointerup', this.textBoxUp);
- let dragData = new DragManager.DocumentDragData(FormattedTextBox.InputBoxOverlay ? [FormattedTextBox.InputBoxOverlay.props.Document] : []);
+ let dragData = new DragManager.DocumentDragData([FormattedTextBox.InputBoxOverlay.props.Document], [FormattedTextBox.InputBoxOverlay.props.DataDoc]);
const [left, top] = this._textXf().inverse().transformPoint(0, 0);
dragData.xOffset = e.clientX - left;
dragData.yOffset = e.clientY - top;
@@ -102,9 +104,9 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps>
document.removeEventListener('pointerup', this.textBoxUp);
}
- addDocTab = (doc: Doc, location: string) => {
+ addDocTab = (doc: Doc, dataDoc: Doc, location: string) => {
if (true) { // location === "onRight") { need to figure out stack to add "inTab"
- CollectionDockingView.Instance.AddRightSplit(doc);
+ CollectionDockingView.Instance.AddRightSplit(doc, dataDoc);
}
}
render() {
@@ -114,14 +116,18 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps>
let s = this._textXf().Scale;
let location = this._textBottom ? textRect.bottom : textRect.top;
let hgt = this._textAutoHeight || this._textBottom ? "auto" : this._textTargetDiv.clientHeight;
- return <div className="mainOverlayTextBox-textInput" style={{ transform: `translate(${textRect.left}px, ${location}px) scale(${1 / s},${1 / s})`, width: "auto", height: "0px" }} >
- <div className="mainOverlayTextBox-textInput" onPointerDown={this.textBoxDown} ref={this._textProxyDiv} onScroll={this.textScroll}
- style={{ width: `${textRect.width * s}px`, height: "0px" }}>
- <div style={{ height: hgt, width: "100%", position: "absolute", bottom: this._textBottom ? "0px" : undefined }}>
- <FormattedTextBox color={`${this._textColor}`} fieldKey={this.TextFieldKey} hideOnLeave={this._textHideOnLeave} isOverlay={true} Document={FormattedTextBox.InputBoxOverlay.props.Document}
- isSelected={returnTrue} select={emptyFunction} isTopMost={true} selectOnLoad={true}
- ContainingCollectionView={undefined} whenActiveChanged={emptyFunction} active={returnTrue}
- ScreenToLocalTransform={this._textXf} PanelWidth={returnZero} PanelHeight={returnZero} focus={emptyFunction} addDocTab={this.addDocTab} />
+ return <div ref={this._outerdiv} className="mainOverlayTextBox-unscaled_div" style={{ transform: `translate(${textRect.left}px, ${location}px)` }} >
+ <div className="mainOverlayTextBox-textInput" style={{ transform: `scale(${1 / s},${1 / s})`, width: "auto", height: "0px" }} >
+ <div className="mainOverlayTextBox-textInput" onPointerDown={this.textBoxDown} ref={this._textProxyDiv} onScroll={this.textScroll}
+ style={{ width: `${textRect.width * s}px`, height: "0px" }}>
+ <div style={{ height: hgt, width: "100%", position: "absolute", bottom: this._textBottom ? "0px" : undefined }}>
+ <FormattedTextBox color={`${this._textColor}`} fieldKey={this.TextFieldKey} fieldExt="" hideOnLeave={this._textHideOnLeave} isOverlay={true}
+ Document={FormattedTextBox.InputBoxOverlay.props.Document}
+ DataDoc={FormattedTextBox.InputBoxOverlay.props.DataDoc}
+ isSelected={returnTrue} select={emptyFunction} renderDepth={0} selectOnLoad={true}
+ ContainingCollectionView={undefined} whenActiveChanged={emptyFunction} active={returnTrue}
+ ScreenToLocalTransform={this._textXf} PanelWidth={returnZero} PanelHeight={returnZero} focus={emptyFunction} addDocTab={this.addDocTab} outer_div={(dominus: HTMLElement) => this._dominus = dominus} />
+ </div>
</div>
</div>
</ div>;
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index b48ece30f..462ea1594 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -165,8 +165,13 @@ export class MainView extends React.Component {
const list = Cast(CurrentUserUtils.UserDocument.data, listSpec(Doc));
if (list) {
let freeformDoc = Docs.FreeformDocument([], { x: 0, y: 400, width: this.pwidth * .7, height: this.pheight, title: `WS collection ${list.length + 1}` });
- var dockingLayout = { content: [{ type: 'row', content: [CollectionDockingView.makeDocumentConfig(CurrentUserUtils.UserDocument, 150), CollectionDockingView.makeDocumentConfig(freeformDoc, 600)] }] };
+ var dockingLayout = { content: [{ type: 'row', content: [CollectionDockingView.makeDocumentConfig(CurrentUserUtils.UserDocument, CurrentUserUtils.UserDocument, 150), CollectionDockingView.makeDocumentConfig(freeformDoc, freeformDoc, 600)] }] };
let mainDoc = Docs.DockDocument([CurrentUserUtils.UserDocument, freeformDoc], JSON.stringify(dockingLayout), { title: `Workspace ${list.length + 1}` }, id);
+ if (!CurrentUserUtils.UserDocument.linkManagerDoc) {
+ let linkManagerDoc = new Doc();
+ linkManagerDoc.allLinks = new List<Doc>([]);
+ CurrentUserUtils.UserDocument.linkManagerDoc = linkManagerDoc;
+ }
list.push(mainDoc);
// bcz: strangely, we need a timeout to prevent exceptions/issues initializing GoldenLayout (the rendering engine for Main Container)
setTimeout(() => {
@@ -198,7 +203,7 @@ export class MainView extends React.Component {
openNotifsCol = () => {
if (this._notifsCol && CollectionDockingView.Instance) {
- CollectionDockingView.Instance.AddRightSplit(this._notifsCol);
+ CollectionDockingView.Instance.AddRightSplit(this._notifsCol, this._notifsCol);
}
}
@@ -224,6 +229,7 @@ export class MainView extends React.Component {
let mainCont = this.mainContainer;
let content = !mainCont ? (null) :
<DocumentView Document={mainCont}
+ DataDoc={undefined}
addDocument={undefined}
addDocTab={emptyFunction}
removeDocument={undefined}
@@ -231,7 +237,7 @@ export class MainView extends React.Component {
ContentScaling={returnOne}
PanelWidth={this.getPWidth}
PanelHeight={this.getPHeight}
- isTopMost={true}
+ renderDepth={0}
selectOnLoad={false}
focus={emptyFunction}
parentActive={returnTrue}
@@ -360,8 +366,8 @@ export class MainView extends React.Component {
globalKeyHandler = (e: KeyboardEvent) => {
if (e.key === "Control" || !e.ctrlKey) return;
- if(e.key === "v") return;
- if(e.key === "c") return;
+ if (e.key === "v") return;
+ if (e.key === "c") return;
e.preventDefault();
e.stopPropagation();
@@ -369,12 +375,12 @@ export class MainView extends React.Component {
switch (e.key) {
case "ArrowRight":
if (this.mainFreeform) {
- CollectionDockingView.Instance.AddRightSplit(this.mainFreeform!);
+ CollectionDockingView.Instance.AddRightSplit(this.mainFreeform, undefined);
}
break;
case "ArrowLeft":
if (this.mainFreeform) {
- CollectionDockingView.Instance.CloseRightSplit(this.mainFreeform!);
+ CollectionDockingView.Instance.CloseRightSplit(this.mainFreeform);
}
break;
case "o":
diff --git a/src/client/views/Templates.tsx b/src/client/views/Templates.tsx
index 3d5f7b6ea..5bb8d454a 100644
--- a/src/client/views/Templates.tsx
+++ b/src/client/views/Templates.tsx
@@ -49,8 +49,8 @@ export namespace Templates {
export const Title = new Template("Title", TemplatePosition.InnerTop,
`<div>
- <div style="height:25px; width:100%; background-color: rgba(0, 0, 0, .4); color: white; ">
- <span style="text-align:center;width:100%;font-size:20px;position:absolute;overflow:hidden;white-space:nowrap;text-overflow:ellipsis">{props.Document.title}</span>
+ <div style="height:25px; width:100%; background-color: rgba(0, 0, 0, .4); color: white; z-index: 100">
+ <span style="text-align:center;width:100%;font-size:20px;position:absolute;overflow:hidden;white-space:nowrap;text-overflow:ellipsis">{props.DataDoc.title}</span>
</div>
<div style="height:calc(100% - 25px);">
<div style="width:100%;overflow:auto">{layout}</div>
@@ -84,6 +84,16 @@ export namespace Templates {
</div > `);
}
+ export function TitleBar(datastring: string) {
+ return (`<div>
+ <div style="height:25px; width:100%; background-color: rgba(0, 0, 0, .4); color: white; z-index: 100">
+ <span style="text-align:center;width:100%;font-size:20px;position:absolute;overflow:hidden;white-space:nowrap;text-overflow:ellipsis">${datastring}</span>
+ </div>
+ <div style="height:calc(100% - 25px);">
+ <div style="width:100%;overflow:auto">{layout}</div>
+ </div>
+ </div>` );
+ }
export const TemplateList: Template[] = [Title, Header, Caption, Bullet];
export function sortTemplates(a: Template, b: Template) {
diff --git a/src/client/views/_nodeModuleOverrides.scss b/src/client/views/_nodeModuleOverrides.scss
index 6f97e60f8..3594ac9f4 100644
--- a/src/client/views/_nodeModuleOverrides.scss
+++ b/src/client/views/_nodeModuleOverrides.scss
@@ -3,7 +3,6 @@
// goldenlayout stuff
div .lm_header {
background: $dark-color;
- min-height: 2em;
}
.lm_tab {
diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx
index ae77ef42d..879898018 100644
--- a/src/client/views/collections/CollectionBaseView.tsx
+++ b/src/client/views/collections/CollectionBaseView.tsx
@@ -1,16 +1,16 @@
import { action, computed, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { Doc, DocListCast, Opt } from '../../../new_fields/Doc';
+import { Doc } from '../../../new_fields/Doc';
import { Id } from '../../../new_fields/FieldSymbols';
import { List } from '../../../new_fields/List';
import { listSpec } from '../../../new_fields/Schema';
-import { Cast, FieldValue, NumCast, PromiseValue } from '../../../new_fields/Types';
+import { BoolCast, Cast, NumCast, PromiseValue } from '../../../new_fields/Types';
+import { DocumentManager } from '../../util/DocumentManager';
import { SelectionManager } from '../../util/SelectionManager';
import { ContextMenu } from '../ContextMenu';
import { FieldViewProps } from '../nodes/FieldView';
import './CollectionBaseView.scss';
-import { DocumentManager } from '../../util/DocumentManager';
export enum CollectionViewType {
Invalid,
@@ -36,7 +36,6 @@ export interface CollectionViewProps extends FieldViewProps {
contentRef?: React.Ref<HTMLDivElement>;
}
-
@observer
export class CollectionBaseView extends React.Component<CollectionViewProps> {
@observable private static _safeMode = false;
@@ -60,10 +59,12 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
}
}
+ @computed get dataDoc() { return Doc.resolvedFieldDataDoc(BoolCast(this.props.Document.isTemplate) ? this.props.DataDoc ? this.props.DataDoc : this.props.Document : this.props.Document, this.props.fieldKey, this.props.fieldExt); }
+ @computed get dataField() { return this.props.fieldExt ? this.props.fieldExt : this.props.fieldKey; }
+
active = (): boolean => {
var isSelected = this.props.isSelected();
- var topMost = this.props.isTopMost;
- return isSelected || this._isChildActive || topMost;
+ return isSelected || this._isChildActive || this.props.renderDepth === 0;
}
//TODO should this be observable?
@@ -73,57 +74,21 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
this.props.whenActiveChanged(isActive);
}
- createsCycle(documentToAdd: Doc, containerDocument: Doc): boolean {
- if (!(documentToAdd instanceof Doc)) {
- return false;
- }
- let data = DocListCast(documentToAdd.data);
- for (const doc of data) {
- if (this.createsCycle(doc, containerDocument)) {
- return true;
- }
- }
- let annots = DocListCast(documentToAdd.annotations);
- for (const annot of annots) {
- if (this.createsCycle(annot, containerDocument)) {
- return true;
- }
- }
- for (let containerProto: Opt<Doc> = containerDocument; containerProto !== undefined; containerProto = FieldValue(containerProto.proto)) {
- if (containerProto[Id] === documentToAdd[Id]) {
- return true;
- }
- }
- return false;
- }
- @computed get isAnnotationOverlay() { return this.props.fieldKey === "annotations"; }
-
@action.bound
addDocument(doc: Doc, allowDuplicates: boolean = false): boolean {
- let props = this.props;
- var curPage = NumCast(props.Document.curPage, -1);
+ var curPage = NumCast(this.props.Document.curPage, -1);
Doc.GetProto(doc).page = curPage;
if (curPage >= 0) {
- Doc.GetProto(doc).annotationOn = props.Document;
+ Doc.GetProto(doc).annotationOn = this.props.Document;
}
- if (!this.createsCycle(doc, props.Document)) {
- //TODO This won't create the field if it doesn't already exist
- const value = Cast(props.Document[props.fieldKey], listSpec(Doc));
- let alreadyAdded = true;
- if (value !== undefined) {
- if (allowDuplicates || !value.some(v => v instanceof Doc && v[Id] === doc[Id])) {
- alreadyAdded = false;
- value.push(doc);
- }
- } else {
- alreadyAdded = false;
- Doc.SetOnPrototype(this.props.Document, this.props.fieldKey, new List([doc]));
- }
- // set the ZoomBasis only if hasn't already been set -- bcz: maybe set/resetting the ZoomBasis should be a parameter to addDocument?
- if (!alreadyAdded && (this.collectionViewType === CollectionViewType.Freeform || this.collectionViewType === CollectionViewType.Invalid)) {
- let zoom = NumCast(this.props.Document.scale, 1);
- // Doc.GetProto(doc).zoomBasis = zoom;
+ allowDuplicates = true;
+ const value = Cast(this.dataDoc[this.dataField], listSpec(Doc));
+ if (value !== undefined) {
+ if (allowDuplicates || !value.some(v => v instanceof Doc && v[Id] === doc[Id])) {
+ value.push(doc);
}
+ } else {
+ Doc.GetProto(this.dataDoc)[this.dataField] = new List([doc]);
}
return true;
}
@@ -132,22 +97,12 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
removeDocument(doc: Doc): boolean {
let docView = DocumentManager.Instance.getDocumentView(doc, this.props.ContainingCollectionView);
docView && SelectionManager.DeselectDoc(docView);
- const props = this.props;
//TODO This won't create the field if it doesn't already exist
- const value = Cast(props.Document[props.fieldKey], listSpec(Doc), []);
- let index = -1;
- for (let i = 0; i < value.length; i++) {
- let v = value[i];
- if (v instanceof Doc && v[Id] === doc[Id]) {
- index = i;
- break;
- }
- }
- PromiseValue(Cast(doc.annotationOn, Doc)).then(annotationOn => {
- if (annotationOn === props.Document) {
- doc.annotationOn = undefined;
- }
- });
+ const value = Cast(this.dataDoc[this.dataField], listSpec(Doc), []);
+ let index = value.reduce((p, v, i) => (v instanceof Doc && v[Id] === doc[Id]) ? i : p, -1);
+ PromiseValue(Cast(doc.annotationOn, Doc)).then(annotationOn =>
+ annotationOn === this.dataDoc.Document && (doc.annotationOn = undefined)
+ );
if (index !== -1) {
value.splice(index, 1);
@@ -161,7 +116,7 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
@action.bound
moveDocument(doc: Doc, targetCollection: Doc, addDocument: (doc: Doc) => boolean): boolean {
- if (Doc.AreProtosEqual(this.props.Document, targetCollection)) {
+ if (Doc.AreProtosEqual(this.dataDoc, targetCollection)) {
return true;
}
if (this.removeDocument(doc)) {
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index dd99649b8..e675e82fc 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -24,6 +24,7 @@ import { SubCollectionViewProps } from "./CollectionSubView";
import { ParentDocSelector } from './ParentDocumentSelector';
import React = require("react");
import { MainView } from '../MainView';
+import { LinkManager } from '../../util/LinkManager';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { library } from '@fortawesome/fontawesome-svg-core';
import { faFile } from '@fortawesome/free-solid-svg-icons';
@@ -32,7 +33,7 @@ library.add(faFile);
@observer
export class CollectionDockingView extends React.Component<SubCollectionViewProps> {
public static Instance: CollectionDockingView;
- public static makeDocumentConfig(document: Doc, width?: number) {
+ public static makeDocumentConfig(document: Doc, dataDoc: Doc | undefined, width?: number) {
return {
type: 'react-component',
component: 'DocumentFrameRenderer',
@@ -40,6 +41,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
width: width,
props: {
documentId: document[Id],
+ dataDocumentId: dataDoc ? dataDoc[Id] : ""
//collectionDockingView: CollectionDockingView.Instance
}
};
@@ -60,19 +62,19 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
}
hack: boolean = false;
undohack: any = null;
- public StartOtherDrag(dragDocs: Doc[], e: any) {
+ public StartOtherDrag(e: any, dragDocs: Doc[], dragDataDocs?: (Doc | undefined)[]) {
this.hack = true;
this.undohack = UndoManager.StartBatch("goldenDrag");
- dragDocs.map(dragDoc =>
- this.AddRightSplit(dragDoc, true).contentItems[0].tab._dragListener.
+ dragDocs.map((dragDoc, i) =>
+ this.AddRightSplit(dragDoc, dragDataDocs ? dragDataDocs[i] : dragDoc, true).contentItems[0].tab._dragListener.
onMouseDown({ pageX: e.pageX, pageY: e.pageY, preventDefault: emptyFunction, button: 0 }));
}
@action
- public OpenFullScreen(document: Doc) {
+ public OpenFullScreen(document: Doc, dataDoc: Doc) {
let newItemStackConfig = {
type: 'stack',
- content: [CollectionDockingView.makeDocumentConfig(document)]
+ content: [CollectionDockingView.makeDocumentConfig(document, dataDoc)]
};
var docconfig = this._goldenLayout.root.layoutManager.createContentItem(newItemStackConfig, this._goldenLayout);
this._goldenLayout.root.contentItems[0].addChild(docconfig);
@@ -127,14 +129,14 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
// Creates a vertical split on the right side of the docking view, and then adds the Document to that split
//
@action
- public AddRightSplit = (document: Doc, minimize: boolean = false) => {
+ public AddRightSplit = (document: Doc, dataDoc: Doc | undefined, minimize: boolean = false) => {
let docs = Cast(this.props.Document.data, listSpec(Doc));
if (docs) {
docs.push(document);
}
let newItemStackConfig = {
type: 'stack',
- content: [CollectionDockingView.makeDocumentConfig(document)]
+ content: [CollectionDockingView.makeDocumentConfig(document, dataDoc)]
};
var newContentItem = this._goldenLayout.root.layoutManager.createContentItem(newItemStackConfig, this._goldenLayout);
@@ -165,12 +167,12 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
return newContentItem;
}
@action
- public AddTab = (stack: any, document: Doc) => {
+ public AddTab = (stack: any, document: Doc, dataDocument: Doc) => {
let docs = Cast(this.props.Document.data, listSpec(Doc));
if (docs) {
docs.push(document);
}
- let docContentConfig = CollectionDockingView.makeDocumentConfig(document);
+ let docContentConfig = CollectionDockingView.makeDocumentConfig(document, dataDocument);
var newContentItem = stack.layoutManager.createContentItem(docContentConfig, this._goldenLayout);
stack.addChild(newContentItem.contentItems[0], undefined);
this.layoutChanged();
@@ -286,14 +288,16 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
let x = e.clientX;
let y = e.clientY;
let docid = (e.target as any).DashDocId;
+ let datadocid = (e.target as any).DashDataDocId;
let tab = (e.target as any).parentElement as HTMLElement;
let glTab = (e.target as any).Tab;
if (glTab && glTab.contentItem && glTab.contentItem.parent) {
glTab.contentItem.parent.setActiveContentItem(glTab.contentItem);
}
- DocServer.GetRefField(docid).then(action((f: Opt<Field>) => {
+ DocServer.GetRefField(docid).then(action(async (f: Opt<Field>) => {
if (f instanceof Doc) {
- DragManager.StartDocumentDrag([tab], new DragManager.DocumentDragData([f]), x, y,
+ let dataDoc = (datadocid !== docid) ? await DocServer.GetRefField(datadocid) : f;
+ DragManager.StartDocumentDrag([tab], new DragManager.DocumentDragData([f], [dataDoc instanceof Doc ? dataDoc : f]), x, y,
{
handlers: {
dragComplete: emptyFunction,
@@ -344,45 +348,45 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
if (tab.contentItem.config.fixed) {
tab.contentItem.parent.config.fixed = true;
}
- DocServer.GetRefField(tab.contentItem.config.props.documentId).then(async doc => {
- if (doc instanceof Doc) {
- let dragSpan = document.createElement("span");
- dragSpan.style.position = "relative";
- dragSpan.style.bottom = "6px";
- dragSpan.style.paddingLeft = "4px";
- dragSpan.style.paddingRight = "2px";
- let upDiv = document.createElement("span");
- const stack = tab.contentItem.parent;
- // shifts the focus to this tab when another tab is dragged over it
- tab.element[0].onmouseenter = (e: any) => {
- if (!this._isPointerDown) return;
- var activeContentItem = tab.header.parent.getActiveContentItem();
- if (tab.contentItem !== activeContentItem) {
- tab.header.parent.setActiveContentItem(tab.contentItem);
- }
- tab.setActive(true);
- };
- ReactDOM.render(<span onPointerDown={
- e => {
- e.preventDefault();
- e.stopPropagation();
- DragManager.StartDocumentDrag([dragSpan], new DragManager.DocumentDragData([doc]), e.clientX, e.clientY, {
- handlers: { dragComplete: emptyFunction },
- hideSource: false
- });
- }}><FontAwesomeIcon icon="file" size="lg" /></span>, dragSpan);
- ReactDOM.render(<ParentDocSelector Document={doc} addDocTab={doc => CollectionDockingView.Instance.AddTab(stack, doc)} />, upDiv);
- tab.reactComponents = [dragSpan, upDiv];
- tab.element.append(dragSpan);
- tab.element.append(upDiv);
- tab.reactionDisposer = reaction(() => [doc.title],
- () => {
- tab.titleElement[0].textContent = doc.title;
- }, { fireImmediately: true });
- //TODO why can't this just be doc instead of the id?
- tab.titleElement[0].DashDocId = tab.contentItem.config.props.documentId;
- }
- });
+ let doc = await DocServer.GetRefField(tab.contentItem.config.props.documentId) as Doc;
+ let dataDoc = await DocServer.GetRefField(tab.contentItem.config.props.dataDocumentId) as Doc;
+ if (doc instanceof Doc && dataDoc instanceof Doc) {
+ let dragSpan = document.createElement("span");
+ dragSpan.style.position = "relative";
+ dragSpan.style.bottom = "6px";
+ dragSpan.style.paddingLeft = "4px";
+ dragSpan.style.paddingRight = "2px";
+ let upDiv = document.createElement("span");
+ const stack = tab.contentItem.parent;
+ // shifts the focus to this tab when another tab is dragged over it
+ tab.element[0].onmouseenter = (e: any) => {
+ if (!this._isPointerDown) return;
+ var activeContentItem = tab.header.parent.getActiveContentItem();
+ if (tab.contentItem !== activeContentItem) {
+ tab.header.parent.setActiveContentItem(tab.contentItem);
+ }
+ tab.setActive(true);
+ };
+ ReactDOM.render(<span onPointerDown={
+ e => {
+ e.preventDefault();
+ e.stopPropagation();
+ DragManager.StartDocumentDrag([dragSpan], new DragManager.DocumentDragData([doc], [dataDoc]), e.clientX, e.clientY, {
+ handlers: { dragComplete: emptyFunction },
+ hideSource: false
+ });
+ }}><FontAwesomeIcon icon="file" size="lg" /></span>, dragSpan);
+ ReactDOM.render(<ParentDocSelector Document={doc} addDocTab={doc => CollectionDockingView.Instance.AddTab(stack, doc, dataDoc)} />, upDiv);
+ tab.reactComponents = [dragSpan, upDiv];
+ tab.element.append(dragSpan);
+ tab.element.append(upDiv);
+ tab.reactionDisposer = reaction(() => [doc.title],
+ () => {
+ tab.titleElement[0].textContent = doc.title;
+ }, { fireImmediately: true });
+ //TODO why can't this just be doc instead of the id?
+ tab.titleElement[0].DashDocId = tab.contentItem.config.props.documentId;
+ }
}
tab.titleElement[0].Tab = tab;
tab.closeElement.off('click') //unbind the current click handler
@@ -445,6 +449,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
interface DockedFrameProps {
documentId: FieldId;
+ dataDocumentId: FieldId;
//collectionDockingView: CollectionDockingView
}
@observer
@@ -453,6 +458,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
@observable private _panelWidth = 0;
@observable private _panelHeight = 0;
@observable private _document: Opt<Doc>;
+ @observable private _dataDoc: Opt<Doc>;
get _stack(): any {
let parent = (this.props as any).glContainer.parent.parent;
if (this._document && this._document.excludeFromLibrary && parent.parent && parent.parent.contentItems.length > 1) {
@@ -462,7 +468,12 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
}
constructor(props: any) {
super(props);
- DocServer.GetRefField(this.props.documentId).then(action((f: Opt<Field>) => this._document = f as Doc));
+ DocServer.GetRefField(this.props.documentId).then(action((f: Opt<Field>) => {
+ this._dataDoc = this._document = f as Doc;
+ if (this.props.dataDocumentId && this.props.documentId !== this.props.dataDocumentId) {
+ DocServer.GetRefField(this.props.dataDocumentId).then(action((f: Opt<Field>) => this._dataDoc = f as Doc));
+ }
+ }));
}
nativeWidth = () => NumCast(this._document!.nativeWidth, this._panelWidth);
@@ -499,23 +510,25 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
}
get previewPanelCenteringOffset() { return (this._panelWidth - this.nativeWidth() * this.contentScaling()) / 2; }
- addDocTab = (doc: Doc, location: string) => {
+ addDocTab = (doc: Doc, dataDoc: Doc, location: string) => {
if (doc.dockingConfig) {
MainView.Instance.openWorkspace(doc);
} else if (location === "onRight") {
- CollectionDockingView.Instance.AddRightSplit(doc);
+ CollectionDockingView.Instance.AddRightSplit(doc, dataDoc);
} else {
- CollectionDockingView.Instance.AddTab(this._stack, doc);
+ CollectionDockingView.Instance.AddTab(this._stack, doc, dataDoc);
}
}
get content() {
- if (!this._document) {
+ if (!this._document || !this._dataDoc) {
return (null);
}
return (
<div className="collectionDockingView-content" ref={this._mainCont}
- style={{ transform: `translate(${this.previewPanelCenteringOffset}px, 0px) scale(${this.scaleToFitMultiplier}, ${this.scaleToFitMultiplier})` }}>
- <DocumentView key={this._document[Id]} Document={this._document}
+ style={{ transform: `translate(${this.previewPanelCenteringOffset}px, 0px) scale(${this.scaleToFitMultiplier})` }}>
+ <DocumentView key={this._document[Id]}
+ Document={this._document}
+ DataDoc={this._dataDoc}
bringToFront={emptyFunction}
addDocument={undefined}
removeDocument={undefined}
@@ -523,7 +536,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
PanelWidth={this.nativeWidth}
PanelHeight={this.nativeHeight}
ScreenToLocalTransform={this.ScreenToLocalTransform}
- isTopMost={true}
+ renderDepth={0}
selectOnLoad={false}
parentActive={returnTrue}
whenActiveChanged={emptyFunction}
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 97781f722..98bf513bb 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -11,10 +11,10 @@ import { Doc, DocListCast, DocListCastAsync, Field } from "../../../new_fields/D
import { Id } from "../../../new_fields/FieldSymbols";
import { List } from "../../../new_fields/List";
import { listSpec } from "../../../new_fields/Schema";
-import { Cast, FieldValue, NumCast, StrCast } from "../../../new_fields/Types";
+import { Cast, FieldValue, NumCast, StrCast, BoolCast } from "../../../new_fields/Types";
import { Docs } from "../../documents/Documents";
import { Gateway } from "../../northstar/manager/Gateway";
-import { SetupDrag } from "../../util/DragManager";
+import { SetupDrag, DragManager } from "../../util/DragManager";
import { CompileScript } from "../../util/Scripting";
import { Transform } from "../../util/Transform";
import { COLLECTION_BORDER_WIDTH, MAX_ROW_HEIGHT } from '../../views/globalCssVariables.scss';
@@ -29,6 +29,8 @@ import "./CollectionSchemaView.scss";
import { CollectionSubView } from "./CollectionSubView";
import { CollectionVideoView } from "./CollectionVideoView";
import { CollectionView } from "./CollectionView";
+import { undoBatch } from "../../util/UndoManager";
+import { timesSeries } from "async";
library.add(faCog);
@@ -98,11 +100,13 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
renderCell = (rowProps: CellInfo) => {
let props: FieldViewProps = {
Document: rowProps.original,
+ DataDoc: rowProps.original,
fieldKey: rowProps.column.id as string,
+ fieldExt: "",
ContainingCollectionView: this.props.CollectionView,
isSelected: returnFalse,
select: emptyFunction,
- isTopMost: false,
+ renderDepth: this.props.renderDepth + 1,
selectOnLoad: false,
ScreenToLocalTransform: Transform.Identity,
focus: emptyFunction,
@@ -114,9 +118,10 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
};
let fieldContentView = <FieldView {...props} />;
let reference = React.createRef<HTMLDivElement>();
- let onItemDown = (e: React.PointerEvent) =>
+ let onItemDown = (e: React.PointerEvent) => {
(this.props.CollectionView.props.isSelected() ?
SetupDrag(reference, () => props.Document, this.props.moveDocument, this.props.Document.schemaDoc ? "copy" : undefined)(e) : undefined);
+ };
let applyToDoc = (doc: Doc, run: (args?: { [name: string]: any }) => any) => {
const res = run({ this: doc });
if (!res.success) return false;
@@ -280,9 +285,9 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
@computed
get previewDocument(): Doc | undefined {
- const children = DocListCast(this.props.Document[this.props.fieldKey]);
- const selected = children.length > this._selectedIndex ? FieldValue(children[this._selectedIndex]) : undefined;
- return selected ? (this.previewScript && this.previewScript !== "this" ? FieldValue(Cast(selected[this.previewScript], Doc)) : selected) : undefined;
+ const selected = this.childDocs.length > this._selectedIndex ? this.childDocs[this._selectedIndex] : undefined;
+ let pdc = selected ? (this.previewScript && this.previewScript !== "this" ? FieldValue(Cast(selected[this.previewScript], Doc)) : selected) : undefined;
+ return pdc;
}
getPreviewTransform = (): Transform => this.props.ScreenToLocalTransform().translate(
@@ -346,22 +351,26 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
@computed
get previewPanel() {
- trace();
- return <CollectionSchemaPreview
- Document={this.previewDocument}
- width={this.previewWidth}
- height={this.previewHeight}
- getTransform={this.getPreviewTransform}
- CollectionView={this.props.CollectionView}
- moveDocument={this.props.moveDocument}
- addDocument={this.props.addDocument}
- removeDocument={this.props.removeDocument}
- active={this.props.active}
- whenActiveChanged={this.props.whenActiveChanged}
- addDocTab={this.props.addDocTab}
- setPreviewScript={this.setPreviewScript}
- previewScript={this.previewScript}
- />;
+ return <div ref={this.createTarget}>
+ <CollectionSchemaPreview
+ Document={this.previewDocument}
+ DataDocument={BoolCast(this.props.Document.isTemplate) ? this.previewDocument : this.props.DataDoc}
+ childDocs={this.childDocs}
+ renderDepth={this.props.renderDepth}
+ width={this.previewWidth}
+ height={this.previewHeight}
+ getTransform={this.getPreviewTransform}
+ CollectionView={this.props.CollectionView}
+ moveDocument={this.props.moveDocument}
+ addDocument={this.props.addDocument}
+ removeDocument={this.props.removeDocument}
+ active={this.props.active}
+ whenActiveChanged={this.props.whenActiveChanged}
+ addDocTab={this.props.addDocTab}
+ setPreviewScript={this.setPreviewScript}
+ previewScript={this.previewScript}
+ />
+ </div>;
}
@action
setPreviewScript = (script: string) => {
@@ -383,6 +392,9 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
}
interface CollectionSchemaPreviewProps {
Document?: Doc;
+ DataDocument?: Doc;
+ childDocs?: Doc[];
+ renderDepth: number;
width: () => number;
height: () => number;
CollectionView?: CollectionView | CollectionPDFView | CollectionVideoView;
@@ -392,13 +404,15 @@ interface CollectionSchemaPreviewProps {
removeDocument: (document: Doc) => boolean;
active: () => boolean;
whenActiveChanged: (isActive: boolean) => void;
- addDocTab: (document: Doc, where: string) => void;
+ addDocTab: (document: Doc, dataDoc: Doc, where: string) => void;
setPreviewScript: (script: string) => void;
previewScript?: string;
}
@observer
export class CollectionSchemaPreview extends React.Component<CollectionSchemaPreviewProps>{
+ private dropDisposer?: DragManager.DragDropDisposer;
+ _mainCont?: HTMLDivElement;
private get nativeWidth() { return NumCast(this.props.Document!.nativeWidth, this.props.width()); }
private get nativeHeight() { return NumCast(this.props.Document!.nativeHeight, this.props.height()); }
private contentScaling = () => {
@@ -408,26 +422,55 @@ export class CollectionSchemaPreview extends React.Component<CollectionSchemaPre
}
return wscale;
}
- private PanelWidth = () => this.nativeWidth * this.contentScaling();
- private PanelHeight = () => this.nativeHeight * this.contentScaling();
+ protected createDropTarget = (ele: HTMLDivElement) => {
+ }
+ private createTarget = (ele: HTMLDivElement) => {
+ this._mainCont = ele;
+ this.dropDisposer && this.dropDisposer();
+ if (ele) {
+ this.dropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.drop.bind(this) } });
+ }
+ }
+
+ @undoBatch
+ @action
+ drop = (e: Event, de: DragManager.DropEvent) => {
+ if (de.data instanceof DragManager.DocumentDragData) {
+ let docDrag = de.data;
+ this.props.childDocs && this.props.childDocs.map(otherdoc => {
+ Doc.GetProto(otherdoc).layout = Doc.MakeDelegate(docDrag.draggedDocuments[0]);
+ });
+ e.stopPropagation();
+ }
+ return true;
+ }
+ private PanelWidth = () => this.nativeWidth ? this.nativeWidth * this.contentScaling() : this.props.width();
+ private PanelHeight = () => this.nativeHeight ? this.nativeHeight * this.contentScaling() : this.props.height();
private getTransform = () => this.props.getTransform().translate(-this.centeringOffset, 0).scale(1 / this.contentScaling());
- get centeringOffset() { return (this.props.width() - this.nativeWidth * this.contentScaling()) / 2; }
+ get centeringOffset() { return this.nativeWidth ? (this.props.width() - this.nativeWidth * this.contentScaling()) / 2 : 0; }
@action
onPreviewScriptChange = (e: React.ChangeEvent<HTMLInputElement>) => {
this.props.setPreviewScript(e.currentTarget.value);
}
render() {
let input = this.props.previewScript === undefined ? (null) :
- <input className="collectionSchemaView-input" value={this.props.previewScript} onChange={this.onPreviewScriptChange}
- style={{ left: `calc(50% - ${Math.min(75, (this.props.Document ? this.PanelWidth() / 2 : 75))}px)` }} />;
+ <div ref={this.createTarget}><input className="collectionSchemaView-input" value={this.props.previewScript} onChange={this.onPreviewScriptChange}
+ style={{ left: `calc(50% - ${Math.min(75, (this.props.Document ? this.PanelWidth() / 2 : 75))}px)` }} /></div>;
return (<div className="collectionSchemaView-previewRegion" style={{ width: this.props.width(), height: "100%" }}>
{!this.props.Document || !this.props.width ? (null) : (
<div className="collectionSchemaView-previewDoc" style={{ transform: `translate(${this.centeringOffset}px, 0px)`, height: "100%" }}>
- <DocumentView Document={this.props.Document} isTopMost={false} selectOnLoad={false}
- addDocument={this.props.addDocument} removeDocument={this.props.removeDocument} moveDocument={this.props.moveDocument}
+ <DocumentView
+ DataDoc={this.props.Document.layout instanceof Doc ? this.props.Document : this.props.DataDocument}
+ Document={this.props.Document}
+ renderDepth={this.props.renderDepth + 1}
+ selectOnLoad={false}
+ addDocument={this.props.addDocument}
+ removeDocument={this.props.removeDocument}
+ moveDocument={this.props.moveDocument}
ScreenToLocalTransform={this.getTransform}
ContentScaling={this.contentScaling}
- PanelWidth={this.PanelWidth} PanelHeight={this.PanelHeight}
+ PanelWidth={this.PanelWidth}
+ PanelHeight={this.PanelHeight}
ContainingCollectionView={this.props.CollectionView}
focus={emptyFunction}
parentActive={this.props.active}
diff --git a/src/client/views/collections/CollectionStackingView.scss b/src/client/views/collections/CollectionStackingView.scss
index 485ecf1de..034a09eaa 100644
--- a/src/client/views/collections/CollectionStackingView.scss
+++ b/src/client/views/collections/CollectionStackingView.scss
@@ -39,6 +39,13 @@
color: $light-color;
}
+ .collectionStackingView-columnDragger {
+ width: 15;
+ height: 15;
+ position: absolute;
+ margin-left: -5;
+ }
+
.collectionStackingView-columnDoc,
.collectionStackingView-masonryDoc {
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 597e4795c..6c4ea18a1 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -1,20 +1,20 @@
import React = require("react");
-import { action, computed, IReactionDisposer, reaction, trace } from "mobx";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { action, computed, IReactionDisposer, reaction } from "mobx";
import { observer } from "mobx-react";
import { Doc, HeightSym, WidthSym } from "../../../new_fields/Doc";
import { Id } from "../../../new_fields/FieldSymbols";
import { BoolCast, NumCast } from "../../../new_fields/Types";
-import { emptyFunction, returnOne, Utils } from "../../../Utils";
+import { emptyFunction, Utils } from "../../../Utils";
import { ContextMenu } from "../ContextMenu";
-import { DocumentView } from "../nodes/DocumentView";
import { CollectionSchemaPreview } from "./CollectionSchemaView";
import "./CollectionStackingView.scss";
import { CollectionSubView } from "./CollectionSubView";
-import { Transform } from "../../util/Transform";
@observer
export class CollectionStackingView extends CollectionSubView(doc => doc) {
_masonryGridRef: HTMLDivElement | null = null;
+ _draggerRef = React.createRef<HTMLDivElement>();
_heightDisposer?: IReactionDisposer;
_gridSize = 1;
@computed get xMargin() { return NumCast(this.props.Document.xMargin, 2 * this.gridGap); }
@@ -76,6 +76,8 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
style={{ width: width(), height: height() }} >
<CollectionSchemaPreview
Document={d}
+ DataDocument={this.props.Document.layout instanceof Doc ? this.props.Document : this.props.DataDoc}
+ renderDepth={this.props.renderDepth}
width={width}
height={height}
getTransform={dxf}
@@ -107,6 +109,8 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
style={{ gridRowEnd: `span ${rowSpan}` }} >
<CollectionSchemaPreview
Document={d}
+ DataDocument={this.props.Document.layout instanceof Doc ? this.props.Document : this.props.DataDoc}
+ renderDepth={this.props.renderDepth}
CollectionView={this.props.CollectionView}
addDocument={this.props.addDocument}
moveDocument={this.props.moveDocument}
@@ -123,6 +127,35 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
</div>);
});
}
+
+ _columnStart: number = 0;
+ columnDividerDown = (e: React.PointerEvent) => {
+ e.stopPropagation();
+ e.preventDefault();
+ document.addEventListener("pointermove", this.onDividerMove);
+ document.addEventListener('pointerup', this.onDividerUp);
+ this._columnStart = this.props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY)[0];
+ }
+ @action
+ onDividerMove = (e: PointerEvent): void => {
+ let dragPos = this.props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY)[0];
+ let delta = dragPos - this._columnStart;
+ this._columnStart = dragPos;
+
+ this.props.Document.columnWidth = this.columnWidth + delta;
+ }
+
+ @action
+ onDividerUp = (e: PointerEvent): void => {
+ document.removeEventListener("pointermove", this.onDividerMove);
+ document.removeEventListener('pointerup', this.onDividerUp);
+ }
+
+ @computed get columnDragger() {
+ return <div className="collectionStackingView-columnDragger" onPointerDown={this.columnDividerDown} ref={this._draggerRef} style={{ left: `${this.columnWidth + this.xMargin}px` }} >
+ <FontAwesomeIcon icon={"caret-down"} />
+ </div>;
+ }
onContextMenu = (e: React.MouseEvent): void => {
if (!e.isPropagationStopped() && this.props.Document[Id] !== "mainDoc") { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
ContextMenu.Instance.addItem({
@@ -139,7 +172,6 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
return (
<div className="collectionStackingView" ref={this.createRef} onContextMenu={this.onContextMenu} onWheel={(e: React.WheelEvent) => e.stopPropagation()} >
<div className={`collectionStackingView-masonry${this.singleColumn ? "Single" : "Grid"}`}
-
style={{
padding: this.singleColumn ? `${this.yMargin}px ${this.xMargin}px ${this.yMargin}px ${this.xMargin}px` : `${this.yMargin}px ${this.xMargin}px`,
margin: "auto",
@@ -152,6 +184,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
}}
>
{this.singleColumn ? this.singleColumnChildren : this.children}
+ {this.singleColumn ? (null) : this.columnDragger}
</div>
</div>
);
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 699bddc7c..3b3bbdbd9 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -1,10 +1,11 @@
-import { action } from "mobx";
+import { action, computed } from "mobx";
import * as rp from 'request-promise';
import CursorField from "../../../new_fields/CursorField";
import { Doc, DocListCast, Opt } from "../../../new_fields/Doc";
+import { Id } from "../../../new_fields/FieldSymbols";
import { List } from "../../../new_fields/List";
import { listSpec } from "../../../new_fields/Schema";
-import { Cast, PromiseValue } from "../../../new_fields/Types";
+import { BoolCast, Cast } from "../../../new_fields/Types";
import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
import { RouteStore } from "../../../server/RouteStore";
import { DocServer } from "../../DocServer";
@@ -13,12 +14,11 @@ import { DragManager } from "../../util/DragManager";
import { undoBatch, UndoManager } from "../../util/UndoManager";
import { DocComponent } from "../DocComponent";
import { FieldViewProps } from "../nodes/FieldView";
+import { FormattedTextBox } from "../nodes/FormattedTextBox";
import { CollectionPDFView } from "./CollectionPDFView";
import { CollectionVideoView } from "./CollectionVideoView";
import { CollectionView } from "./CollectionView";
import React = require("react");
-import { FormattedTextBox } from "../nodes/FormattedTextBox";
-import { Id } from "../../../new_fields/FieldSymbols";
export interface CollectionViewProps extends FieldViewProps {
addDocument: (document: Doc, allowDuplicates?: boolean) => boolean;
@@ -45,10 +45,13 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
this.createDropTarget(ele);
}
+ @computed get extensionDoc() { return Doc.resolvedFieldDataDoc(BoolCast(this.props.Document.isTemplate) && this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey, this.props.fieldExt); }
+
get childDocs() {
+ let self = this;
//TODO tfs: This might not be what we want?
//This linter error can't be fixed because of how js arguments work, so don't switch this to filter(FieldValue)
- return DocListCast(this.props.Document[this.props.fieldKey]);
+ return DocListCast((BoolCast(this.props.Document.isTemplate) ? this.extensionDoc : this.props.Document)[this.props.fieldExt ? this.props.fieldExt : this.props.fieldKey]);
}
@action
diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss
index a85604e58..5205f4313 100644
--- a/src/client/views/collections/CollectionTreeView.scss
+++ b/src/client/views/collections/CollectionTreeView.scss
@@ -43,13 +43,12 @@
display: inline;
}
-
- .coll-title {
- width: max-content;
+ .editableView-input, .editableView-container-editing {
display: block;
+ text-overflow: ellipsis;
font-size: 24px;
+ white-space: nowrap;
}
-
}
.collectionTreeView-keyHeader {
font-style: italic;
@@ -59,6 +58,12 @@
background: lightgray;
}
+.collectionTreeView-subtitle {
+ font-style: italic;
+ font-size: 8pt;
+ color: $intermediate-color;
+}
+
.docContainer {
position: relative;
text-overflow: ellipsis;
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 18654905f..5c80fbd38 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -1,7 +1,7 @@
import { library } from '@fortawesome/fontawesome-svg-core';
-import { faAngleRight, faCaretDown, faCaretRight, faTrashAlt, faCaretSquareRight, faCaretSquareDown } from '@fortawesome/free-solid-svg-icons';
+import { faAngleRight, faCaretDown, faCaretRight, faCaretSquareDown, faCaretSquareRight, faTrashAlt } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, observable, computed } from "mobx";
+import { action, computed, observable } from "mobx";
import { observer } from "mobx-react";
import { Doc, DocListCast, HeightSym, WidthSym } from '../../../new_fields/Doc';
import { Id } from '../../../new_fields/FieldSymbols';
@@ -9,7 +9,7 @@ import { List } from '../../../new_fields/List';
import { Document, listSpec } from '../../../new_fields/Schema';
import { BoolCast, Cast, NumCast, StrCast } from '../../../new_fields/Types';
import { emptyFunction, Utils } from '../../../Utils';
-import { Docs } from '../../documents/Documents';
+import { Docs, DocUtils } from '../../documents/Documents';
import { DocumentManager } from '../../util/DocumentManager';
import { DragManager, dropActionType, SetupDrag } from "../../util/DragManager";
import { SelectionManager } from '../../util/SelectionManager';
@@ -25,14 +25,18 @@ import { CollectionSchemaPreview } from './CollectionSchemaView';
import { CollectionSubView } from "./CollectionSubView";
import "./CollectionTreeView.scss";
import React = require("react");
+import { LinkManager } from '../../util/LinkManager';
export interface TreeViewProps {
document: Doc;
+ dataDoc?: Doc;
+ containingCollection: Doc;
+ renderDepth: number;
deleteDoc: (doc: Doc) => boolean;
moveDocument: DragManager.MoveFunction;
dropAction: "alias" | "copy" | undefined;
- addDocTab: (doc: Doc, where: string) => void;
+ addDocTab: (doc: Doc, dataDoc: Doc, where: string) => void;
panelWidth: () => number;
panelHeight: () => number;
addDocument: (doc: Doc, relativeTo?: Doc, before?: boolean) => boolean;
@@ -59,9 +63,32 @@ class TreeView extends React.Component<TreeViewProps> {
private _header?: React.RefObject<HTMLDivElement> = React.createRef();
private _treedropDisposer?: DragManager.DragDropDisposer;
private _dref = React.createRef<HTMLDivElement>();
- @observable _chosenKey: string = "data";
+ @observable __chosenKey: string = "";
+ @computed get _chosenKey() { return this.__chosenKey ? this.__chosenKey : this.fieldKey; }
@observable _collapsed: boolean = true;
+ @computed get fieldKey() {
+ let keys = Array.from(Object.keys(this.resolvedDataDoc));
+ if (this.resolvedDataDoc.proto instanceof Doc) {
+ keys.push(...Array.from(Object.keys(this.resolvedDataDoc.proto)));
+ while (keys.indexOf("proto") !== -1) keys.splice(keys.indexOf("proto"), 1);
+ }
+ let keyList: string[] = [];
+ keys.map(key => {
+ let docList = Cast(this.resolvedDataDoc[key], listSpec(Doc));
+ if (docList && docList.length > 0) {
+ keyList.push(key);
+ }
+ });
+ let layout = StrCast(this.props.document.layout);
+ if (layout.indexOf("fieldKey={\"") !== -1) {
+ return layout.split("fieldKey={\"")[1].split("\"")[0];
+ }
+ return keyList.length ? keyList[0] : "data";
+ }
+
+ @computed get resolvedDataDoc() { return BoolCast(this.props.document.isTemplate) && this.props.dataDoc ? this.props.dataDoc : this.props.document; }
+
protected createTreeDropTarget = (ele: HTMLDivElement) => {
this._treedropDisposer && this._treedropDisposer();
if (ele) {
@@ -69,8 +96,8 @@ class TreeView extends React.Component<TreeViewProps> {
}
}
- @undoBatch delete = () => this.props.deleteDoc(this.props.document);
- @undoBatch openRight = async () => this.props.addDocTab(this.props.document, "onRight");
+ @undoBatch delete = () => this.props.deleteDoc(this.resolvedDataDoc);
+ @undoBatch openRight = async () => this.props.addDocTab(this.props.document, this.props.document, "onRight");
onPointerDown = (e: React.PointerEvent) => e.stopPropagation();
onPointerEnter = (e: React.PointerEvent): void => {
@@ -101,7 +128,7 @@ class TreeView extends React.Component<TreeViewProps> {
@action
remove = (document: Document, key: string): boolean => {
- let children = Cast(this.props.document[key], listSpec(Doc), []);
+ let children = Cast(this.resolvedDataDoc[key], listSpec(Doc), []);
if (children.indexOf(document) !== -1) {
children.splice(children.indexOf(document), 1);
return true;
@@ -117,8 +144,8 @@ class TreeView extends React.Component<TreeViewProps> {
indent = () => this.props.addDocument(this.props.document) && this.delete()
renderBullet() {
- let docList = Cast(this.props.document.data, listSpec(Doc));
- let doc = Cast(this.props.document.data, Doc);
+ let docList = Cast(this.resolvedDataDoc[this.fieldKey], listSpec(Doc));
+ let doc = Cast(this.resolvedDataDoc[this.fieldKey], Doc);
let isDoc = doc instanceof Doc || docList;
return <div className="bullet" onClick={action(() => this._collapsed = !this._collapsed)}>
{<FontAwesomeIcon icon={this._collapsed ? (isDoc ? "caret-square-right" : "caret-right") : (isDoc ? "caret-square-down" : "caret-down")} />}
@@ -136,15 +163,15 @@ class TreeView extends React.Component<TreeViewProps> {
editableView = (key: string, style?: string) => (<EditableView
oneLine={true}
display={"inline"}
- editing={this.props.document[Id] === TreeView.loadId}
+ editing={this.resolvedDataDoc[Id] === TreeView.loadId}
contents={StrCast(this.props.document[key])}
onClick={this.titleClicked}
height={36}
fontStyle={style}
GetValue={() => StrCast(this.props.document[key])}
- SetValue={(value: string) => (Doc.GetProto(this.props.document)[key] = value) ? true : true}
+ SetValue={(value: string) => (Doc.GetProto(this.resolvedDataDoc)[key] = value) ? true : true}
OnFillDown={(value: string) => {
- Doc.GetProto(this.props.document)[key] = value;
+ Doc.GetProto(this.resolvedDataDoc)[key] = value;
let doc = Docs.FreeformDocument([], { title: "", x: 0, y: 0, width: 100, height: 25, templates: new List<string>([Templates.Title.Layout]) });
TreeView.loadId = doc[Id];
return this.props.addDocument(doc);
@@ -153,23 +180,18 @@ class TreeView extends React.Component<TreeViewProps> {
/>)
@computed get keyList() {
- let keys = Array.from(Object.keys(this.props.document));
- if (this.props.document.proto instanceof Doc) {
- keys.push(...Array.from(Object.keys(this.props.document.proto)));
+ let keys = Array.from(Object.keys(this.resolvedDataDoc));
+ if (this.resolvedDataDoc.proto instanceof Doc) {
+ keys.push(...Array.from(Object.keys(this.resolvedDataDoc.proto)));
while (keys.indexOf("proto") !== -1) keys.splice(keys.indexOf("proto"), 1);
}
- let keyList: string[] = [];
- keys.map(key => {
- let docList = Cast(this.props.document[key], listSpec(Doc));
- let doc = Cast(this.props.document[key], Doc);
- if (doc instanceof Doc || docList) {
- keyList.push(key);
- }
- });
- if (keyList.indexOf("data") !== -1) {
- keyList.splice(keyList.indexOf("data"), 1);
+ let keyList: string[] = keys.reduce((l, key) => Cast(this.resolvedDataDoc[key], listSpec(Doc)) ? [...l, key] : l, [] as string[]);
+ keys.map(key => Cast(this.resolvedDataDoc[key], Doc) instanceof Doc && keyList.push(key));
+ if (LinkManager.Instance.getAllRelatedLinks(this.props.document).length > 0) keyList.push("links");
+ if (keyList.indexOf(this.fieldKey) !== -1) {
+ keyList.splice(keyList.indexOf(this.fieldKey), 1);
}
- keyList.splice(0, 0, "data");
+ keyList.splice(0, 0, this.fieldKey);
return keyList;
}
/**
@@ -177,19 +199,19 @@ class TreeView extends React.Component<TreeViewProps> {
*/
renderTitle() {
let reference = React.createRef<HTMLDivElement>();
- let onItemDown = SetupDrag(reference, () => this.props.document, this.move, this.props.dropAction, this.props.treeViewId, true);
+ let onItemDown = SetupDrag(reference, () => this.resolvedDataDoc, this.move, this.props.dropAction, this.props.treeViewId, true);
let headerElements = (
<span className="collectionTreeView-keyHeader" key={this._chosenKey}
onPointerDown={action(() => {
let ind = this.keyList.indexOf(this._chosenKey);
ind = (ind + 1) % this.keyList.length;
- this._chosenKey = this.keyList[ind];
+ this.__chosenKey = this.keyList[ind];
})} >
{this._chosenKey}
</span>);
- let dataDocs = CollectionDockingView.Instance ? Cast(CollectionDockingView.Instance.props.Document.data, listSpec(Doc), []) : [];
- let openRight = dataDocs && dataDocs.indexOf(this.props.document) !== -1 ? (null) : (
+ let dataDocs = CollectionDockingView.Instance ? Cast(CollectionDockingView.Instance.props.Document[this.fieldKey], listSpec(Doc), []) : [];
+ let openRight = dataDocs && dataDocs.indexOf(this.resolvedDataDoc) !== -1 ? (null) : (
<div className="treeViewItem-openRight" onPointerDown={this.onPointerDown} onClick={this.openRight}>
<FontAwesomeIcon icon="angle-right" size="lg" />
</div>);
@@ -210,13 +232,13 @@ class TreeView extends React.Component<TreeViewProps> {
onWorkspaceContextMenu = (e: React.MouseEvent): void => {
if (!e.isPropagationStopped()) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
- ContextMenu.Instance.addItem({ description: "Open as Workspace", event: undoBatch(() => MainView.Instance.openWorkspace(this.props.document)) });
- ContextMenu.Instance.addItem({ description: "Open Fields", event: () => this.props.addDocTab(Docs.KVPDocument(this.props.document, { width: 300, height: 300 }), "onRight"), icon: "layer-group" });
+ ContextMenu.Instance.addItem({ description: "Open as Workspace", event: undoBatch(() => MainView.Instance.openWorkspace(this.resolvedDataDoc)) });
+ ContextMenu.Instance.addItem({ description: "Open Fields", event: () => { let kvp = Docs.KVPDocument(this.props.document, { width: 300, height: 300 }); this.props.addDocTab(kvp, kvp, "onRight"); }, icon: "layer-group" });
if (NumCast(this.props.document.viewType) !== CollectionViewType.Docking) {
- ContextMenu.Instance.addItem({ description: "Open Tab", event: () => this.props.addDocTab(this.props.document, "inTab"), icon: "folder" });
- ContextMenu.Instance.addItem({ description: "Open Right", event: () => this.props.addDocTab(this.props.document, "onRight"), icon: "caret-square-right" });
- if (DocumentManager.Instance.getDocumentViews(this.props.document).length) {
- ContextMenu.Instance.addItem({ description: "Focus", event: () => DocumentManager.Instance.getDocumentViews(this.props.document).map(view => view.props.focus(this.props.document, false)) });
+ ContextMenu.Instance.addItem({ description: "Open Tab", event: () => this.props.addDocTab(this.props.document, this.resolvedDataDoc, "inTab"), icon: "folder" });
+ ContextMenu.Instance.addItem({ description: "Open Right", event: () => this.props.addDocTab(this.props.document, this.resolvedDataDoc, "onRight"), icon: "caret-square-right" });
+ if (DocumentManager.Instance.getDocumentViews(this.resolvedDataDoc).length) {
+ ContextMenu.Instance.addItem({ description: "Focus", event: () => DocumentManager.Instance.getDocumentViews(this.resolvedDataDoc).map(view => view.props.focus(this.props.document, true)) });
}
ContextMenu.Instance.addItem({ description: "Delete Item", event: undoBatch(() => this.props.deleteDoc(this.props.document)) });
} else {
@@ -232,10 +254,16 @@ class TreeView extends React.Component<TreeViewProps> {
let bounds = this.props.ScreenToLocalTransform().transformPoint(rect.left, rect.top + rect.height / 2);
let before = x[1] < bounds[1];
let inside = x[0] > bounds[0] + 75 || (!before && !this._collapsed);
+ if (de.data instanceof DragManager.LinkDragData) {
+ let sourceDoc = de.data.linkSourceDocument;
+ let destDoc = this.props.document;
+ DocUtils.MakeLink(sourceDoc, destDoc);
+ e.stopPropagation();
+ }
if (de.data instanceof DragManager.DocumentDragData) {
- let addDoc = (doc: Doc) => this.props.addDocument(doc, this.props.document, before);
+ let addDoc = (doc: Doc) => this.props.addDocument(doc, this.resolvedDataDoc, before);
if (inside) {
- let docList = Cast(this.props.document.data, listSpec(Doc));
+ let docList = Cast(this.resolvedDataDoc.data, listSpec(Doc));
if (docList !== undefined) {
addDoc = (doc: Doc) => { docList && docList.push(doc); return true; };
}
@@ -243,10 +271,10 @@ class TreeView extends React.Component<TreeViewProps> {
e.stopPropagation();
let movedDocs = (de.data.options === this.props.treeViewId ? de.data.draggedDocuments : de.data.droppedDocuments);
return (de.data.dropAction || de.data.userDropAction) ?
- de.data.droppedDocuments.reduce((added: boolean, d) => this.props.addDocument(d, this.props.document, before) || added, false)
+ de.data.droppedDocuments.reduce((added: boolean, d) => this.props.addDocument(d, this.resolvedDataDoc, before) || added, false)
: (de.data.moveDocument) ?
- movedDocs.reduce((added: boolean, d) => de.data.moveDocument(d, this.props.document, addDoc) || added, false)
- : de.data.droppedDocuments.reduce((added: boolean, d) => this.props.addDocument(d, this.props.document, before), false);
+ movedDocs.reduce((added: boolean, d) => de.data.moveDocument(d, this.resolvedDataDoc, addDoc) || added, false)
+ : de.data.droppedDocuments.reduce((added: boolean, d) => this.props.addDocument(d, this.resolvedDataDoc, before), false);
}
return false;
}
@@ -259,23 +287,47 @@ class TreeView extends React.Component<TreeViewProps> {
return finalXf;
}
+ renderLinks = () => {
+ let ele: JSX.Element[] = [];
+ let remDoc = (doc: Doc) => this.remove(doc, this._chosenKey);
+ let addDoc = (doc: Doc, addBefore?: Doc, before?: boolean) => Doc.AddDocToList(this.props.document, this._chosenKey, doc, addBefore, before);
+ let groups = LinkManager.Instance.getRelatedGroupedLinks(this.props.document);
+ groups.forEach((groupLinkDocs, groupType) => {
+ let destLinks = groupLinkDocs.map(d => LinkManager.Instance.getOppositeAnchor(d, this.props.document));
+ ele.push(
+ <div key={"treeviewlink-" + groupType + "subtitle"}>
+ <div className="collectionTreeView-subtitle">{groupType}:</div>
+ {
+ TreeView.GetChildElements(destLinks, this.props.treeViewId, this.props.document, this.props.dataDoc, "treeviewlink-" + groupType, addDoc, remDoc, this.move,
+ this.props.dropAction, this.props.addDocTab, this.props.ScreenToLocalTransform, this.props.outerXf, this.props.active, this.props.panelWidth, this.props.renderDepth)
+ }
+ </div>
+ );
+ });
+ return ele;
+ }
+
render() {
let contentElement: (JSX.Element | null) = null;
- let docList = Cast(this.props.document[this._chosenKey], listSpec(Doc));
+ let docList = Cast(this.resolvedDataDoc[this._chosenKey], listSpec(Doc));
let remDoc = (doc: Doc) => this.remove(doc, this._chosenKey);
- let addDoc = (doc: Doc, addBefore?: Doc, before?: boolean) => Doc.AddDocToList(this.props.document, this._chosenKey, doc, addBefore, before);
- let doc = Cast(this.props.document[this._chosenKey], Doc);
+ let addDoc = (doc: Doc, addBefore?: Doc, before?: boolean) => Doc.AddDocToList(this.resolvedDataDoc, this._chosenKey, doc, addBefore, before);
+ let doc = Cast(this.resolvedDataDoc[this._chosenKey], Doc);
let docWidth = () => NumCast(this.props.document.nativeWidth) ? Math.min(this.props.document[WidthSym](), this.props.panelWidth() - 5) : this.props.panelWidth() - 5;
if (!this._collapsed) {
if (!this.props.document.embed) {
contentElement = <ul key={this._chosenKey + "more"}>
- {TreeView.GetChildElements(doc instanceof Doc ? [doc] : DocListCast(docList), this.props.treeViewId, this._chosenKey, addDoc, remDoc, this.move,
- this.props.dropAction, this.props.addDocTab, this.props.ScreenToLocalTransform, this.props.outerXf, this.props.active, this.props.panelWidth)}
+ {this._chosenKey === "links" ? this.renderLinks() :
+ TreeView.GetChildElements(doc instanceof Doc ? [doc] : DocListCast(docList), this.props.treeViewId, this.props.document, this.props.dataDoc, this._chosenKey, addDoc, remDoc, this.move,
+ this.props.dropAction, this.props.addDocTab, this.props.ScreenToLocalTransform, this.props.outerXf, this.props.active, this.props.panelWidth, this.props.renderDepth)}
</ul >;
} else {
+ console.log("PW = " + this.props.panelWidth());
contentElement = <div ref={this._dref} style={{ display: "inline-block", height: this.props.panelHeight() }} key={this.props.document[Id]}>
<CollectionSchemaPreview
Document={this.props.document}
+ DataDocument={this.resolvedDataDoc}
+ renderDepth={this.props.renderDepth}
width={docWidth}
height={this.props.panelHeight}
getTransform={this.docTransform}
@@ -306,18 +358,21 @@ class TreeView extends React.Component<TreeViewProps> {
public static GetChildElements(
docs: Doc[],
treeViewId: string,
+ containingCollection: Doc,
+ dataDoc: Doc | undefined,
key: string,
add: (doc: Doc, relativeTo?: Doc, before?: boolean) => boolean,
remove: ((doc: Doc) => boolean),
move: DragManager.MoveFunction,
dropAction: dropActionType,
- addDocTab: (doc: Doc, where: string) => void,
+ addDocTab: (doc: Doc, dataDoc: Doc, where: string) => void,
screenToLocalXf: () => Transform,
outerXf: () => { translateX: number, translateY: number },
active: () => boolean,
panelWidth: () => number,
+ renderDepth: number
) {
- let docList = docs.filter(child => !child.excludeFromLibrary && (key !== "data" || !child.isMinimized));
+ let docList = docs.filter(child => !child.excludeFromLibrary);
let rowWidth = () => panelWidth() - 20;
return docList.map((child, i) => {
let indent = i === 0 ? undefined : () => {
@@ -337,9 +392,12 @@ class TreeView extends React.Component<TreeViewProps> {
};
return <TreeView
document={child}
+ dataDoc={dataDoc}
+ containingCollection={containingCollection}
treeViewId={treeViewId}
key={child[Id]}
indentDocument={indent}
+ renderDepth={renderDepth}
deleteDoc={remove}
addDocument={addDocument}
panelWidth={rowWidth}
@@ -388,6 +446,8 @@ export class CollectionTreeView extends CollectionSubView(Document) {
}
}
+ @computed get resolvedDataDoc() { return BoolCast(this.props.Document.isTemplate) && this.props.DataDoc ? this.props.DataDoc : this.props.Document; }
+
outerXf = () => Utils.GetScreenTransform(this._mainEle!);
onTreeDrop = (e: React.DragEvent) => this.onDrop(e, {});
@@ -398,28 +458,27 @@ export class CollectionTreeView extends CollectionSubView(Document) {
return !this.childDocs ? (null) : (
<div id="body" className="collectionTreeView-dropTarget"
+ style={{ overflow: "auto" }}
onContextMenu={this.onContextMenu}
- onWheel={(e: React.WheelEvent) => this.props.isSelected() && e.stopPropagation()}
+ onWheel={(e: React.WheelEvent) => (e.target as any).scrollHeight > (e.target as any).clientHeight && e.stopPropagation()}
onDrop={this.onTreeDrop}
ref={this.createTreeDropTarget}>
- <div className="coll-title">
- <EditableView
- contents={this.props.Document.title}
- display={"inline"}
- height={72}
- GetValue={() => StrCast(this.props.Document.title)}
- SetValue={(value: string) => (Doc.GetProto(this.props.Document).title = value) ? true : true}
- OnFillDown={(value: string) => {
- Doc.GetProto(this.props.Document).title = value;
- let doc = Docs.FreeformDocument([], { title: "", x: 0, y: 0, width: 100, height: 25, templates: new List<string>([Templates.Title.Layout]) });
- TreeView.loadId = doc[Id];
- Doc.AddDocToList(this.props.Document, this.props.fieldKey, doc, this.childDocs.length ? this.childDocs[0] : undefined, true);
- }} />
- </div>
- <ul className="no-indent">
+ <EditableView
+ contents={this.resolvedDataDoc.title}
+ display={"block"}
+ height={72}
+ GetValue={() => StrCast(this.resolvedDataDoc.title)}
+ SetValue={(value: string) => (Doc.GetProto(this.resolvedDataDoc).title = value) ? true : true}
+ OnFillDown={(value: string) => {
+ Doc.GetProto(this.props.Document).title = value;
+ let doc = Docs.FreeformDocument([], { title: "", x: 0, y: 0, width: 100, height: 25, templates: new List<string>([Templates.Title.Layout]) });
+ TreeView.loadId = doc[Id];
+ Doc.AddDocToList(this.props.Document, this.props.fieldKey, doc, this.childDocs.length ? this.childDocs[0] : undefined, true);
+ }} />
+ <ul className="no-indent" style={{ width: "max-content" }} >
{
- TreeView.GetChildElements(this.childDocs, this.props.Document[Id], this.props.fieldKey, addDoc, this.remove,
- moveDoc, dropAction, this.props.addDocTab, this.props.ScreenToLocalTransform, this.outerXf, this.props.active, this.props.PanelWidth)
+ TreeView.GetChildElements(this.childDocs, this.props.Document[Id], this.props.Document, this.props.DataDoc, this.props.fieldKey, addDoc, this.remove,
+ moveDoc, dropAction, this.props.addDocTab, this.props.ScreenToLocalTransform, this.outerXf, this.props.active, this.props.PanelWidth, this.props.renderDepth)
}
</ul>
</div >
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 68eefab4c..0517c17bf 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -2,8 +2,10 @@ import { library } from '@fortawesome/fontawesome-svg-core';
import { faProjectDiagram, faSignature, faSquare, faTh, faThList, faTree } from '@fortawesome/free-solid-svg-icons';
import { observer } from "mobx-react";
import * as React from 'react';
+import { Doc } from '../../../new_fields/Doc';
import { Id } from '../../../new_fields/FieldSymbols';
import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils';
+import { Docs } from '../../documents/Documents';
import { undoBatch } from '../../util/UndoManager';
import { ContextMenu } from "../ContextMenu";
import { ContextMenuProps } from '../ContextMenuItem';
@@ -29,7 +31,7 @@ export class CollectionView extends React.Component<FieldViewProps> {
private SubView = (type: CollectionViewType, renderProps: CollectionRenderProps) => {
let props = { ...this.props, ...renderProps };
- switch (type) {
+ switch (this.isAnnotationOverlay ? CollectionViewType.Freeform : type) {
case CollectionViewType.Schema: return (<CollectionSchemaView {...props} CollectionView={this} />);
case CollectionViewType.Docking: return (<CollectionDockingView {...props} CollectionView={this} />);
case CollectionViewType.Tree: return (<CollectionTreeView {...props} CollectionView={this} />);
@@ -41,7 +43,7 @@ export class CollectionView extends React.Component<FieldViewProps> {
return (null);
}
- get isAnnotationOverlay() { return this.props.fieldKey && this.props.fieldKey === "annotations"; } // bcz: ? Why do we need to compare Id's?
+ get isAnnotationOverlay() { return this.props.fieldKey === "annotations" || this.props.fieldExt === "annotations"; }
onContextMenu = (e: React.MouseEvent): void => {
if (!this.isAnnotationOverlay && !e.isPropagationStopped() && this.props.Document[Id] !== CurrentUserUtils.MainDocId) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
@@ -54,6 +56,16 @@ export class CollectionView extends React.Component<FieldViewProps> {
subItems.push({ description: "Treeview", event: undoBatch(() => this.props.Document.viewType = CollectionViewType.Tree), icon: "tree" });
subItems.push({ description: "Stacking", event: undoBatch(() => this.props.Document.viewType = CollectionViewType.Stacking), icon: "th-list" });
ContextMenu.Instance.addItem({ description: "View Modes...", subitems: subItems });
+ ContextMenu.Instance.addItem({
+ description: "Apply Template", event: undoBatch(() => {
+ let otherdoc = new Doc();
+ otherdoc.width = 100;
+ otherdoc.height = 50;
+ Doc.GetProto(otherdoc).title = "applied(" + this.props.Document.title + ")";
+ Doc.GetProto(otherdoc).layout = Doc.MakeDelegate(this.props.Document);
+ this.props.addDocTab && this.props.addDocTab(otherdoc, otherdoc, "onRight");
+ }), icon: "project-diagram"
+ });
}
}
diff --git a/src/client/views/collections/ParentDocumentSelector.tsx b/src/client/views/collections/ParentDocumentSelector.tsx
index f11af04a3..fa7058d5a 100644
--- a/src/client/views/collections/ParentDocumentSelector.tsx
+++ b/src/client/views/collections/ParentDocumentSelector.tsx
@@ -9,7 +9,7 @@ import { CollectionDockingView } from "./CollectionDockingView";
import { NumCast } from "../../../new_fields/Types";
import { CollectionViewType } from "./CollectionBaseView";
-type SelectorProps = { Document: Doc, addDocTab(doc: Doc, location: string): void };
+type SelectorProps = { Document: Doc, addDocTab(doc: Doc, dataDoc: Doc, location: string): void };
@observer
export class SelectorContextMenu extends React.Component<SelectorProps> {
@observable private _docs: { col: Doc, target: Doc }[] = [];
@@ -43,7 +43,7 @@ export class SelectorContextMenu extends React.Component<SelectorProps> {
col.panX = newPanX;
col.panY = newPanY;
}
- this.props.addDocTab(col, "inTab");
+ this.props.addDocTab(col, col, "inTab"); // bcz: dataDoc?
};
}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss
index 7a0fd2b31..fc5212edd 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss
@@ -9,6 +9,7 @@
opacity: 0.5;
transform: translate(10000px,10000px);
pointer-events: all;
+ cursor: pointer;
}
.collectionfreeformlinkview-linkText {
stroke: rgb(0,0,0);
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
index ba7e6cf9e..b546d1b78 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -25,18 +25,18 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
let y1 = NumCast(a.y) + (BoolCast(a.isMinimized, false) ? 5 : a[HeightSym]() / 2);
let x2 = NumCast(b.x) + (BoolCast(b.isMinimized, false) ? 5 : b[WidthSym]() / 2);
let y2 = NumCast(b.y) + (BoolCast(b.isMinimized, false) ? 5 : b[HeightSym]() / 2);
- this.props.LinkDocs.map(l => {
- let width = l[WidthSym]();
- l.x = (x1 + x2) / 2 - width / 2;
- l.y = (y1 + y2) / 2 + 10;
- if (!this.props.removeDocument(l)) this.props.addDocument(l, false);
- });
+ // this.props.LinkDocs.map(l => {
+ // let width = l[WidthSym]();
+ // l.x = (x1 + x2) / 2 - width / 2;
+ // l.y = (y1 + y2) / 2 + 10;
+ // if (!this.props.removeDocument(l)) this.props.addDocument(l, false);
+ // });
e.stopPropagation();
e.preventDefault();
}
}
render() {
- let l = this.props.LinkDocs;
+ // let l = this.props.LinkDocs;
let a = this.props.A;
let b = this.props.B;
let x1 = NumCast(a.x) + (BoolCast(a.isMinimized, false) ? 5 : NumCast(a.width) / NumCast(a.zoomBasis, 1) / 2);
@@ -44,13 +44,13 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
let x2 = NumCast(b.x) + (BoolCast(b.isMinimized, false) ? 5 : NumCast(b.width) / NumCast(b.zoomBasis, 1) / 2);
let y2 = NumCast(b.y) + (BoolCast(b.isMinimized, false) ? 5 : NumCast(b.height) / NumCast(b.zoomBasis, 1) / 2);
let text = "";
- let first = this.props.LinkDocs[0];
- if (this.props.LinkDocs.length === 1) text += first.title + (first.linkDescription ? "(" + StrCast(first.linkDescription) + ")" : "");
- else text = "-multiple-";
+ // let first = this.props.LinkDocs[0];
+ // if (this.props.LinkDocs.length === 1) text += first.title + (first.linkDescription ? "(" + StrCast(first.linkDescription) + ")" : "");
+ // else text = "-multiple-";
return (
<>
<line key="linkLine" className="collectionfreeformlinkview-linkLine"
- style={{ strokeWidth: `${2 * l.length / 2}` }}
+ style={{ strokeWidth: `${2 * 1 / 2}` }}
x1={`${x1}`} y1={`${y1}`}
x2={`${x2}`} y2={`${y2}`} />
{/* <circle key="linkCircle" className="collectionfreeformlinkview-linkCircle"
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.scss
index a80e09fa8..30e158603 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.scss
@@ -1,4 +1,4 @@
-p.collectionfreeformlinksview-svgCanvas{
+.collectionfreeformlinksview-svgCanvas{
transform: translate(-10000px,-10000px);
position: absolute;
top: 0;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
index be75c6c5c..ebeb1fcee 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
@@ -97,6 +97,7 @@ export class CollectionFreeFormLinksView extends React.Component<CollectionViewP
let connections = DocumentManager.Instance.LinkedDocumentViews.reduce((drawnPairs, connection) => {
let srcViews = this.documentAnchors(connection.a);
let targetViews = this.documentAnchors(connection.b);
+
let possiblePairs: { a: Doc, b: Doc, }[] = [];
srcViews.map(sv => targetViews.map(tv => possiblePairs.push({ a: sv.props.Document, b: tv.props.Document })));
possiblePairs.map(possiblePair => {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 663fa5f9d..996032b1d 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1,6 +1,6 @@
import { action, computed } from "mobx";
import { observer } from "mobx-react";
-import { Doc, HeightSym, WidthSym, DocListCastAsync } from "../../../../new_fields/Doc";
+import { Doc, DocListCastAsync, HeightSym, WidthSym } from "../../../../new_fields/Doc";
import { Id } from "../../../../new_fields/FieldSymbols";
import { InkField, StrokeData } from "../../../../new_fields/InkField";
import { createSchema, makeInterface } from "../../../../new_fields/Schema";
@@ -13,11 +13,13 @@ import { SelectionManager } from "../../../util/SelectionManager";
import { Transform } from "../../../util/Transform";
import { undoBatch } from "../../../util/UndoManager";
import { COLLECTION_BORDER_WIDTH } from "../../../views/globalCssVariables.scss";
+import { ContextMenu } from "../../ContextMenu";
import { InkingCanvas } from "../../InkingCanvas";
import { CollectionFreeFormDocumentView } from "../../nodes/CollectionFreeFormDocumentView";
import { DocumentContentsView } from "../../nodes/DocumentContentsView";
import { DocumentViewProps, positionSchema } from "../../nodes/DocumentView";
import { pageSchema } from "../../nodes/ImageBox";
+import PDFMenu from "../../pdf/PDFMenu";
import { CollectionSubView } from "../CollectionSubView";
import { CollectionFreeFormLinksView } from "./CollectionFreeFormLinksView";
import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCursors";
@@ -25,8 +27,6 @@ import "./CollectionFreeFormView.scss";
import { MarqueeView } from "./MarqueeView";
import React = require("react");
import v5 = require("uuid/v5");
-import PDFMenu from "../../pdf/PDFMenu";
-import { ContextMenu } from "../../ContextMenu";
export const panZoomSchema = createSchema({
panX: "number",
@@ -47,7 +47,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
@computed get nativeWidth() { return this.Document.nativeWidth || 0; }
@computed get nativeHeight() { return this.Document.nativeHeight || 0; }
- public get isAnnotationOverlay() { return this.props.fieldKey && this.props.fieldKey === "annotations"; }
+ public get isAnnotationOverlay() { return this.props.fieldKey === "annotations" || this.props.fieldExt === "annotations"; }
private get borderWidth() { return this.isAnnotationOverlay ? 0 : COLLECTION_BORDER_WIDTH; }
private panX = () => this.Document.panX || 0;
private panY = () => this.Document.panY || 0;
@@ -163,7 +163,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
return [[range[0][0] > x ? x : range[0][0], range[0][1] < xe ? xe : range[0][1]],
[range[1][0] > y ? y : range[1][0], range[1][1] < ye ? ye : range[1][1]]];
}, [[minx, maxx], [miny, maxy]]);
- let ink = Cast(this.props.Document.ink, InkField);
+ let ink = Cast(this.extensionDoc.ink, InkField);
if (ink && ink.inkData) {
ink.inkData.forEach((value: StrokeData, key: string) => {
let bounds = InkingCanvas.StrokeRect(value);
@@ -198,7 +198,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
var dv = DocumentManager.Instance.getDocumentView(doc);
return dv && SelectionManager.IsSelected(dv) ? true : false;
});
- if (!this.props.isSelected() && !childSelected && !this.props.isTopMost) {
+ if (!this.props.isSelected() && !childSelected && this.props.renderDepth > 0) {
return;
}
e.stopPropagation();
@@ -326,17 +326,22 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
}
- getDocumentViewProps(document: Doc): DocumentViewProps {
+ getDocumentViewProps(layoutDoc: Doc): DocumentViewProps {
+ let datadoc = BoolCast(this.props.Document.isTemplate) || this.props.DataDoc === this.props.Document ? undefined : this.props.DataDoc;
+ if (Cast(layoutDoc.layout, Doc) instanceof Doc) { // if this document is using a template to render, then set the dataDoc for the template to be this document
+ datadoc = layoutDoc;
+ }
return {
- Document: document,
+ DataDoc: datadoc,
+ Document: layoutDoc,
addDocument: this.props.addDocument,
removeDocument: this.props.removeDocument,
moveDocument: this.props.moveDocument,
ScreenToLocalTransform: this.getTransform,
- isTopMost: false,
- selectOnLoad: document[Id] === this._selectOnLoaded,
- PanelWidth: document[WidthSym],
- PanelHeight: document[HeightSym],
+ renderDepth: this.props.renderDepth + 1,
+ selectOnLoad: layoutDoc[Id] === this._selectOnLoaded,
+ PanelWidth: layoutDoc[WidthSym],
+ PanelHeight: layoutDoc[HeightSym],
ContentScaling: returnOne,
ContainingCollectionView: this.props.CollectionView,
focus: this.focusDocument,
@@ -402,12 +407,13 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
}
private childViews = () => [
- <CollectionFreeFormBackgroundView key="backgroundView" {...this.props} {...this.getDocumentViewProps(this.props.Document)} />,
+ <CollectionFreeFormBackgroundView key="backgroundView" {...this.props} {...this.getDocumentViewProps(this.props.Document)} DataDoc={this.props.DataDoc} />,
...this.views
]
render() {
const containerName = `collectionfreeformview${this.isAnnotationOverlay ? "-overlay" : "-container"}`;
const easing = () => this.props.Document.panTransformType === "Ease";
+ if (this.props.fieldExt) Doc.UpdateDocumentExtensionForField(this.extensionDoc, this.props.fieldKey);
return (
<div className={containerName} ref={this.createDropTarget} onWheel={this.onPointerWheel}
style={{ borderRadius: "inherit" }}
@@ -419,7 +425,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
easing={easing} zoomScaling={this.zoomScaling} panX={this.panX} panY={this.panY}>
<CollectionFreeFormLinksView {...this.props} key="freeformLinks">
- <InkingCanvas getScreenTransform={this.getTransform} Document={this.props.Document} >
+ <InkingCanvas getScreenTransform={this.getTransform} Document={this.extensionDoc} inkFieldKey={this.props.fieldExt ? "ink" : this.props.fieldKey + "_ink"} >
{this.childViews}
</InkingCanvas>
</CollectionFreeFormLinksView>
@@ -436,7 +442,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
class CollectionFreeFormOverlayView extends React.Component<DocumentViewProps & { isSelected: () => boolean }> {
@computed get overlayView() {
return (<DocumentContentsView {...this.props} layoutKey={"overlayLayout"}
- isTopMost={this.props.isTopMost} isSelected={this.props.isSelected} select={emptyFunction} />);
+ renderDepth={this.props.renderDepth} isSelected={this.props.isSelected} select={emptyFunction} />);
}
render() {
return this.overlayView;
@@ -446,8 +452,9 @@ class CollectionFreeFormOverlayView extends React.Component<DocumentViewProps &
@observer
class CollectionFreeFormBackgroundView extends React.Component<DocumentViewProps & { isSelected: () => boolean }> {
@computed get backgroundView() {
+ let props = this.props;
return (<DocumentContentsView {...this.props} layoutKey={"backgroundLayout"}
- isTopMost={this.props.isTopMost} isSelected={this.props.isSelected} select={emptyFunction} />);
+ renderDepth={this.props.renderDepth} isSelected={this.props.isSelected} select={emptyFunction} />);
}
render() {
return this.props.Document.backgroundLayout ? this.backgroundView : (null);
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 9bfe9d7e1..50f76b5f0 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -14,7 +14,6 @@ import { Transform } from "../../../util/Transform";
import { undoBatch } from "../../../util/UndoManager";
import { InkingCanvas } from "../../InkingCanvas";
import { PreviewCursor } from "../../PreviewCursor";
-import { SearchBox } from "../../search/FilterBox";
import { Templates } from "../../Templates";
import { CollectionViewType } from "../CollectionBaseView";
import { CollectionFreeFormView } from "./CollectionFreeFormView";
@@ -50,7 +49,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
document.removeEventListener("pointerup", this.onPointerUp, true);
document.removeEventListener("pointermove", this.onPointerMove, true);
}
- if (all) {
+ if (rem_keydown) {
document.removeEventListener("keydown", this.marqueeCommand, true);
}
this._visible = false;
@@ -95,7 +94,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
}
});
} else if (!e.ctrlKey) {
- let newBox = Docs.TextDocument({ width: 200, height: 30, x: x, y: y, title: "-typed text-" });
+ let newBox = Docs.TextDocument({ width: 200, height: 50, x: x, y: y, title: "-typed text-" });
newBox.proto!.autoHeight = true;
this.props.addLiveTextDocument(newBox);
}
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 02396c3af..0da4888a1 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -23,6 +23,7 @@ import { FieldViewProps } from "./FieldView";
import { Without, OmitKeys } from "../../../Utils";
import { Cast, StrCast, NumCast } from "../../../new_fields/Types";
import { List } from "../../../new_fields/List";
+import { Doc } from "../../../new_fields/Doc";
const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
type BindingProps = Without<FieldViewProps, 'fieldKey'>;
@@ -47,11 +48,12 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
hideOnLeave?: boolean
}> {
@computed get layout(): string {
- const layout = Cast(this.props.Document[this.props.layoutKey], "string");
+ let layoutDoc = this.props.Document.layout instanceof Doc ? this.props.Document.layout : this.props.Document;
+ const layout = Cast(layoutDoc[this.props.layoutKey], "string");
if (layout === undefined) {
return this.props.Document.data ?
"<FieldView {...props} fieldKey='data' />" :
- KeyValueBox.LayoutString(this.props.Document.proto ? "proto" : "");
+ KeyValueBox.LayoutString(layoutDoc.proto ? "proto" : "");
} else if (typeof layout === "string") {
return layout;
} else {
@@ -59,8 +61,8 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
}
}
- CreateBindings(): JsxBindings {
- return { props: OmitKeys(this.props, ['parentActive'], (obj: any) => obj.active = this.props.parentActive).omit };
+ CreateBindings(layoutDoc?: Doc): JsxBindings {
+ return { props: { ...OmitKeys(this.props, ['parentActive'], (obj: any) => obj.active = this.props.parentActive).omit, Document: layoutDoc } };
}
@computed get templates(): List<string> {
@@ -101,10 +103,11 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
}
render() {
+ if (this.props.renderDepth > 7) return (null);
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 }}
- bindings={this.CreateBindings()}
+ bindings={this.CreateBindings(this.props.Document.layout instanceof Doc ? this.props.Document.layout : this.props.Document)}
jsx={this.finalLayout}
showWarnings={true}
onError={(test: any) => { console.log(test); }}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index e6d8086b0..9acfe6cff 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -31,6 +31,8 @@ import "./DocumentView.scss";
import React = require("react");
import { Id, Copy } from '../../../new_fields/FieldSymbols';
import { ContextMenuProps } from '../ContextMenuItem';
+import { list, object, createSimpleSchema } from 'serializr';
+import { LinkManager } from '../../util/LinkManager';
import { RouteStore } from '../../../server/RouteStore';
const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
@@ -53,25 +55,27 @@ library.add(fa.faDesktop);
library.add(fa.faUnlock);
library.add(fa.faLock);
-const linkSchema = createSchema({
- title: "string",
- linkDescription: "string",
- linkTags: "string",
- linkedTo: Doc,
- linkedFrom: Doc
-});
-type LinkDoc = makeInterface<[typeof linkSchema]>;
-const LinkDoc = makeInterface(linkSchema);
+// const linkSchema = createSchema({
+// title: "string",
+// linkDescription: "string",
+// linkTags: "string",
+// linkedTo: Doc,
+// linkedFrom: Doc
+// });
+
+// type LinkDoc = makeInterface<[typeof linkSchema]>;
+// const LinkDoc = makeInterface(linkSchema);
export interface DocumentViewProps {
ContainingCollectionView: Opt<CollectionView | CollectionPDFView | CollectionVideoView>;
Document: Doc;
+ DataDoc?: Doc;
addDocument?: (doc: Doc, allowDuplicates?: boolean) => boolean;
removeDocument?: (doc: Doc) => boolean;
moveDocument?: (doc: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean;
ScreenToLocalTransform: () => Transform;
- isTopMost: boolean;
+ renderDepth: number;
ContentScaling: () => number;
PanelWidth: () => number;
PanelHeight: () => number;
@@ -80,7 +84,7 @@ export interface DocumentViewProps {
parentActive: () => boolean;
whenActiveChanged: (isActive: boolean) => void;
bringToFront: (doc: Doc) => void;
- addDocTab: (doc: Doc, where: string) => void;
+ addDocTab: (doc: Doc, dataDoc: Doc, where: string) => void;
collapseToPoint?: (scrpt: number[], expandedDocs: Doc[] | undefined) => void;
zoomToScale: (scale: number) => void;
getScale: () => number;
@@ -125,7 +129,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
public get ContentDiv() { return this._mainCont.current; }
@computed get active(): boolean { return SelectionManager.IsSelected(this) || this.props.parentActive(); }
- @computed get topMost(): boolean { return this.props.isTopMost; }
+ @computed get topMost(): boolean { return this.props.renderDepth === 0; }
@computed get templates(): List<string> {
let field = this.props.Document.templates;
if (field && field instanceof List) {
@@ -222,11 +226,13 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
e.stopPropagation();
}
+ get dataDoc() { return this.props.DataDoc ? this.props.DataDoc : this.props.Document; }
startDragging(x: number, y: number, dropAction: dropActionType, dragSubBullets: boolean) {
if (this._mainCont.current) {
let allConnected = [this.props.Document, ...(dragSubBullets ? DocListCast(this.props.Document.subBulletDocs) : [])];
+ let alldataConnected = [this.dataDoc, ...(dragSubBullets ? DocListCast(this.props.Document.subBulletDocs) : [])];
const [left, top] = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).inverse().transformPoint(0, 0);
- let dragData = new DragManager.DocumentDragData(allConnected);
+ let dragData = new DragManager.DocumentDragData(allConnected, alldataConnected);
const [xoff, yoff] = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).transformDirection(x - left, y - top);
dragData.dropAction = dropAction;
dragData.xOffset = xoff;
@@ -280,8 +286,10 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
e.stopPropagation();
let altKey = e.altKey;
let ctrlKey = e.ctrlKey;
- if (this._doubleTap && !this.props.isTopMost) {
- this.props.addDocTab(this.props.Document, "inTab");
+ if (this._doubleTap && this.props.renderDepth) {
+ let fullScreenAlias = Doc.MakeAlias(this.props.Document);
+ fullScreenAlias.templates = new List<string>();
+ this.props.addDocTab(fullScreenAlias, this.dataDoc, "inTab");
SelectionManager.DeselectAll();
this.props.Document.libraryBrush = false;
}
@@ -295,8 +303,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
let subBulletDocs = await DocListCastAsync(this.props.Document.subBulletDocs);
let maximizedDocs = await DocListCastAsync(this.props.Document.maximizedDocs);
let summarizedDocs = await DocListCastAsync(this.props.Document.summarizedDocs);
- let linkedToDocs = await DocListCastAsync(this.props.Document.linkedToDocs, []);
- let linkedFromDocs = await DocListCastAsync(this.props.Document.linkedFromDocs, []);
+ let linkedDocs = LinkManager.Instance.getAllRelatedLinks(this.props.Document);
let expandedDocs: Doc[] = [];
expandedDocs = subBulletDocs ? [...subBulletDocs, ...expandedDocs] : expandedDocs;
expandedDocs = maximizedDocs ? [...maximizedDocs, ...expandedDocs] : expandedDocs;
@@ -324,31 +331,38 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
if (dataDocs) {
expandedDocs.forEach(maxDoc =>
(!CollectionDockingView.Instance.CloseRightSplit(Doc.GetProto(maxDoc)) &&
- this.props.addDocTab(getDispDoc(maxDoc), maxLocation)));
+ this.props.addDocTab(getDispDoc(maxDoc), getDispDoc(maxDoc), maxLocation)));
}
} else {
let scrpt = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).inverse().transformPoint(NumCast(this.Document.width) / 2, NumCast(this.Document.height) / 2);
this.collapseTargetsToPoint(scrpt, expandedProtoDocs);
}
}
- else if (linkedToDocs.length || linkedFromDocs.length) {
- let linkedFwdDocs = [
- linkedToDocs.length ? linkedToDocs[0].linkedTo as Doc : linkedFromDocs.length ? linkedFromDocs[0].linkedFrom as Doc : expandedDocs[0],
- linkedFromDocs.length ? linkedFromDocs[0].linkedFrom as Doc : linkedToDocs.length ? linkedToDocs[0].linkedTo as Doc : expandedDocs[0]];
-
- let linkedFwdContextDocs = [
- linkedToDocs.length ? await (linkedToDocs[0].linkedToContext) as Doc : linkedFromDocs.length ? await PromiseValue(linkedFromDocs[0].linkedFromContext) as Doc : undefined,
- linkedFromDocs.length ? await (linkedFromDocs[0].linkedFromContext) as Doc : linkedToDocs.length ? await PromiseValue(linkedToDocs[0].linkedToContext) as Doc : undefined];
-
- let linkedFwdPage = [
- linkedToDocs.length ? NumCast(linkedToDocs[0].linkedToPage, undefined) : linkedFromDocs.length ? NumCast(linkedFromDocs[0].linkedFromPage, undefined) : undefined,
- linkedFromDocs.length ? NumCast(linkedFromDocs[0].linkedFromPage, undefined) : linkedToDocs.length ? NumCast(linkedToDocs[0].linkedToPage, undefined) : undefined];
-
- if (!linkedFwdDocs.some(l => l instanceof Promise)) {
- let maxLocation = StrCast(linkedFwdDocs[altKey ? 1 : 0].maximizeLocation, "inTab");
- let targetContext = !Doc.AreProtosEqual(linkedFwdContextDocs[altKey ? 1 : 0], this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.Document) ? linkedFwdContextDocs[altKey ? 1 : 0] : undefined;
- DocumentManager.Instance.jumpToDocument(linkedFwdDocs[altKey ? 1 : 0], ctrlKey, false, document => this.props.addDocTab(document, maxLocation), linkedFwdPage[altKey ? 1 : 0], targetContext);
- }
+ else if (linkedDocs.length) {
+ let linkedDoc = linkedDocs.length ? linkedDocs[0] : expandedDocs[0];
+ let linkedPages = [linkedDocs.length ? NumCast(linkedDocs[0].anchor1Page, undefined) : NumCast(linkedDocs[0].anchor2Page, undefined),
+ linkedDocs.length ? NumCast(linkedDocs[0].anchor2Page, undefined) : NumCast(linkedDocs[0].anchor1Page, undefined)];
+ let maxLocation = StrCast(linkedDoc.maximizeLocation, "inTab");
+ DocumentManager.Instance.jumpToDocument(linkedDoc, ctrlKey, false, document => this.props.addDocTab(document, document, maxLocation), linkedPages[altKey ? 1 : 0]);
+
+ // else if (linkedToDocs.length || linkedFromDocs.length) {
+ // let linkedFwdDocs = [
+ // linkedToDocs.length ? linkedToDocs[0].linkedTo as Doc : linkedFromDocs.length ? linkedFromDocs[0].linkedFrom as Doc : expandedDocs[0],
+ // linkedFromDocs.length ? linkedFromDocs[0].linkedFrom as Doc : linkedToDocs.length ? linkedToDocs[0].linkedTo as Doc : expandedDocs[0]];
+
+ // let linkedFwdContextDocs = [
+ // linkedToDocs.length ? await (linkedToDocs[0].linkedToContext) as Doc : linkedFromDocs.length ? await PromiseValue(linkedFromDocs[0].linkedFromContext) as Doc : undefined,
+ // linkedFromDocs.length ? await (linkedFromDocs[0].linkedFromContext) as Doc : linkedToDocs.length ? await PromiseValue(linkedToDocs[0].linkedToContext) as Doc : undefined];
+
+ // let linkedFwdPage = [
+ // linkedToDocs.length ? NumCast(linkedToDocs[0].linkedToPage, undefined) : linkedFromDocs.length ? NumCast(linkedFromDocs[0].linkedFromPage, undefined) : undefined,
+ // linkedFromDocs.length ? NumCast(linkedFromDocs[0].linkedFromPage, undefined) : linkedToDocs.length ? NumCast(linkedToDocs[0].linkedToPage, undefined) : undefined];
+
+ // if (!linkedFwdDocs.some(l => l instanceof Promise)) {
+ // let maxLocation = StrCast(linkedFwdDocs[altKey ? 1 : 0].maximizeLocation, "inTab");
+ // let targetContext = !Doc.AreProtosEqual(linkedFwdContextDocs[altKey ? 1 : 0], this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.Document) ? linkedFwdContextDocs[altKey ? 1 : 0] : undefined;
+ // DocumentManager.Instance.jumpToDocument(linkedFwdDocs[altKey ? 1 : 0], ctrlKey, false, document => this.props.addDocTab(document, document, maxLocation), linkedFwdPage[altKey ? 1 : 0], targetContext);
+ // }
}
}
}
@@ -358,7 +372,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
this._downY = e.clientY;
this._hitExpander = DocListCast(this.props.Document.subBulletDocs).length > 0;
if (e.shiftKey && e.buttons === 1 && CollectionDockingView.Instance) {
- CollectionDockingView.Instance.StartOtherDrag([Doc.MakeAlias(this.props.Document)], e);
+ CollectionDockingView.Instance.StartOtherDrag(e, [Doc.MakeAlias(this.props.Document)], [this.dataDoc]);
e.stopPropagation();
} else {
if (this.active) e.stopPropagation(); // events stop at the lowest document that is active.
@@ -389,7 +403,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
}
deleteClicked = (): void => { this.props.removeDocument && this.props.removeDocument(this.props.Document); };
- fieldsClicked = (): void => { this.props.addDocTab(Docs.KVPDocument(this.props.Document, { width: 300, height: 300 }), "onRight"); };
+ fieldsClicked = (): void => { let kvp = Docs.KVPDocument(this.props.Document, { width: 300, height: 300 }); this.props.addDocTab(kvp, kvp, "onRight"); };
makeBtnClicked = (): void => {
let doc = Doc.GetProto(this.props.Document);
doc.isButton = !BoolCast(doc.isButton, false);
@@ -403,7 +417,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
}
}
fullScreenClicked = (): void => {
- CollectionDockingView.Instance && CollectionDockingView.Instance.OpenFullScreen(Doc.MakeCopy(this.props.Document, false));
+ CollectionDockingView.Instance && CollectionDockingView.Instance.OpenFullScreen(Doc.MakeAlias(this.props.Document), this.dataDoc);
SelectionManager.DeselectAll();
}
@@ -486,10 +500,10 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
const cm = ContextMenu.Instance;
let subitems: ContextMenuProps[] = [];
subitems.push({ description: "Open Full Screen", event: this.fullScreenClicked, icon: "desktop" });
- subitems.push({ description: "Open Tab", event: () => this.props.addDocTab && this.props.addDocTab(this.props.Document, "inTab"), icon: "folder" });
- subitems.push({ description: "Open Tab Alias", event: () => this.props.addDocTab && this.props.addDocTab(Doc.MakeAlias(this.props.Document), "inTab"), icon: "folder" });
- subitems.push({ description: "Open Right", event: () => this.props.addDocTab && this.props.addDocTab(this.props.Document, "onRight"), icon: "caret-square-right" });
- subitems.push({ description: "Open Right Alias", event: () => this.props.addDocTab && this.props.addDocTab(Doc.MakeAlias(this.props.Document), "onRight"), icon: "caret-square-right" });
+ subitems.push({ description: "Open Tab", event: () => this.props.addDocTab && this.props.addDocTab(this.props.Document, this.dataDoc, "inTab"), icon: "folder" });
+ subitems.push({ description: "Open Tab Alias", event: () => this.props.addDocTab && this.props.addDocTab(Doc.MakeAlias(this.props.Document), this.dataDoc, "inTab"), icon: "folder" });
+ subitems.push({ description: "Open Right", event: () => this.props.addDocTab && this.props.addDocTab(this.props.Document, this.dataDoc, "onRight"), icon: "caret-square-right" });
+ subitems.push({ description: "Open Right Alias", event: () => this.props.addDocTab && this.props.addDocTab(Doc.MakeAlias(this.props.Document), this.dataDoc, "onRight"), icon: "caret-square-right" });
subitems.push({ description: "Open Fields", event: this.fieldsClicked, icon: "layer-group" });
cm.addItem({ description: "Open...", subitems: subitems, icon: "external-link-alt" });
cm.addItem({ description: BoolCast(this.props.Document.ignoreAspect, false) || !this.props.Document.nativeWidth || !this.props.Document.nativeHeight ? "Freeze" : "Unfreeze", event: this.freezeNativeDimensions, icon: "edit" });
@@ -499,7 +513,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
cm.addItem({
description: "Find aliases", event: async () => {
const aliases = await SearchUtil.GetAliasesOfDocument(this.props.Document);
- this.props.addDocTab && this.props.addDocTab(Docs.SchemaDocument(["title"], aliases, {}), "onRight");
+ this.props.addDocTab && this.props.addDocTab(Docs.SchemaDocument(["title"], aliases, {}), Docs.SchemaDocument(["title"], aliases, {}), "onRight"); // bcz: dataDoc?
}, icon: "search"
});
cm.addItem({ description: "Center View", event: () => this.props.focus(this.props.Document, false), icon: "crosshairs" });
@@ -556,11 +570,13 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
if (this.Document.hidden) {
return null;
}
+ let backgroundColor = this.props.Document.layout instanceof Doc ? StrCast(this.props.Document.layout.backgroundColor) : this.Document.backgroundColor;
var scaling = this.props.ContentScaling();
var nativeWidth = this.nativeWidth > 0 ? `${this.nativeWidth}px` : "100%";
+
var nativeHeight = BoolCast(this.props.Document.ignoreAspect) ? this.props.PanelHeight() / this.props.ContentScaling() : this.nativeHeight > 0 ? `${this.nativeHeight}px` : "100%";
return (
- <div className={`documentView-node${this.props.isTopMost ? "-topmost" : ""}`}
+ <div className={`documentView-node${this.topMost ? "-topmost" : ""}`}
ref={this._mainCont}
style={{
outlineColor: "maroon",
@@ -570,14 +586,12 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
`${1 * this.props.ScreenToLocalTransform().Scale}px`
: "0px",
borderRadius: "inherit",
- background: this.Document.backgroundColor || "",
+ background: backgroundColor || "",
width: nativeWidth,
height: nativeHeight,
transform: `scale(${scaling}, ${scaling})`,
- opacity: NumCast(this.props.Document.opacity, 1)
}}
onDrop={this.onDrop} onContextMenu={this.onContextMenu} onPointerDown={this.onPointerDown} onClick={this.onClick}
-
onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}
>
{this.contents}
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 1f1582f22..55f61ddff 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -7,18 +7,17 @@ import { IconField } from "../../../new_fields/IconField";
import { List } from "../../../new_fields/List";
import { RichTextField } from "../../../new_fields/RichTextField";
import { AudioField, ImageField, VideoField } from "../../../new_fields/URLField";
-import { emptyFunction, returnFalse, returnOne } from "../../../Utils";
import { Transform } from "../../util/Transform";
import { CollectionPDFView } from "../collections/CollectionPDFView";
import { CollectionVideoView } from "../collections/CollectionVideoView";
import { CollectionView } from "../collections/CollectionView";
import { AudioBox } from "./AudioBox";
-import { DocumentContentsView } from "./DocumentContentsView";
import { FormattedTextBox } from "./FormattedTextBox";
import { IconBox } from "./IconBox";
import { ImageBox } from "./ImageBox";
-import { VideoBox } from "./VideoBox";
import { PDFBox } from "./PDFBox";
+import { VideoBox } from "./VideoBox";
+import { Id } from "../../../new_fields/FieldSymbols";
//
@@ -28,14 +27,16 @@ import { PDFBox } from "./PDFBox";
//
export interface FieldViewProps {
fieldKey: string;
+ fieldExt: string;
ContainingCollectionView: Opt<CollectionView | CollectionPDFView | CollectionVideoView>;
Document: Doc;
+ DataDoc?: Doc;
isSelected: () => boolean;
select: (isCtrlPressed: boolean) => void;
- isTopMost: boolean;
+ renderDepth: number;
selectOnLoad: boolean;
addDocument?: (document: Doc, allowDuplicates?: boolean) => boolean;
- addDocTab: (document: Doc, where: string) => void;
+ addDocTab: (document: Doc, dataDoc: Doc, where: string) => void;
removeDocument?: (document: Doc) => boolean;
moveDocument?: (document: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean;
ScreenToLocalTransform: () => Transform;
@@ -85,7 +86,7 @@ export class FieldView extends React.Component<FieldViewProps> {
return <p>{field.date.toLocaleString()}</p>;
}
else if (field instanceof Doc) {
- return <p><b>{field.title}</b></p>;
+ return <p><b>{field.title + " + " + field[Id]}</b></p>;
// let returnHundred = () => 100;
// return (
// <DocumentContentsView Document={field}
@@ -96,7 +97,7 @@ export class FieldView extends React.Component<FieldViewProps> {
// ContentScaling={returnOne}
// PanelWidth={returnHundred}
// PanelHeight={returnHundred}
- // isTopMost={true} //TODO Why is this top most?
+ // renderDepth={0} //TODO Why is renderDepth reset?
// selectOnLoad={false}
// focus={emptyFunction}
// isSelected={this.props.isSelected}
diff --git a/src/client/views/nodes/FormattedTextBox.scss b/src/client/views/nodes/FormattedTextBox.scss
index 4a29c1949..d3045ae2f 100644
--- a/src/client/views/nodes/FormattedTextBox.scss
+++ b/src/client/views/nodes/FormattedTextBox.scss
@@ -1,7 +1,7 @@
@import "../globalCssVariables";
.ProseMirror {
width: 100%;
- height: auto;
+ height: 100%;
min-height: 100%;
font-family: $serif;
}
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 376b5a574..f0b89cbef 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -1,6 +1,6 @@
import { library } from '@fortawesome/fontawesome-svg-core';
import { faEdit, faSmile } from '@fortawesome/free-solid-svg-icons';
-import { action, IReactionDisposer, observable, reaction, runInAction } from "mobx";
+import { action, IReactionDisposer, observable, reaction, runInAction, computed } from "mobx";
import { observer } from "mobx-react";
import { baseKeymap } from "prosemirror-commands";
import { history } from "prosemirror-history";
@@ -45,6 +45,7 @@ export interface FormattedTextBoxProps {
hideOnLeave?: boolean;
height?: string;
color?: string;
+ outer_div?: (domminus: HTMLElement) => void;
}
const richTextSchema = createSchema({
@@ -60,6 +61,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
return FieldView.LayoutString(FormattedTextBox, fieldStr);
}
private _ref: React.RefObject<HTMLDivElement>;
+ private _outerdiv?: (dominus: HTMLElement) => void;
private _proseRef?: HTMLDivElement;
private _editorView: Opt<EditorView>;
private _toolTipTextMenu: TooltipTextMenu | undefined = undefined;
@@ -97,6 +99,10 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
constructor(props: FieldViewProps) {
super(props);
+ if (this.props.outer_div) {
+ this._outerdiv = this.props.outer_div;
+ console.log("yay");
+ }
this._ref = React.createRef();
if (this.props.isOverlay) {
@@ -104,19 +110,21 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
}
}
+ @computed get dataDoc() { return BoolCast(this.props.Document.isTemplate) && this.props.DataDoc ? this.props.DataDoc : this.props.Document; }
+
dispatchTransaction = (tx: Transaction) => {
if (this._editorView) {
const state = this._editorView.state.apply(tx);
this._editorView.updateState(state);
this._applyingChange = true;
- Doc.SetOnPrototype(this.props.Document, this.props.fieldKey, new RichTextField(JSON.stringify(state.toJSON())));
- Doc.SetOnPrototype(this.props.Document, "documentText", state.doc.textBetween(0, state.doc.content.size, "\n\n"));
+ Doc.GetProto(this.dataDoc)[this.props.fieldKey] = new RichTextField(JSON.stringify(state.toJSON()));
+ Doc.GetProto(this.dataDoc)[this.props.fieldKey + "_text"] = state.doc.textBetween(0, state.doc.content.size, "\n\n");
this._applyingChange = false;
- let title = StrCast(this.props.Document.title);
+ let title = StrCast(this.dataDoc.title);
if (title && title.startsWith("-") && this._editorView) {
let str = this._editorView.state.doc.textContent;
let titlestr = str.substr(0, Math.min(40, str.length));
- let target = this.props.Document.proto ? this.props.Document.proto : this.props.Document;
+ let target = this.dataDoc.proto ? this.dataDoc.proto : this.dataDoc;
target.title = "-" + titlestr + (str.length > 40 ? "..." : "");
}
}
@@ -144,14 +152,14 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
e.stopPropagation();
} else {
if (de.data instanceof DragManager.DocumentDragData) {
- let ldocs = Cast(this.props.Document.subBulletDocs, listSpec(Doc));
+ let ldocs = Cast(this.dataDoc.subBulletDocs, listSpec(Doc));
if (!ldocs) {
- this.props.Document.subBulletDocs = new List<Doc>([]);
+ this.dataDoc.subBulletDocs = new List<Doc>([]);
}
- ldocs = Cast(this.props.Document.subBulletDocs, listSpec(Doc));
+ ldocs = Cast(this.dataDoc.subBulletDocs, listSpec(Doc));
if (!ldocs) return;
if (!ldocs || !ldocs[0] || ldocs[0] instanceof Promise || StrCast((ldocs[0] as Doc).layout).indexOf("CollectionView") === -1) {
- ldocs.splice(0, 0, Docs.StackingDocument([], { title: StrCast(this.props.Document.title) + "-subBullets", x: NumCast(this.props.Document.x), y: NumCast(this.props.Document.y) + NumCast(this.props.Document.height), width: 300, height: 300 }));
+ ldocs.splice(0, 0, Docs.StackingDocument([], { title: StrCast(this.dataDoc.title) + "-subBullets", x: NumCast(this.props.Document.x), y: NumCast(this.props.Document.y) + NumCast(this.props.Document.height), width: 300, height: 300 }));
this.props.addDocument && this.props.addDocument(ldocs[0] as Doc);
this.props.Document.templates = new List<string>([Templates.Bullet.Layout]);
this.props.Document.isBullet = true;
@@ -201,21 +209,26 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
this._reactionDisposer = reaction(
() => {
- const field = this.props.Document ? Cast(this.props.Document[this.props.fieldKey], RichTextField) : undefined;
+ const field = this.dataDoc ? Cast(this.dataDoc[this.props.fieldKey], RichTextField) : undefined;
return field ? field.Data : `{"doc":{"type":"doc","content":[]},"selection":{"type":"text","anchor":0,"head":0}}`;
},
field => this._editorView && !this._applyingChange &&
this._editorView.updateState(EditorState.fromJSON(config, JSON.parse(field)))
);
- this.setupEditor(config, this.props.Document, this.props.fieldKey);
+ this.setupEditor(config, this.dataDoc, this.props.fieldKey);
}
private setupEditor(config: any, doc: Doc, fieldKey: string) {
let field = doc ? Cast(doc[fieldKey], RichTextField) : undefined;
let startup = StrCast(doc.documentText);
startup = startup.startsWith("@@@") ? startup.replace("@@@", "") : "";
- if (!startup && !field && doc) {
- startup = StrCast(doc[fieldKey]);
+ if (!field && doc) {
+ let text = StrCast(doc[fieldKey]);
+ if (text) {
+ startup = text;
+ } else if (Cast(doc[fieldKey], "number")) {
+ startup = NumCast(doc[fieldKey], 99).toString();
+ }
}
if (this._proseRef) {
this._editorView = new EditorView(this._proseRef, {
@@ -254,7 +267,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
if (e.button === 0 && this.props.isSelected() && !e.altKey && !e.ctrlKey && !e.metaKey) {
e.stopPropagation();
if (this._toolTipTextMenu && this._toolTipTextMenu.tooltip) {
- this._toolTipTextMenu.tooltip.style.opacity = "0";
+ //this._toolTipTextMenu.tooltip.style.opacity = "0";
}
}
let ctrlKey = e.ctrlKey;
@@ -268,7 +281,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
this._linkClicked = href.replace(DocServer.prepend("/doc/"), "").split("?")[0];
if (this._linkClicked) {
DocServer.GetRefField(this._linkClicked).then(f => {
- (f instanceof Doc) && DocumentManager.Instance.jumpToDocument(f, ctrlKey, document => this.props.addDocTab(document, "inTab"));
+ (f instanceof Doc) && DocumentManager.Instance.jumpToDocument(f, ctrlKey, false, document => this.props.addDocTab(document, document, "inTab"));
});
e.stopPropagation();
e.preventDefault();
@@ -289,7 +302,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
}
onPointerUp = (e: React.PointerEvent): void => {
if (this._toolTipTextMenu && this._toolTipTextMenu.tooltip) {
- this._toolTipTextMenu.tooltip.style.opacity = "1";
+ //this._toolTipTextMenu.tooltip.style.opacity = "1";
}
if (e.buttons === 1 && this.props.isSelected() && !e.altKey) {
e.stopPropagation();
@@ -360,10 +373,10 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
// stop propagation doesn't seem to stop propagation of native keyboard events.
// so we set a flag on the native event that marks that the event's been handled.
(e.nativeEvent as any).DASHFormattedTextBoxHandled = true;
- if (StrCast(this.props.Document.title).startsWith("-") && this._editorView) {
+ if (StrCast(this.dataDoc.title).startsWith("-") && this._editorView) {
let str = this._editorView.state.doc.textContent;
let titlestr = str.substr(0, Math.min(40, str.length));
- let target = this.props.Document.proto ? this.props.Document.proto : this.props.Document;
+ let target = this.dataDoc.proto ? this.dataDoc.proto : this.dataDoc;
target.title = "-" + titlestr + (str.length > 40 ? "..." : "");
}
if (!this._undoTyping) {
@@ -372,11 +385,11 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
if (this.props.isOverlay && this.props.Document.autoHeight) {
let xf = this._ref.current!.getBoundingClientRect();
let scrBounds = this.props.ScreenToLocalTransform().transformBounds(0, 0, xf.width, xf.height);
- let nh = NumCast(this.props.Document.nativeHeight, 0);
+ let nh = NumCast(this.dataDoc.nativeHeight, 0);
let dh = NumCast(this.props.Document.height, 0);
let sh = scrBounds.height;
this.props.Document.height = nh ? dh / nh * sh : sh;
- this.props.Document.proto!.nativeHeight = nh ? sh : undefined;
+ this.dataDoc.proto!.nativeHeight = nh ? sh : undefined;
}
}
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index f56a2d926..0e6c1ee19 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -1,13 +1,13 @@
import { library } from '@fortawesome/fontawesome-svg-core';
import { faImage } from '@fortawesome/free-solid-svg-icons';
-import { action, observable } from 'mobx';
+import { action, observable, computed } from 'mobx';
import { observer } from "mobx-react";
import Lightbox from 'react-image-lightbox';
import 'react-image-lightbox/style.css'; // This only needs to be imported once in your app
import { Doc, HeightSym, WidthSym } from '../../../new_fields/Doc';
import { List } from '../../../new_fields/List';
import { createSchema, listSpec, makeInterface } from '../../../new_fields/Schema';
-import { Cast, FieldValue, NumCast, StrCast } from '../../../new_fields/Types';
+import { Cast, FieldValue, NumCast, StrCast, BoolCast } from '../../../new_fields/Types';
import { ImageField } from '../../../new_fields/URLField';
import { Utils } from '../../../Utils';
import { DragManager } from '../../util/DragManager';
@@ -36,7 +36,7 @@ const ImageDocument = makeInterface(pageSchema, positionSchema);
@observer
export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageDocument) {
- public static LayoutString() { return FieldView.LayoutString(ImageBox); }
+ public static LayoutString(fieldKey?: string) { return FieldView.LayoutString(ImageBox, fieldKey); }
private _imgRef: React.RefObject<HTMLImageElement> = React.createRef();
private _downX: number = 0;
private _downY: number = 0;
@@ -46,6 +46,8 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
private dropDisposer?: DragManager.DragDropDisposer;
+ @computed get dataDoc() { return BoolCast(this.props.Document.isTemplate) && this.props.DataDoc ? this.props.DataDoc : this.props.Document; }
+
protected createDropTarget = (ele: HTMLDivElement) => {
if (this.dropDisposer) {
@@ -66,19 +68,24 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
drop = (e: Event, de: DragManager.DropEvent) => {
if (de.data instanceof DragManager.DocumentDragData) {
de.data.droppedDocuments.forEach(action((drop: Doc) => {
- let layout = StrCast(drop.backgroundLayout);
- if (layout.indexOf(ImageBox.name) !== -1) {
- let imgData = this.props.Document[this.props.fieldKey];
- if (imgData instanceof ImageField) {
- Doc.SetOnPrototype(this.props.Document, "data", new List([imgData]));
- }
- let imgList = Cast(this.props.Document[this.props.fieldKey], listSpec(ImageField), [] as any[]);
- if (imgList) {
- let field = drop.data;
- if (field instanceof ImageField) imgList.push(field);
- else if (field instanceof List) imgList.concat(field);
- }
+ if (/*this.dataDoc !== this.props.Document &&*/ drop.data instanceof ImageField) {
+ Doc.GetProto(this.dataDoc)[this.props.fieldKey] = new ImageField(drop.data.url);
e.stopPropagation();
+ } else {
+ let layout = StrCast(drop.backgroundLayout);
+ if (layout.indexOf(ImageBox.name) !== -1) {
+ let imgData = this.dataDoc[this.props.fieldKey];
+ if (imgData instanceof ImageField) {
+ Doc.GetProto(this.dataDoc)[this.props.fieldKey] = new List([imgData]);
+ }
+ let imgList = Cast(this.dataDoc[this.props.fieldKey], listSpec(ImageField), [] as any[]);
+ if (imgList) {
+ let field = drop.data;
+ if (field instanceof ImageField) imgList.push(field);
+ else if (field instanceof List) imgList.concat(field);
+ }
+ e.stopPropagation();
+ }
}
}));
// de.data.removeDocument() bcz: need to implement
@@ -206,7 +213,7 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
let paths: string[] = ["http://www.cs.brown.edu/~bcz/noImage.png"];
// this._curSuffix = "";
// if (w > 20) {
- let field = this.Document[this.props.fieldKey];
+ let field = this.dataDoc[this.props.fieldKey];
// if (w < 100 && this._smallRetryCount < 10) this._curSuffix = "_s";
// else if (w < 600 && this._mediumRetryCount < 10) this._curSuffix = "_m";
// else if (this._largeRetryCount < 10) this._curSuffix = "_l";
@@ -214,8 +221,8 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
else if (field instanceof List) paths = field.filter(val => val instanceof ImageField).map(p => this.choosePath((p as ImageField).url));
// }
let interactive = InkingControl.Instance.selectedTool ? "" : "-interactive";
- let rotation = NumCast(this.props.Document.rotation, 0);
- let aspect = (rotation % 180) ? this.props.Document[HeightSym]() / this.props.Document[WidthSym]() : 1;
+ let rotation = NumCast(this.dataDoc.rotation, 0);
+ let aspect = (rotation % 180) ? this.dataDoc[HeightSym]() / this.dataDoc[WidthSym]() : 1;
let shift = (rotation % 180) ? (nativeHeight - nativeWidth / aspect) / 2 : 0;
return (
<div id={id} className={`imageBox-cont${interactive}`} style={{ background: "transparent" }}
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index da0aa6ac4..ede4e3858 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -27,11 +27,13 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
render() {
let props: FieldViewProps = {
Document: this.props.doc,
+ DataDoc: this.props.doc,
ContainingCollectionView: undefined,
fieldKey: this.props.keyName,
+ fieldExt: "",
isSelected: returnFalse,
select: emptyFunction,
- isTopMost: false,
+ renderDepth: 1,
selectOnLoad: false,
active: returnFalse,
whenActiveChanged: emptyFunction,
diff --git a/src/client/views/nodes/LinkBox.scss b/src/client/views/nodes/LinkBox.scss
deleted file mode 100644
index 639f83b38..000000000
--- a/src/client/views/nodes/LinkBox.scss
+++ /dev/null
@@ -1,66 +0,0 @@
-@import "../globalCssVariables";
-.link-container {
- width: 100%;
- height: 50px;
- display: flex;
- flex-direction: row;
- border-top: 0.5px solid #bababa;
-}
-
-.info-container {
- width: 65%;
- padding-top: 5px;
- padding-left: 5px;
- display: flex;
- flex-direction: column
-}
-
-.link-name {
- font-size: 11px;
-}
-
-.doc-name {
- font-size: 8px;
-}
-
-.button-container {
- width: 35%;
- padding-top: 8px;
- display: flex;
- flex-direction: row;
-}
-
-.button {
- height: 20px;
- width: 20px;
- margin: 8px 4px;
- border-radius: 50%;
- opacity: 0.9;
- pointer-events: auto;
- background-color: $dark-color;
- color: $light-color;
- text-transform: uppercase;
- letter-spacing: 2px;
- font-size: 60%;
- transition: transform 0.2s;
-}
-
-.button:hover {
- background: $main-accent;
- cursor: pointer;
-}
-
-// .fa-icon-view {
-// margin-left: 3px;
-// margin-top: 5px;
-// }
-
-.fa-icon-edit {
- margin-left: 6px;
- margin-top: 6px;
-}
-
-.fa-icon-delete {
- margin-left: 7px;
- margin-top: 6px;
-} \ No newline at end of file
diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx
deleted file mode 100644
index 68b692aad..000000000
--- a/src/client/views/nodes/LinkBox.tsx
+++ /dev/null
@@ -1,86 +0,0 @@
-import { library } from '@fortawesome/fontawesome-svg-core';
-import { faEdit, faEye, faTimes } 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 { CollectionDockingView } from "../collections/CollectionDockingView";
-import './LinkBox.scss';
-import React = require("react");
-import { Doc } from '../../../new_fields/Doc';
-import { Cast, NumCast } from '../../../new_fields/Types';
-import { listSpec } from '../../../new_fields/Schema';
-import { action } from 'mobx';
-
-
-library.add(faEye);
-library.add(faEdit);
-library.add(faTimes);
-
-interface Props {
- linkDoc: Doc;
- linkName: String;
- pairedDoc: Doc;
- type: String;
- showEditor: () => void;
-}
-
-@observer
-export class LinkBox extends React.Component<Props> {
-
- @undoBatch
- onViewButtonPressed = async (e: React.PointerEvent): Promise<void> => {
- e.stopPropagation();
- DocumentManager.Instance.jumpToDocument(this.props.pairedDoc, e.altKey);
- }
-
- onEditButtonPressed = (e: React.PointerEvent): void => {
- e.stopPropagation();
-
- this.props.showEditor();
- }
-
- @action
- onDeleteButtonPressed = async (e: React.PointerEvent): Promise<void> => {
- e.stopPropagation();
- const [linkedFrom, linkedTo] = await Promise.all([Cast(this.props.linkDoc.linkedFrom, Doc), Cast(this.props.linkDoc.linkedTo, Doc)]);
- if (linkedFrom) {
- const linkedToDocs = Cast(linkedFrom.linkedToDocs, listSpec(Doc));
- if (linkedToDocs) {
- linkedToDocs.splice(linkedToDocs.indexOf(this.props.linkDoc), 1);
- }
- }
- if (linkedTo) {
- const linkedFromDocs = Cast(linkedTo.linkedFromDocs, listSpec(Doc));
- if (linkedFromDocs) {
- linkedFromDocs.splice(linkedFromDocs.indexOf(this.props.linkDoc), 1);
- }
- }
- }
-
- render() {
-
- return (
- //<LinkEditor linkBox={this} linkDoc={this.props.linkDoc} />
- <div className="link-container">
- <div className="info-container" onPointerDown={this.onViewButtonPressed}>
- <div className="link-name">
- <p>{this.props.linkName}</p>
- </div>
- <div className="doc-name">
- <p>{this.props.type}{this.props.pairedDoc.Title}</p>
- </div>
- </div>
-
- <div className="button-container">
- {/* <div title="Follow Link" className="button" onPointerDown={this.onViewButtonPressed}>
- <FontAwesomeIcon className="fa-icon-view" icon="eye" size="sm" /></div> */}
- <div title="Edit Link" className="button" onPointerDown={this.onEditButtonPressed}>
- <FontAwesomeIcon className="fa-icon-edit" icon="edit" size="sm" /></div>
- <div title="Delete Link" className="button" onPointerDown={this.onDeleteButtonPressed}>
- <FontAwesomeIcon className="fa-icon-delete" icon="times" size="sm" /></div>
- </div>
- </div>
- );
- }
-} \ No newline at end of file
diff --git a/src/client/views/nodes/LinkEditor.scss b/src/client/views/nodes/LinkEditor.scss
index 9629585d7..3c49c2212 100644
--- a/src/client/views/nodes/LinkEditor.scss
+++ b/src/client/views/nodes/LinkEditor.scss
@@ -1,42 +1,133 @@
@import "../globalCssVariables";
-.edit-container {
+
+.linkEditor {
width: 100%;
height: auto;
+ font-size: 12px; // TODO
+}
+
+.linkEditor-back {
+ margin-bottom: 6px;
+}
+
+.linkEditor-info {
+ border-bottom: 0.5px solid $light-color-secondary;
+ padding-bottom: 6px;
+ margin-bottom: 6px;
display: flex;
- flex-direction: column;
+ justify-content: space-between;
+
+ .linkEditor-linkedTo {
+ width: calc(100% - 26px);
+ }
}
-.name-input {
- margin-bottom: 10px;
- padding: 5px;
- font-size: 12px;
- border: 1px solid #bababa;
+.linkEditor-button {
+ width: 20px;
+ height: 20px;
+ margin-left: 6px;
+ padding: 0;
+ // font-size: 12px;
+ border-radius: 10px;
+
+ &:disabled {
+ background-color: gray;
+ }
}
-.description-input {
- font-size: 11px;
- padding: 5px;
- margin-bottom: 10px;
- border: 1px solid #bababa;
+.linkEditor-groupsLabel {
+ display: flex;
+ justify-content: space-between;
+}
+
+.linkEditor-group {
+ background-color: $light-color-secondary;
+ padding: 6px;
+ margin: 3px 0;
+ border-radius: 3px;
+
+ .linkEditor-group-row {
+ // display: flex;
+ margin-bottom: 3px;
+
+ .linkEditor-group-row-label {
+ margin-right: 6px;
+ }
+ }
+
+ .linkEditor-metadata-row {
+ display: flex;
+ justify-content: space-between;
+ margin-bottom: 6px;
+
+ .linkEditor-error {
+ border-color: red;
+ }
+
+ input {
+ width: calc(50% - 16px);
+ height: 20px;
+ }
+
+ button {
+ width: 20px;
+ height: 20px;
+ margin-left: 3px;
+ padding: 0;
+ font-size: 10px;
+ }
+ }
}
-.save-button {
- width: 50px;
- height: 22px;
- pointer-events: auto;
- background-color: $dark-color;
- color: $light-color;
- text-transform: uppercase;
- letter-spacing: 2px;
- padding: 2px;
- font-size: 10px;
- margin: 0 auto;
- transition: transform 0.2s;
- text-align: center;
- line-height: 20px;
+
+.linkEditor-dropdown {
+ width: 100%;
+ position: relative;
+ z-index: 999;
+
+ input {
+ width: 100%;
+ }
+
+ .linkEditor-options-wrapper {
+ width: 100%;
+ position: absolute;
+ top: 19px;
+ left: 0;
+ display: flex;
+ flex-direction: column;
+ }
+
+ .linkEditor-option {
+ background-color: $light-color-secondary;
+ border: 1px solid $intermediate-color;
+ border-top: 0;
+ padding: 3px;
+ cursor: pointer;
+
+ &:hover {
+ background-color: lightgray;
+ }
+ }
}
-.save-button:hover {
- background: $main-accent;
- cursor: pointer;
+.linkEditor-group-buttons {
+ height: 20px;
+ display: flex;
+ justify-content: flex-end;
+
+ .linkEditor-button {
+ margin-left: 6px;
+ }
+
+ // .linkEditor-groupOpts {
+ // width: calc(20% - 3px);
+ // height: 20px;
+ // padding: 0;
+ // font-size: 10px;
+
+ // &:disabled {
+ // background-color: gray;
+ // }
+ // }
} \ No newline at end of file
diff --git a/src/client/views/nodes/LinkEditor.tsx b/src/client/views/nodes/LinkEditor.tsx
index 71a423338..22da732cf 100644
--- a/src/client/views/nodes/LinkEditor.tsx
+++ b/src/client/views/nodes/LinkEditor.tsx
@@ -1,57 +1,345 @@
-import { observable, computed, action } from "mobx";
+import { observable, computed, action, trace } from "mobx";
import React = require("react");
-import { SelectionManager } from "../../util/SelectionManager";
import { observer } from "mobx-react";
import './LinkEditor.scss';
-import { props } from "bluebird";
-import { DocumentView } from "./DocumentView";
-import { link } from "fs";
-import { StrCast } from "../../../new_fields/Types";
+import { StrCast, Cast, FieldValue } from "../../../new_fields/Types";
import { Doc } from "../../../new_fields/Doc";
+import { LinkManager } from "../../util/LinkManager";
+import { Docs } from "../../documents/Documents";
+import { Utils } from "../../../Utils";
+import { faArrowLeft, faEllipsisV, faTable, faTrash, faCog, faExchangeAlt, faTimes, faPlus } from '@fortawesome/free-solid-svg-icons';
+import { library } from "@fortawesome/fontawesome-svg-core";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { SetupDrag } from "../../util/DragManager";
-interface Props {
- linkDoc: Doc;
- showLinks: () => void;
+library.add(faArrowLeft, faEllipsisV, faTable, faTrash, faCog, faExchangeAlt, faTimes, faPlus);
+
+
+interface GroupTypesDropdownProps {
+ groupType: string;
+ setGroupType: (group: string) => void;
+}
+// this dropdown could be generalized
+@observer
+class GroupTypesDropdown extends React.Component<GroupTypesDropdownProps> {
+ @observable private _searchTerm: string = "";
+ @observable private _groupType: string = this.props.groupType;
+
+ @action setSearchTerm = (value: string): void => { this._searchTerm = value; };
+ @action setGroupType = (value: string): void => { this._groupType = value; };
+
+ @action
+ createGroup = (groupType: string): void => {
+ this.props.setGroupType(groupType);
+ LinkManager.Instance.addGroupType(groupType);
+ }
+
+ onChange = (val: string): void => {
+ this.setSearchTerm(val);
+ this.setGroupType(val);
+ }
+
+ renderOptions = (): JSX.Element[] | JSX.Element => {
+ if (this._searchTerm === "") return <></>;
+
+ let allGroupTypes = Array.from(LinkManager.Instance.getAllGroupTypes());
+ let groupOptions = allGroupTypes.filter(groupType => groupType.toUpperCase().indexOf(this._searchTerm.toUpperCase()) > -1);
+ let exactFound = groupOptions.findIndex(groupType => groupType.toUpperCase() === this._searchTerm.toUpperCase()) > -1;
+
+ let options = groupOptions.map(groupType => {
+ return <div key={groupType} className="linkEditor-option"
+ onClick={() => { this.props.setGroupType(groupType); this.setGroupType(groupType); this.setSearchTerm(""); }}>{groupType}</div>;
+ });
+
+ // if search term does not already exist as a group type, give option to create new group type
+ if (!exactFound && this._searchTerm !== "") {
+ options.push(<div key={""} className="linkEditor-option"
+ onClick={() => { this.createGroup(this._searchTerm); this.setGroupType(this._searchTerm); this.setSearchTerm(""); }}>Define new "{this._searchTerm}" relationship</div>);
+ }
+
+ return options;
+ }
+
+ render() {
+ return (
+ <div className="linkEditor-dropdown">
+ <input type="text" value={this._groupType} placeholder="Search for or create a new group"
+ onChange={e => this.onChange(e.target.value)}></input>
+ <div className="linkEditor-options-wrapper">
+ {this.renderOptions()}
+ </div>
+ </div >
+ );
+ }
}
+
+interface LinkMetadataEditorProps {
+ id: string;
+ groupType: string;
+ mdDoc: Doc;
+ mdKey: string;
+ mdValue: string;
+ changeMdIdKey: (id: string, newKey: string) => void;
+}
@observer
-export class LinkEditor extends React.Component<Props> {
+class LinkMetadataEditor extends React.Component<LinkMetadataEditorProps> {
+ @observable private _key: string = this.props.mdKey;
+ @observable private _value: string = this.props.mdValue;
+ @observable private _keyError: boolean = false;
- @observable private _nameInput: string = StrCast(this.props.linkDoc.title);
- @observable private _descriptionInput: string = StrCast(this.props.linkDoc.linkDescription);
+ @action
+ setMetadataKey = (value: string): void => {
+ let groupMdKeys = LinkManager.Instance.getMetadataKeysInGroup(this.props.groupType);
+ // don't allow user to create existing key
+ let newIndex = groupMdKeys.findIndex(key => key.toUpperCase() === value.toUpperCase());
+ if (newIndex > -1) {
+ this._keyError = true;
+ this._key = value;
+ return;
+ } else {
+ this._keyError = false;
+ }
- onSaveButtonPressed = (e: React.PointerEvent): void => {
- e.stopPropagation();
+ // set new value for key
+ let currIndex = groupMdKeys.findIndex(key => {
+ return StrCast(key).toUpperCase() === this._key.toUpperCase();
+ });
+ if (currIndex === -1) console.error("LinkMetadataEditor: key was not found");
+ groupMdKeys[currIndex] = value;
- let linkDoc = this.props.linkDoc.proto ? this.props.linkDoc.proto : this.props.linkDoc;
- linkDoc.title = this._nameInput;
- linkDoc.linkDescription = this._descriptionInput;
+ this.props.changeMdIdKey(this.props.id, value);
+ this._key = value;
+ LinkManager.Instance.setMetadataKeysForGroup(this.props.groupType, [...groupMdKeys]);
+ }
- this.props.showLinks();
+ @action
+ setMetadataValue = (value: string): void => {
+ if (!this._keyError) {
+ this._value = value;
+ this.props.mdDoc[this._key] = value;
+ }
}
+ @action
+ removeMetadata = (): void => {
+ let groupMdKeys = LinkManager.Instance.getMetadataKeysInGroup(this.props.groupType);
+ let index = groupMdKeys.findIndex(key => key.toUpperCase() === this._key.toUpperCase());
+ if (index === -1) console.error("LinkMetadataEditor: key was not found");
+ groupMdKeys.splice(index, 1);
- render() {
+ LinkManager.Instance.setMetadataKeysForGroup(this.props.groupType, groupMdKeys);
+ this._key = "";
+ }
+ render() {
return (
- <div className="edit-container">
- <input onChange={this.onNameChanged} className="name-input" type="text" value={this._nameInput} placeholder="Name . . ."></input>
- <textarea onChange={this.onDescriptionChanged} className="description-input" value={this._descriptionInput} placeholder="Description . . ."></textarea>
- <div className="save-button" onPointerDown={this.onSaveButtonPressed}>SAVE</div>
+ <div className="linkEditor-metadata-row">
+ <input className={this._keyError ? "linkEditor-error" : ""} type="text" value={this._key === "new key" ? "" : this._key} placeholder="key" onChange={e => this.setMetadataKey(e.target.value)}></input>:
+ <input type="text" value={this._value} placeholder="value" onChange={e => this.setMetadataValue(e.target.value)}></input>
+ <button onClick={() => this.removeMetadata()}><FontAwesomeIcon icon="times" size="sm" /></button>
</div>
+ );
+ }
+}
+
+interface LinkGroupEditorProps {
+ sourceDoc: Doc;
+ linkDoc: Doc;
+ groupDoc: Doc;
+}
+@observer
+export class LinkGroupEditor extends React.Component<LinkGroupEditorProps> {
+ private _metadataIds: Map<string, string> = new Map();
+
+ constructor(props: LinkGroupEditorProps) {
+ super(props);
+
+ let groupMdKeys = LinkManager.Instance.getMetadataKeysInGroup(StrCast(props.groupDoc.type));
+ groupMdKeys.forEach(key => {
+ this._metadataIds.set(key, Utils.GenerateGuid());
+ });
+ }
+
+ @action
+ setGroupType = (groupType: string): void => {
+ this.props.groupDoc.type = groupType;
+ }
+
+ removeGroupFromLink = (groupType: string): void => {
+ LinkManager.Instance.removeGroupFromAnchor(this.props.linkDoc, this.props.sourceDoc, groupType);
+ }
+
+ deleteGroup = (groupType: string): void => {
+ LinkManager.Instance.deleteGroupType(groupType);
+ }
+
+ copyGroup = (groupType: string): void => {
+ let sourceGroupDoc = this.props.groupDoc;
+ let sourceMdDoc = Cast(sourceGroupDoc.metadata, Doc, new Doc);
+
+ let destDoc = LinkManager.Instance.getOppositeAnchor(this.props.linkDoc, this.props.sourceDoc);
+ // let destGroupList = LinkManager.Instance.getAnchorGroups(this.props.linkDoc, destDoc);
+ let keys = LinkManager.Instance.getMetadataKeysInGroup(groupType);
+
+ // create new metadata doc with copied kvp
+ let destMdDoc = new Doc();
+ destMdDoc.anchor1 = StrCast(sourceMdDoc.anchor2);
+ destMdDoc.anchor2 = StrCast(sourceMdDoc.anchor1);
+ keys.forEach(key => {
+ let val = sourceMdDoc[key] === undefined ? "" : StrCast(sourceMdDoc[key]);
+ destMdDoc[key] = val;
+ });
+
+ // create new group doc with new metadata doc
+ let destGroupDoc = new Doc();
+ destGroupDoc.type = groupType;
+ destGroupDoc.metadata = destMdDoc;
+
+ LinkManager.Instance.addGroupToAnchor(this.props.linkDoc, destDoc, destGroupDoc, true);
+ }
+
+ @action
+ addMetadata = (groupType: string): void => {
+ this._metadataIds.set("new key", Utils.GenerateGuid());
+ let mdKeys = LinkManager.Instance.getMetadataKeysInGroup(groupType);
+ // only add "new key" if there is no other key with value "new key"; prevents spamming
+ if (mdKeys.indexOf("new key") === -1) mdKeys.push("new key");
+ LinkManager.Instance.setMetadataKeysForGroup(groupType, mdKeys);
+ }
+
+ // for key rendering purposes
+ changeMdIdKey = (id: string, newKey: string) => {
+ this._metadataIds.set(newKey, id);
+ }
+
+ renderMetadata = (): JSX.Element[] => {
+ let metadata: Array<JSX.Element> = [];
+ let groupDoc = this.props.groupDoc;
+ const mdDoc = FieldValue(Cast(groupDoc.metadata, Doc));
+ if (!mdDoc) {
+ return [];
+ }
+ let groupType = StrCast(groupDoc.type);
+ let groupMdKeys = LinkManager.Instance.getMetadataKeysInGroup(groupType);
+
+ groupMdKeys.forEach((key) => {
+ let val = StrCast(mdDoc[key]);
+ metadata.push(
+ <LinkMetadataEditor key={"mded-" + this._metadataIds.get(key)} id={this._metadataIds.get(key)!} groupType={groupType} mdDoc={mdDoc} mdKey={key} mdValue={val} changeMdIdKey={this.changeMdIdKey} />
+ );
+ });
+ return metadata;
+ }
+
+ viewGroupAsTable = (groupType: string): JSX.Element => {
+ let keys = LinkManager.Instance.getMetadataKeysInGroup(groupType);
+ let index = keys.indexOf("");
+ if (index > -1) keys.splice(index, 1);
+ let cols = ["anchor1", "anchor2", ...[...keys]];
+ let docs: Doc[] = LinkManager.Instance.getAllMetadataDocsInGroup(groupType);
+ let createTable = action(() => Docs.SchemaDocument(cols, docs, { width: 500, height: 300, title: groupType + " table" }));
+ let ref = React.createRef<HTMLDivElement>();
+ return <div ref={ref}><button className="linkEditor-button" onPointerDown={SetupDrag(ref, createTable)} title="Drag to view relationship table"><FontAwesomeIcon icon="table" size="sm" /></button></div>;
+ }
+
+ render() {
+ let groupType = StrCast(this.props.groupDoc.type);
+ // if ((groupType && LinkManager.Instance.getMetadataKeysInGroup(groupType).length > 0) || groupType === "") {
+ let buttons;
+ if (groupType === "") {
+ buttons = (
+ <>
+ <button className="linkEditor-button" disabled={true} title="Add KVP"><FontAwesomeIcon icon="plus" size="sm" /></button>
+ <button className="linkEditor-button" disabled title="Copy group to opposite anchor"><FontAwesomeIcon icon="exchange-alt" size="sm" /></button>
+ <button className="linkEditor-button" onClick={() => this.removeGroupFromLink(groupType)} title="Remove group from link"><FontAwesomeIcon icon="times" size="sm" /></button>
+ <button className="linkEditor-button" disabled title="Delete group"><FontAwesomeIcon icon="trash" size="sm" /></button>
+ <button className="linkEditor-button" disabled title="Drag to view relationship table"><FontAwesomeIcon icon="table" size="sm" /></button>
+ </>
+ );
+ } else {
+ buttons = (
+ <>
+ <button className="linkEditor-button" onClick={() => this.addMetadata(groupType)} title="Add KVP"><FontAwesomeIcon icon="plus" size="sm" /></button>
+ <button className="linkEditor-button" onClick={() => this.copyGroup(groupType)} title="Copy group to opposite anchor"><FontAwesomeIcon icon="exchange-alt" size="sm" /></button>
+ <button className="linkEditor-button" onClick={() => this.removeGroupFromLink(groupType)} title="Remove group from link"><FontAwesomeIcon icon="times" size="sm" /></button>
+ <button className="linkEditor-button" onClick={() => this.deleteGroup(groupType)} title="Delete group"><FontAwesomeIcon icon="trash" size="sm" /></button>
+ {this.viewGroupAsTable(groupType)}
+ </>
+ );
+ }
+ trace();
+ return (
+ <div className="linkEditor-group">
+ <div className="linkEditor-group-row">
+ <p className="linkEditor-group-row-label">type:</p>
+ <GroupTypesDropdown groupType={groupType} setGroupType={this.setGroupType} />
+ </div>
+ {this.renderMetadata().length > 0 ? <p className="linkEditor-group-row-label">metadata:</p> : <></>}
+ {this.renderMetadata()}
+ <div className="linkEditor-group-buttons">
+ {buttons}
+ </div>
+ </div>
);
}
+}
+
+
+interface LinkEditorProps {
+ sourceDoc: Doc;
+ linkDoc: Doc;
+ showLinks: () => void;
+}
+@observer
+export class LinkEditor extends React.Component<LinkEditorProps> {
@action
- onNameChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
- this._nameInput = e.target.value;
+ deleteLink = (): void => {
+ LinkManager.Instance.deleteLink(this.props.linkDoc);
+ this.props.showLinks();
}
@action
- onDescriptionChanged = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
- this._descriptionInput = e.target.value;
+ addGroup = (): void => {
+ // create new metadata document for group
+ let mdDoc = new Doc();
+ mdDoc.anchor1 = this.props.sourceDoc.title;
+ mdDoc.anchor2 = LinkManager.Instance.getOppositeAnchor(this.props.linkDoc, this.props.sourceDoc).title;
+
+ // create new group document
+ let groupDoc = new Doc();
+ groupDoc.type = "";
+ groupDoc.metadata = mdDoc;
+
+ LinkManager.Instance.addGroupToAnchor(this.props.linkDoc, this.props.sourceDoc, groupDoc);
+ }
+
+ render() {
+ let destination = LinkManager.Instance.getOppositeAnchor(this.props.linkDoc, this.props.sourceDoc);
+
+ let groupList = LinkManager.Instance.getAnchorGroups(this.props.linkDoc, this.props.sourceDoc);
+ let groups = groupList.map(groupDoc => {
+ return <LinkGroupEditor key={"gred-" + StrCast(groupDoc.type)} linkDoc={this.props.linkDoc} sourceDoc={this.props.sourceDoc} groupDoc={groupDoc} />;
+ });
+
+ return (
+ <div className="linkEditor">
+ <button className="linkEditor-back" onPointerDown={() => this.props.showLinks()}><FontAwesomeIcon icon="arrow-left" size="sm" /></button>
+ <div className="linkEditor-info">
+ <p className="linkEditor-linkedTo">editing link to: <b>{destination.proto!.title}</b></p>
+ <button className="linkEditor-button" onPointerDown={() => this.deleteLink()} title="Delete link"><FontAwesomeIcon icon="trash" size="sm" /></button>
+ </div>
+ <div className="linkEditor-groupsLabel">
+ <b>Relationships:</b>
+ <button className="linkEditor-button" onClick={() => this.addGroup()} title=" Add Group"><FontAwesomeIcon icon="plus" size="sm" /></button>
+ </div>
+ {groups.length > 0 ? groups : <div className="linkEditor-group">There are currently no relationships associated with this link.</div>}
+ </div>
+
+ );
}
} \ No newline at end of file
diff --git a/src/client/views/nodes/LinkMenu.scss b/src/client/views/nodes/LinkMenu.scss
index dedcce6ef..a4018bd2d 100644
--- a/src/client/views/nodes/LinkMenu.scss
+++ b/src/client/views/nodes/LinkMenu.scss
@@ -1,21 +1,137 @@
-#linkMenu-container {
+@import "../globalCssVariables";
+
+.linkMenu {
width: 100%;
height: auto;
- display: flex;
- flex-direction: column;
}
-#linkMenu-searchBar {
- width: 100%;
- padding: 5px;
- margin-bottom: 10px;
+.linkMenu-list {
+ max-height: 200px;
+ overflow-y: scroll;
+}
+
+.linkMenu-group {
+ border-bottom: 0.5px solid lightgray;
+ padding: 5px 0;
+
+
+ &:last-child {
+ border-bottom: none;
+ }
+
+ .linkMenu-group-name {
+ display: flex;
+
+ &:hover {
+ p {
+ background-color: lightgray;
+ }
+ p.expand-one {
+ width: calc(100% - 26px);
+ }
+ .linkEditor-tableButton {
+ display: block;
+ }
+ }
+
+ p {
+ width: 100%;
+ padding: 4px 6px;
+ line-height: 12px;
+ border-radius: 5px;
+ font-weight: bold;
+ }
+
+ .linkEditor-tableButton {
+ display: none;
+ }
+ }
+}
+
+.linkMenu-item {
+ // border-top: 0.5px solid $main-accent;
+ position: relative;
+ display: flex;
font-size: 12px;
- border: 1px solid #bababa;
+
+
+ .link-name {
+ position: relative;
+
+ p {
+ padding: 4px 6px;
+ line-height: 12px;
+ border-radius: 5px;
+ overflow-wrap: break-word;
+ }
+ }
+
+ .linkMenu-item-content {
+ width: 100%;
+ }
+
+ .link-metadata {
+ padding: 0 10px 0 16px;
+ margin-bottom: 4px;
+ color: $main-accent;
+ font-style: italic;
+ font-size: 10.5px;
+ }
+
+ &:hover {
+ .linkMenu-item-buttons {
+ display: flex;
+ }
+ .linkMenu-item-content {
+ &.expand-two p {
+ width: calc(100% - 52px);
+ background-color: lightgray;
+ }
+ &.expand-three p {
+ width: calc(100% - 84px);
+ background-color: lightgray;
+ }
+ }
+ }
}
-#linkMenu-list {
- margin-top: 5px;
- width: 100%;
- height: 100px;
- overflow-y: scroll;
-} \ No newline at end of file
+.linkMenu-item-buttons {
+ display: none;
+ position: absolute;
+ top: 50%;
+ right: 0;
+ transform: translateY(-50%);
+
+ .button {
+ width: 20px;
+ height: 20px;
+ margin: 0;
+ margin-right: 6px;
+ border-radius: 50%;
+ cursor: pointer;
+ pointer-events: auto;
+ background-color: $dark-color;
+ color: $light-color;
+ font-size: 65%;
+ transition: transform 0.2s;
+ text-align: center;
+ position: relative;
+
+ .fa-icon {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ }
+
+ &:last-child {
+ margin-right: 0;
+ }
+ &:hover {
+ background: $main-accent;
+ }
+ }
+}
+
+
+
diff --git a/src/client/views/nodes/LinkMenu.tsx b/src/client/views/nodes/LinkMenu.tsx
index c34ccdccb..cccf3c329 100644
--- a/src/client/views/nodes/LinkMenu.tsx
+++ b/src/client/views/nodes/LinkMenu.tsx
@@ -1,11 +1,17 @@
import { action, observable } from "mobx";
import { observer } from "mobx-react";
import { DocumentView } from "./DocumentView";
-import { LinkBox } from "./LinkBox";
import { LinkEditor } from "./LinkEditor";
import './LinkMenu.scss';
import React = require("react");
-import { Doc, DocListCast } from "../../../new_fields/Doc";
+import { Doc } from "../../../new_fields/Doc";
+import { LinkManager } from "../../util/LinkManager";
+import { LinkMenuGroup } from "./LinkMenuGroup";
+import { faTrash } from '@fortawesome/free-solid-svg-icons';
+import { library } from "@fortawesome/fontawesome-svg-core";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+
+library.add(faTrash);
import { Cast, FieldValue, StrCast } from "../../../new_fields/Types";
import { Id } from "../../../new_fields/FieldSymbols";
import { DocTypes } from "../../documents/Documents";
@@ -20,34 +26,46 @@ export class LinkMenu extends React.Component<Props> {
@observable private _editingLink?: Doc;
- renderLinkItems(links: Doc[], key: string, type: string) {
- return links.map(link => {
- let doc = FieldValue(Cast(link[key], Doc));
- if (doc) {
- return <LinkBox key={doc[Id]} linkDoc={link} linkName={StrCast(link.title)} pairedDoc={doc} showEditor={action(() => this._editingLink = link)} type={DocTypes.LINK} />;
- }
+ @action
+ componentWillReceiveProps() {
+ this._editingLink = undefined;
+ }
+
+ clearAllLinks = () => {
+ LinkManager.Instance.deleteAllLinksOnAnchor(this.props.docView.props.Document);
+ }
+
+ renderAllGroups = (groups: Map<string, Array<Doc>>): Array<JSX.Element> => {
+ let linkItems: Array<JSX.Element> = [];
+ groups.forEach((group, groupType) => {
+ linkItems.push(
+ <LinkMenuGroup key={groupType} sourceDoc={this.props.docView.props.Document} group={group} groupType={groupType} showEditor={action((linkDoc: Doc) => this._editingLink = linkDoc)} />
+ );
});
+
+ // if source doc has no links push message
+ if (linkItems.length === 0) linkItems.push(<p key="">No links have been created yet. Drag the linking button onto another document to create a link.</p>);
+
+ return linkItems;
}
render() {
- //get list of links from document
- let linkFrom = DocListCast(this.props.docView.props.Document.linkedFromDocs);
- let linkTo = DocListCast(this.props.docView.props.Document.linkedToDocs);
+ let sourceDoc = this.props.docView.props.Document;
+ let groups: Map<string, Doc[]> = LinkManager.Instance.getRelatedGroupedLinks(sourceDoc);
if (this._editingLink === undefined) {
return (
- <div id="linkMenu-container">
+ <div className="linkMenu">
+ <button className="linkEditor-button linkEditor-clearButton" onClick={() => this.clearAllLinks()} title="Clear all links"><FontAwesomeIcon icon="trash" size="sm" /></button>
{/* <input id="linkMenu-searchBar" type="text" placeholder="Search..."></input> */}
- <div id="linkMenu-list">
- {this.renderLinkItems(linkTo, "linkedTo", "Destination: ")}
- {this.renderLinkItems(linkFrom, "linkedFrom", "Source: ")}
+ <div className="linkMenu-list">
+ {this.renderAllGroups(groups)}
</div>
</div>
);
} else {
return (
- <LinkEditor linkDoc={this._editingLink} showLinks={action(() => this._editingLink = undefined)}></LinkEditor>
+ <LinkEditor sourceDoc={this.props.docView.props.Document} linkDoc={this._editingLink} showLinks={action(() => this._editingLink = undefined)}></LinkEditor>
);
}
-
}
} \ No newline at end of file
diff --git a/src/client/views/nodes/LinkMenuGroup.tsx b/src/client/views/nodes/LinkMenuGroup.tsx
new file mode 100644
index 000000000..e4cf56d20
--- /dev/null
+++ b/src/client/views/nodes/LinkMenuGroup.tsx
@@ -0,0 +1,92 @@
+import { action, observable } from "mobx";
+import { observer } from "mobx-react";
+import { DocumentView } from "./DocumentView";
+import { LinkMenuItem } from "./LinkMenuItem";
+import { LinkEditor } from "./LinkEditor";
+import './LinkMenu.scss';
+import React = require("react");
+import { Doc, DocListCast } from "../../../new_fields/Doc";
+import { Id } from "../../../new_fields/FieldSymbols";
+import { LinkManager } from "../../util/LinkManager";
+import { DragLinksAsDocuments, DragManager, SetupDrag } from "../../util/DragManager";
+import { emptyFunction } from "../../../Utils";
+import { Docs } from "../../documents/Documents";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+
+interface LinkMenuGroupProps {
+ sourceDoc: Doc;
+ group: Doc[];
+ groupType: string;
+ showEditor: (linkDoc: Doc) => void;
+}
+
+@observer
+export class LinkMenuGroup extends React.Component<LinkMenuGroupProps> {
+
+ private _drag = React.createRef<HTMLDivElement>();
+ private _table = React.createRef<HTMLDivElement>();
+
+ 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);
+
+ let draggedDocs = this.props.group.map(linkDoc => LinkManager.Instance.getOppositeAnchor(linkDoc, this.props.sourceDoc));
+ let dragData = new DragManager.DocumentDragData(draggedDocs, draggedDocs.map(d => undefined));
+
+ DragManager.StartLinkedDocumentDrag([this._drag.current], this.props.sourceDoc, dragData, e.x, e.y, {
+ handlers: {
+ dragComplete: action(emptyFunction),
+ },
+ hideSource: false
+ });
+ }
+ e.stopPropagation();
+ }
+
+ viewGroupAsTable = (groupType: string): JSX.Element => {
+ let keys = LinkManager.Instance.getMetadataKeysInGroup(groupType);
+ let index = keys.indexOf("");
+ if (index > -1) keys.splice(index, 1);
+ let cols = ["anchor1", "anchor2", ...[...keys]];
+ let docs: Doc[] = LinkManager.Instance.getAllMetadataDocsInGroup(groupType);
+ let createTable = action(() => Docs.SchemaDocument(cols, docs, { width: 500, height: 300, title: groupType + " table" }));
+ let ref = React.createRef<HTMLDivElement>();
+ return <div ref={ref}><button className="linkEditor-button linkEditor-tableButton" onPointerDown={SetupDrag(ref, createTable)} title="Drag to view relationship table"><FontAwesomeIcon icon="table" size="sm" /></button></div>;
+ }
+
+ render() {
+ let groupItems = this.props.group.map(linkDoc => {
+ let destination = LinkManager.Instance.getOppositeAnchor(linkDoc, this.props.sourceDoc);
+ return <LinkMenuItem key={destination[Id] + this.props.sourceDoc[Id]} groupType={this.props.groupType}
+ linkDoc={linkDoc} sourceDoc={this.props.sourceDoc} destinationDoc={destination} showEditor={this.props.showEditor} />;
+ });
+
+ return (
+ <div className="linkMenu-group">
+ <div className="linkMenu-group-name">
+ <p ref={this._drag} onPointerDown={this.onLinkButtonDown}
+ className={this.props.groupType === "*" || this.props.groupType === "" ? "" : "expand-one"} > {this.props.groupType}:</p>
+ {this.props.groupType === "*" || this.props.groupType === "" ? <></> : this.viewGroupAsTable(this.props.groupType)}
+ </div>
+ <div className="linkMenu-group-wrapper">
+ {groupItems}
+ </div>
+ </div >
+ );
+ }
+} \ No newline at end of file
diff --git a/src/client/views/nodes/LinkMenuItem.tsx b/src/client/views/nodes/LinkMenuItem.tsx
new file mode 100644
index 000000000..732e6de84
--- /dev/null
+++ b/src/client/views/nodes/LinkMenuItem.tsx
@@ -0,0 +1,111 @@
+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 } from '../../../new_fields/Doc';
+import { StrCast, Cast } from '../../../new_fields/Types';
+import { observable, action } from 'mobx';
+import { LinkManager } from '../../util/LinkManager';
+import { DragLinkAsDocument } from '../../util/DragManager';
+import { CollectionDockingView } from '../collections/CollectionDockingView';
+library.add(faEye, faEdit, faTimes, faArrowRight, faChevronDown, faChevronUp);
+
+
+interface LinkMenuItemProps {
+ groupType: string;
+ linkDoc: Doc;
+ sourceDoc: Doc;
+ destinationDoc: Doc;
+ showEditor: (linkDoc: Doc) => 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();
+ if (DocumentManager.Instance.getDocumentView(this.props.destinationDoc)) {
+ DocumentManager.Instance.jumpToDocument(this.props.destinationDoc, e.altKey);
+ } else {
+ CollectionDockingView.Instance.AddRightSplit(this.props.destinationDoc, undefined);
+ }
+ }
+
+ 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, new Doc);
+ 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/PDFBox.scss b/src/client/views/nodes/PDFBox.scss
index 5edff69f3..9a38d6241 100644
--- a/src/client/views/nodes/PDFBox.scss
+++ b/src/client/views/nodes/PDFBox.scss
@@ -32,11 +32,15 @@
height: 100px;
}
-.pdfBox-cont {
- pointer-events: none;
+.pdfBox-cont, .pdfBox-cont-interactive {
display: flex;
flex-direction: row;
-
+ height: 100%;
+ overflow-y: scroll;
+ overflow-x: hidden;
+}
+.pdfBox-cont {
+ pointer-events: none;
.textlayer {
pointer-events: none;
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index b6436d70f..8fb18e8fc 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -1,9 +1,9 @@
-import { action, IReactionDisposer, observable, reaction, trace, untracked } from 'mobx';
+import { action, IReactionDisposer, observable, reaction, trace, untracked, computed } from 'mobx';
import { observer } from "mobx-react";
import 'react-image-lightbox/style.css';
import { WidthSym, Doc } from "../../../new_fields/Doc";
import { makeInterface } from "../../../new_fields/Schema";
-import { Cast, NumCast } from "../../../new_fields/Types";
+import { Cast, NumCast, BoolCast } from "../../../new_fields/Types";
import { PdfField } from "../../../new_fields/URLField";
//@ts-ignore
// import { Document, Page } from "react-pdf";
@@ -33,6 +33,8 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
@observable private _alt = false;
@observable private _scrollY: number = 0;
+ @computed get dataDoc() { return BoolCast(this.props.Document.isTemplate) && this.props.DataDoc ? this.props.DataDoc : this.props.Document; }
+
@observable private _flyout: boolean = false;
private _mainCont: React.RefObject<HTMLDivElement>;
private _reactionDisposer?: IReactionDisposer;
@@ -70,20 +72,20 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
}
public GetPage() {
- return Math.floor(NumCast(this.props.Document.scrollY) / NumCast(this.Document.pdfHeight)) + 1;
+ return Math.floor(NumCast(this.props.Document.scrollY) / NumCast(this.dataDoc.pdfHeight)) + 1;
}
public BackPage() {
- let cp = Math.ceil(NumCast(this.props.Document.scrollY) / NumCast(this.Document.pdfHeight)) + 1;
+ let cp = Math.ceil(NumCast(this.props.Document.scrollY) / NumCast(this.dataDoc.pdfHeight)) + 1;
cp = cp - 1;
if (cp > 0) {
this.props.Document.curPage = cp;
- this.props.Document.scrollY = (cp - 1) * NumCast(this.Document.pdfHeight);
+ this.props.Document.scrollY = (cp - 1) * NumCast(this.dataDoc.pdfHeight);
}
}
public GotoPage(p: number) {
if (p > 0 && p <= NumCast(this.props.Document.numPages)) {
this.props.Document.curPage = p;
- this.props.Document.scrollY = (p - 1) * NumCast(this.Document.pdfHeight);
+ this.props.Document.scrollY = (p - 1) * NumCast(this.dataDoc.pdfHeight);
}
}
@@ -91,7 +93,7 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
let cp = this.GetPage() + 1;
if (cp <= NumCast(this.props.Document.numPages)) {
this.props.Document.curPage = cp;
- this.props.Document.scrollY = (cp - 1) * NumCast(this.Document.pdfHeight);
+ this.props.Document.scrollY = (cp - 1) * NumCast(this.dataDoc.pdfHeight);
}
}
@@ -198,7 +200,7 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
loaded = (nw: number, nh: number, np: number) => {
if (this.props.Document) {
- let doc = this.props.Document.proto ? this.props.Document.proto : this.props.Document;
+ let doc = this.dataDoc;
doc.numPages = np;
if (doc.nativeWidth && doc.nativeHeight) return;
let oldaspect = NumCast(doc.nativeHeight) / NumCast(doc.nativeWidth, 1);
@@ -226,19 +228,19 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
render() {
// uses mozilla pdf as default
- const pdfUrl = Cast(this.props.Document.data, PdfField, new PdfField(window.origin + RouteStore.corsProxy + "/https://mozilla.github.io/pdf.js/web/compressed.tracemonkey-pldi-09.pdf"));
+ const pdfUrl = Cast(this.props.Document.data, PdfField);
+ if (!(pdfUrl instanceof PdfField)) return <div>{`pdf, ${this.props.Document.data}, not found`}</div>;
let classname = "pdfBox-cont" + (this.props.active() && !InkingControl.Instance.selectedTool && !this._alt ? "-interactive" : "");
return (
- <div onScroll={this.onScroll}
+ <div className={classname}
+ onScroll={this.onScroll}
style={{
- height: "100%",
- overflowY: "scroll", overflowX: "hidden",
marginTop: `${NumCast(this.props.ContainingCollectionView!.props.Document.panY)}px`
}}
ref={this._mainCont}
onWheel={(e: React.WheelEvent) => {
e.stopPropagation();
- }} className={classname}>
+ }}>
<PDFViewer url={pdfUrl.url.pathname} loaded={this.loaded} scrollY={this._scrollY} parent={this} />
{/* <div style={{ width: "100px", height: "300px" }}></div> */}
{this.settingsPanel()}
diff --git a/src/client/views/pdf/PDFMenu.tsx b/src/client/views/pdf/PDFMenu.tsx
index e58ad9ccc..aeed5213c 100644
--- a/src/client/views/pdf/PDFMenu.tsx
+++ b/src/client/views/pdf/PDFMenu.tsx
@@ -38,7 +38,7 @@ export default class PDFMenu extends React.Component {
private _snippetButton: React.RefObject<HTMLButtonElement>;
@observable private _keyValue: string = "";
@observable private _valueValue: string = "";
- @observable private _added: boolean = false;;
+ @observable private _added: boolean = false;
constructor(props: Readonly<{}>) {
super(props);
diff --git a/src/client/views/pdf/PDFViewer.scss b/src/client/views/pdf/PDFViewer.scss
index 5a89a85f4..11d3d7e27 100644
--- a/src/client/views/pdf/PDFViewer.scss
+++ b/src/client/views/pdf/PDFViewer.scss
@@ -1,3 +1,9 @@
+
+.textLayer {
+ div {
+ user-select: text;
+ }
+}
.viewer-button-cont {
position: absolute;
display: flex;
@@ -14,6 +20,9 @@
border-radius: 5px;
}
+.textLayer {
+ user-select: auto;
+}
.viewer {
// position: absolute;
// top: 0;
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index d1d239f41..bafc3cbae 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -145,7 +145,7 @@ class Viewer extends React.Component<IViewerProps> {
}
});
}
- )
+ );
if (this.props.parent.props.ContainingCollectionView) {
this._filterReactionDisposer = reaction(
@@ -163,7 +163,7 @@ class Viewer extends React.Component<IViewerProps> {
d.opacity = run.result ? 1 : 0;
}
}
- })
+ });
}
});
}
@@ -320,10 +320,14 @@ class Viewer extends React.Component<IViewerProps> {
if (this._isPage[page] !== "image") {
this._isPage[page] = "image";
const address = this.props.url;
- let res = JSON.parse(await rp.get(DocServer.prepend(`/thumbnail${address.substring("files/".length, address.length - ".pdf".length)}-${page + 1}.PNG`)));
- runInAction(() => this._visibleElements[page] =
- <img key={res.path} src={res.path} onError={handleError}
- style={{ width: `${parseInt(res.width) * scale}px`, height: `${parseInt(res.height) * scale}px` }} />);
+ try {
+ let res = JSON.parse(await rp.get(DocServer.prepend(`/thumbnail${address.substring("files/".length, address.length - ".pdf".length)}-${page + 1}.PNG`)));
+ runInAction(() => this._visibleElements[page] =
+ <img key={res.path} src={res.path} onError={handleError}
+ style={{ width: `${parseInt(res.width) * scale}px`, height: `${parseInt(res.height) * scale}px` }} />);
+ } catch (e) {
+
+ }
}
}
@@ -716,7 +720,7 @@ class RegionAnnotation extends React.Component<IAnnotationProps> {
this.props.parent.scrollTo(this.props.y - 50);
}
}
- )
+ );
}
componentWillUnmount() {
@@ -745,7 +749,7 @@ class RegionAnnotation extends React.Component<IAnnotationProps> {
if (e.button === 0) {
let targetDoc = Cast(this.props.document.target, Doc, null);
if (targetDoc) {
- DocumentManager.Instance.jumpToDocument(targetDoc);
+ DocumentManager.Instance.jumpToDocument(targetDoc, true);
}
}
if (e.button === 2) {
diff --git a/src/client/views/pdf/Page.tsx b/src/client/views/pdf/Page.tsx
index 2b69bc47c..01aa96705 100644
--- a/src/client/views/pdf/Page.tsx
+++ b/src/client/views/pdf/Page.tsx
@@ -187,7 +187,7 @@ export default class Page extends React.Component<IPageProps> {
view.nativeHeight = marquee.height;
view.startY = marquee.top + this.props.getScrollFromPage(this.props.page);
view.width = doc[WidthSym]();
- let dragData = new DragManager.DocumentDragData([view]);
+ let dragData = new DragManager.DocumentDragData([view], [undefined]);
DragManager.StartDocumentDrag([], dragData, 0, 0);
}
diff --git a/src/client/views/presentationview/PresentationElement.tsx b/src/client/views/presentationview/PresentationElement.tsx
index 07cdcd43a..4afc0210f 100644
--- a/src/client/views/presentationview/PresentationElement.tsx
+++ b/src/client/views/presentationview/PresentationElement.tsx
@@ -8,19 +8,17 @@ import "./PresentationView.scss";
import { Utils } from "../../../Utils";
import { library } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { faFile as fileSolid, faLocationArrow, faArrowUp, faSearch } from '@fortawesome/free-solid-svg-icons';
+import { faFile as fileSolid, faFileDownload, faLocationArrow, faArrowUp, faSearch } from '@fortawesome/free-solid-svg-icons';
import { faFile as fileRegular } from '@fortawesome/free-regular-svg-icons';
import { List } from "../../../new_fields/List";
import { listSpec } from "../../../new_fields/Schema";
-
-
library.add(faArrowUp);
library.add(fileSolid);
-library.add(fileRegular);
library.add(faLocationArrow);
library.add(faSearch);
+library.add(fileRegular);
interface PresentationElementProps {
mainDocument: Doc;
@@ -391,8 +389,8 @@ export default class PresentationElement extends React.Component<PresentationEle
<button title="Zoom" className={this.selectedButtons[buttonIndex.Show] ? "presentation-interaction-selected" : "presentation-interaction"} onClick={this.onZoomDocumentClick}><FontAwesomeIcon icon={"search"} /></button>
<button title="Navigate" className={this.selectedButtons[buttonIndex.Navigate] ? "presentation-interaction-selected" : "presentation-interaction"} onClick={this.onNavigateDocumentClick}><FontAwesomeIcon icon={"location-arrow"} /></button>
<button title="Hide Document Till Presented" className={this.selectedButtons[buttonIndex.HideTillPressed] ? "presentation-interaction-selected" : "presentation-interaction"} onClick={this.onHideDocumentUntilPressClick}><FontAwesomeIcon icon={fileSolid} /></button>
- <button title="Fade Document After Presented" className={this.selectedButtons[buttonIndex.FadeAfter] ? "presentation-interaction-selected" : "presentation-interaction"} onClick={this.onFadeDocumentAfterPresentedClick}><FontAwesomeIcon icon={fileRegular} color={"gray"} /></button>
- <button title="Hide Document After Presented" className={this.selectedButtons[buttonIndex.HideAfter] ? "presentation-interaction-selected" : "presentation-interaction"} onClick={this.onHideDocumentAfterPresentedClick}><FontAwesomeIcon icon={fileRegular} /></button>
+ <button title="Fade Document After Presented" className={this.selectedButtons[buttonIndex.FadeAfter] ? "presentation-interaction-selected" : "presentation-interaction"} onClick={this.onFadeDocumentAfterPresentedClick}><FontAwesomeIcon icon={faFileDownload} color={"gray"} /></button>
+ <button title="Hide Document After Presented" className={this.selectedButtons[buttonIndex.HideAfter] ? "presentation-interaction-selected" : "presentation-interaction"} onClick={this.onHideDocumentAfterPresentedClick}><FontAwesomeIcon icon={faFileDownload} /></button>
<button title="Group With Up" className={this.selectedButtons[buttonIndex.Group] ? "presentation-interaction-selected" : "presentation-interaction"} onClick={(e) => {
e.stopPropagation();
this.changeGroupStatus();
diff --git a/src/client/views/search/Pager.tsx b/src/client/views/search/Pager.tsx
index 258f112b9..1c62773b1 100644
--- a/src/client/views/search/Pager.tsx
+++ b/src/client/views/search/Pager.tsx
@@ -3,7 +3,7 @@ import { observer } from 'mobx-react';
import { faArrowCircleRight, faArrowCircleLeft } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { library } from '@fortawesome/fontawesome-svg-core';
-import "./Pager.scss"
+import "./Pager.scss";
import { SearchBox } from './SearchBox';
import { observable, action } from 'mobx';
import { FilterBox } from './FilterBox';
@@ -20,7 +20,7 @@ export class Pager extends React.Component {
@action
onLeftClick(e: React.PointerEvent) {
FilterBox.Instance._pointerTime = e.timeStamp;
- if(SearchBox.Instance._pageNum > 0){
+ if (SearchBox.Instance._pageNum > 0) {
SearchBox.Instance._pageNum -= 1;
}
}
@@ -28,7 +28,7 @@ export class Pager extends React.Component {
@action
onRightClick(e: React.PointerEvent) {
FilterBox.Instance._pointerTime = e.timeStamp;
- if(SearchBox.Instance._pageNum+1 < SearchBox.Instance._maxNum){
+ if (SearchBox.Instance._pageNum + 1 < SearchBox.Instance._maxNum) {
SearchBox.Instance._pageNum += 1;
}
}
@@ -57,22 +57,22 @@ export class Pager extends React.Component {
return (
<div className="search-pager">
<div className="search-arrows">
- <div className = "arrow"
- onPointerDown = {this.onLeftClick} style = {SearchBox.Instance._pageNum === 0 ? {opacity: .2} : this._leftHover ? {opacity: 1} : {opacity: .7}}
- onMouseEnter = {this.mouseInLeft} onMouseOut = {this.mouseOutLeft}>
+ <div className="arrow"
+ onPointerDown={this.onLeftClick} style={SearchBox.Instance._pageNum === 0 ? { opacity: .2 } : this._leftHover ? { opacity: 1 } : { opacity: .7 }}
+ onMouseEnter={this.mouseInLeft} onMouseOut={this.mouseOutLeft}>
<FontAwesomeIcon className="fontawesome-icon" icon={faArrowCircleLeft} />
</div>
<div className="pager-title">
page {SearchBox.Instance._pageNum + 1} of {SearchBox.Instance._maxNum}
</div>
- <div className = "arrow"
- onPointerDown = {this.onRightClick} style = {SearchBox.Instance._pageNum === SearchBox.Instance._maxNum-1 ? {opacity: .2} : this._rightHover ? {opacity: 1} : {opacity: .7}}
- onMouseEnter = {this.mouseInRight} onMouseOut = {this.mouseOutRight}>
+ <div className="arrow"
+ onPointerDown={this.onRightClick} style={SearchBox.Instance._pageNum === SearchBox.Instance._maxNum - 1 ? { opacity: .2 } : this._rightHover ? { opacity: 1 } : { opacity: .7 }}
+ onMouseEnter={this.mouseInRight} onMouseOut={this.mouseOutRight}>
<FontAwesomeIcon className="fontawesome-icon" icon={faArrowCircleRight} />
</div>
</div>
</div>
- )
+ );
}
} \ No newline at end of file
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index 154e0b7b6..f53a4e34f 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -169,7 +169,7 @@ export class SearchBox extends React.Component {
@action.bound
closeSearch = () => {
- console.log("closing search")
+ console.log("closing search");
FilterBox.Instance.closeFilter();
this.closeResults();
}
diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx
index 50bf1f6b8..5082f9623 100644
--- a/src/client/views/search/SearchItem.tsx
+++ b/src/client/views/search/SearchItem.tsx
@@ -66,7 +66,7 @@ export class SelectorContextMenu extends React.Component<SearchItemProps> {
col.panX = newPanX;
col.panY = newPanY;
}
- CollectionDockingView.Instance.AddRightSplit(col);
+ CollectionDockingView.Instance.AddRightSplit(col, undefined);
};
}
@@ -87,7 +87,7 @@ export class SearchItem extends React.Component<SearchItemProps> {
@observable _selected: boolean = false;
onClick = () => {
- CollectionDockingView.Instance.AddRightSplit(this.props.doc);
+ CollectionDockingView.Instance.AddRightSplit(this.props.doc, undefined);
}
@computed
diff --git a/src/client/views/search/ToggleBar.tsx b/src/client/views/search/ToggleBar.tsx
index 8a3aacf96..178578c5c 100644
--- a/src/client/views/search/ToggleBar.tsx
+++ b/src/client/views/search/ToggleBar.tsx
@@ -78,7 +78,7 @@ export class ToggleBar extends React.Component<ToggleBarProps>{
<div className="toggle-option" style={{ opacity: (this.props.getStatus() ? .4 : 1) }}>{this.props.optionTwo}</div>
</div>
<div className="toggle-bar" id="toggle-bar" onClick={this.onclick} style={{ flexDirection: (this._originalStatus ? "row" : "row-reverse") }}>
- <div className="toggle-button" id="toggle-button" ref={this._toggleButton} />
+ <div className="toggle-button" id="toggle-button" ref={this._toggleButton} />
</div>
</div>
);
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index cce4fff5d..bf98c2e6c 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -8,6 +8,8 @@ import { listSpec } from "./Schema";
import { ObjectField } from "./ObjectField";
import { RefField, FieldId } from "./RefField";
import { ToScriptString, SelfProxy, Parent, OnUpdate, Self, HandleUpdate, Update, Id } from "./FieldSymbols";
+import { LinkManager } from "../client/util/LinkManager";
+import { DocUtils } from "../client/documents/Documents";
export namespace Field {
export function toScriptString(field: Field): string {
@@ -175,6 +177,14 @@ export namespace Doc {
export function IsPrototype(doc: Doc) {
return GetT(doc, "isPrototype", "boolean", true);
}
+ export async function SetInPlace(doc: Doc, key: string, value: Field | undefined, defaultProto: boolean) {
+ let hasProto = doc.proto instanceof Doc;
+ let onDeleg = Object.getOwnPropertyNames(doc).indexOf(key) !== -1;
+ let onProto = hasProto && Object.getOwnPropertyNames(doc.proto).indexOf(key) !== -1;
+ if (onDeleg || !hasProto || (!onProto && !defaultProto)) {
+ doc[key] = value;
+ } else doc.proto![key] = value;
+ }
export async function SetOnPrototype(doc: Doc, key: string, value: Field) {
const proto = Object.getOwnPropertyNames(doc).indexOf("isPrototype") === -1 ? doc.proto : doc;
@@ -241,6 +251,30 @@ export namespace Doc {
return true;
}
+ //
+ // Resolves a reference to a field by returning 'doc' if o field extension is specified,
+ // otherwise, it returns the extension document stored in doc.<fieldKey>_ext.
+ // This mechanism allows any fields to be extended with an extension document that can
+ // be used to capture field-specific metadata. For example, an image field can be extended
+ // to store annotations, ink, and other data.
+ //
+ export function resolvedFieldDataDoc(doc: Doc, fieldKey: string, fieldExt: string) {
+ return fieldExt && doc[fieldKey + "_ext"] instanceof Doc ? doc[fieldKey + "_ext"] as Doc : doc;
+ }
+
+ export function UpdateDocumentExtensionForField(doc: Doc, fieldKey: string) {
+ if (doc[fieldKey + "_ext"] === undefined) {
+ setTimeout(() => {
+ let docExtensionForField = new Doc(doc[Id] + fieldKey, true);
+ docExtensionForField.title = "Extension of " + doc.title + "'s field:" + fieldKey;
+ let proto: Doc | undefined = doc;
+ while (proto && !Doc.IsPrototype(proto)) {
+ proto = proto.proto;
+ }
+ (proto ? proto : doc)[fieldKey + "_ext"] = docExtensionForField;
+ }, 0);
+ }
+ }
export function MakeAlias(doc: Doc) {
if (!GetT(doc, "isPrototype", "boolean", true)) {
return Doc.MakeCopy(doc);
@@ -266,6 +300,7 @@ export namespace Doc {
}
}
});
+
return copy;
}
diff --git a/src/scraping/buxton/scraper.py b/src/scraping/buxton/scraper.py
new file mode 100644
index 000000000..97af10519
--- /dev/null
+++ b/src/scraping/buxton/scraper.py
@@ -0,0 +1,331 @@
+import os
+import docx2txt
+from docx import Document
+from docx.opc.constants import RELATIONSHIP_TYPE as RT
+import re
+from pymongo import MongoClient
+import shutil
+import uuid
+import datetime
+from PIL import Image
+import math
+import sys
+
+source = "./source"
+dist = "../../server/public/files"
+
+db = MongoClient("localhost", 27017)["Dash"]
+schema_guids = []
+
+
+def extract_links(fileName):
+ links = []
+ doc = Document(fileName)
+ rels = doc.part.rels
+ for rel in rels:
+ item = rels[rel]
+ if item.reltype == RT.HYPERLINK and ".aspx" not in item._target:
+ links.append(item._target)
+ return listify(links)
+
+
+def extract_value(kv_string):
+ pieces = kv_string.split(":")
+ return (pieces[1] if len(pieces) > 1 else kv_string).strip()
+
+
+def mkdir_if_absent(path):
+ try:
+ if not os.path.exists(path):
+ os.mkdir(path)
+ except OSError:
+ print("failed to create the appropriate directory structures for %s" % file_name)
+
+
+def guid():
+ return str(uuid.uuid4())
+
+
+def listify(list):
+ return {
+ "fields": list,
+ "__type": "list"
+ }
+
+
+def protofy(fieldId):
+ return {
+ "fieldId": fieldId,
+ "__type": "proxy"
+ }
+
+
+def write_schema(parse_results, display_fields):
+ view_guids = parse_results["child_guids"]
+
+ data_doc = parse_results["schema"]
+ fields = data_doc["fields"]
+
+ view_doc_guid = guid()
+
+ view_doc = {
+ "_id": view_doc_guid,
+ "fields": {
+ "proto": protofy(data_doc["_id"]),
+ "x": 10,
+ "y": 10,
+ "width": 900,
+ "height": 600,
+ "panX": 0,
+ "panY": 0,
+ "zoomBasis": 0.5,
+ "zIndex": 2,
+ "libraryBrush": False,
+ "viewType": 2
+ },
+ "__type": "Doc"
+ }
+
+ fields["proto"] = protofy("collectionProto")
+ fields["data"] = listify(proxify_guids(view_guids))
+ fields["schemaColumns"] = listify(display_fields)
+ fields["backgroundColor"] = "white"
+ fields["scale"] = 0.5
+ fields["viewType"] = 2
+ fields["author"] = "Bill Buxton"
+ fields["creationDate"] = {
+ "date": datetime.datetime.utcnow().microsecond,
+ "__type": "date"
+ }
+ fields["isPrototype"] = True
+ fields["page"] = -1
+
+ db.newDocuments.insert_one(data_doc)
+ db.newDocuments.insert_one(view_doc)
+
+ data_doc_guid = data_doc["_id"]
+ print(f"inserted view document ({view_doc_guid})")
+ print(f"inserted data document ({data_doc_guid})\n")
+
+ return view_doc_guid
+
+
+def write_image(folder, name):
+ path = f"http://localhost:1050/files/{folder}/{name}"
+
+ data_doc_guid = guid()
+ view_doc_guid = guid()
+
+ view_doc = {
+ "_id": view_doc_guid,
+ "fields": {
+ "proto": protofy(data_doc_guid),
+ "x": 10,
+ "y": 10,
+ "width": 300,
+ "zIndex": 2,
+ "libraryBrush": False
+ },
+ "__type": "Doc"
+ }
+
+ image = Image.open(f"{dist}/{folder}/{name}")
+ native_width, native_height = image.size
+
+ data_doc = {
+ "_id": data_doc_guid,
+ "fields": {
+ "proto": protofy("imageProto"),
+ "data": {
+ "url": path,
+ "__type": "image"
+ },
+ "title": name,
+ "nativeWidth": native_width,
+ "author": "Bill Buxton",
+ "creationDate": {
+ "date": datetime.datetime.utcnow().microsecond,
+ "__type": "date"
+ },
+ "isPrototype": True,
+ "page": -1,
+ "nativeHeight": native_height,
+ "height": native_height
+ },
+ "__type": "Doc"
+ }
+
+ db.newDocuments.insert_one(view_doc)
+ db.newDocuments.insert_one(data_doc)
+
+ return view_doc_guid
+
+
+def parse_document(file_name: str):
+ print(f"parsing {file_name}...")
+ pure_name = file_name.split(".")[0]
+
+ result = {}
+
+ dir_path = dist + "/" + pure_name
+ mkdir_if_absent(dir_path)
+
+ raw = str(docx2txt.process(source + "/" + file_name, dir_path))
+
+ view_guids = []
+ count = 0
+ for image in os.listdir(dir_path):
+ count += 1
+ view_guids.append(write_image(pure_name, image))
+ os.rename(dir_path + "/" + image, dir_path +
+ "/" + image.replace(".", "_m.", 1))
+ print(f"extracted {count} images...")
+
+ def sanitize(line): return re.sub("[\n\t]+", "", line).replace(u"\u00A0", " ").replace(
+ u"\u2013", "-").replace(u"\u201c", '''"''').replace(u"\u201d", '''"''').strip()
+
+ def sanitize_price(raw: str):
+ raw = raw.replace(",", "")
+ start = raw.find("$")
+ if start > -1:
+ i = start + 1
+ while (i < len(raw) and re.match(r"[0-9\.]", raw[i])):
+ i += 1
+ price = raw[start + 1: i + 1]
+ return float(price)
+ elif (raw.lower().find("nfs")):
+ return -1
+ else:
+ return math.nan
+
+ def remove_empty(line): return len(line) > 1
+
+ lines = list(map(sanitize, raw.split("\n")))
+ lines = list(filter(remove_empty, lines))
+
+ result["file_name"] = file_name
+ result["title"] = lines[2].strip()
+ result["short_description"] = lines[3].strip().replace(
+ "Short Description: ", "")
+
+ cur = 5
+ notes = ""
+ while lines[cur] != "Device Details":
+ notes += lines[cur] + " "
+ cur += 1
+ result["buxton_notes"] = notes.strip()
+
+ cur += 1
+ clean = list(
+ map(lambda data: data.strip().split(":"), lines[cur].split("|")))
+ result["company"] = clean[0][len(clean[0]) - 1].strip()
+ result["year"] = clean[1][len(clean[1]) - 1].strip()
+ result["original_price"] = sanitize_price(
+ clean[2][len(clean[2]) - 1].strip())
+
+ cur += 1
+ result["degrees_of_freedom"] = extract_value(
+ lines[cur]).replace("NA", "N/A")
+ cur += 1
+
+ dimensions = lines[cur].lower()
+ if dimensions.startswith("dimensions"):
+ dim_concat = dimensions[11:].strip()
+ cur += 1
+ while lines[cur] != "Key Words":
+ dim_concat += (" " + lines[cur].strip())
+ cur += 1
+ result["dimensions"] = dim_concat
+ else:
+ result["dimensions"] = "N/A"
+
+ cur += 1
+ result["primary_key"] = extract_value(lines[cur])
+ cur += 1
+ result["secondary_key"] = extract_value(lines[cur])
+
+ while lines[cur] != "Links":
+ result["secondary_key"] += (" " + extract_value(lines[cur]).strip())
+ cur += 1
+
+ cur += 1
+ link_descriptions = []
+ while lines[cur] != "Image":
+ link_descriptions.append(lines[cur].strip())
+ cur += 1
+ result["link_descriptions"] = listify(link_descriptions)
+
+ result["hyperlinks"] = extract_links(source + "/" + file_name)
+
+ images = []
+ captions = []
+ cur += 3
+ while cur + 1 < len(lines) and lines[cur] != "NOTES:":
+ images.append(lines[cur])
+ captions.append(lines[cur + 1])
+ cur += 2
+ result["images"] = listify(images)
+ result["captions"] = listify(captions)
+
+ notes = []
+ if (cur < len(lines) and lines[cur] == "NOTES:"):
+ cur += 1
+ while cur < len(lines):
+ notes.append(lines[cur])
+ cur += 1
+ if len(notes) > 0:
+ result["notes"] = listify(notes)
+
+ print("writing child schema...")
+
+ return {
+ "schema": {
+ "_id": guid(),
+ "fields": result,
+ "__type": "Doc"
+ },
+ "child_guids": view_guids
+ }
+
+
+def proxify_guids(guids):
+ return list(map(lambda guid: {"fieldId": guid, "__type": "proxy"}, guids))
+
+
+if os.path.exists(dist):
+ shutil.rmtree(dist)
+while os.path.exists(dist):
+ pass
+os.mkdir(dist)
+mkdir_if_absent(source)
+
+candidates = 0
+for file_name in os.listdir(source):
+ if file_name.endswith('.docx'):
+ candidates += 1
+ schema_guids.append(write_schema(
+ parse_document(file_name), ["title", "data"]))
+
+print("writing parent schema...")
+parent_guid = write_schema({
+ "schema": {
+ "_id": guid(),
+ "fields": {},
+ "__type": "Doc"
+ },
+ "child_guids": schema_guids
+}, ["title", "short_description", "original_price"])
+
+print("appending parent schema to main workspace...\n")
+db.newDocuments.update_one(
+ {"fields.title": "WS collection 1"},
+ {"$push": {"fields.data.fields": {"fieldId": parent_guid, "__type": "proxy"}}}
+)
+
+print("rewriting .gitignore...\n")
+lines = ['*', '!.gitignore']
+with open(dist + "/.gitignore", 'w') as f:
+ f.write('\n'.join(lines))
+
+suffix = "" if candidates == 1 else "s"
+print(f"conversion complete. {candidates} candidate{suffix} processed.")
diff --git a/src/scraping/buxton/source/Bill_Notes_Bill_Notes_CyKey.docx b/src/scraping/buxton/source/Bill_Notes_Bill_Notes_CyKey.docx
new file mode 100644
index 000000000..06094b4d3
--- /dev/null
+++ b/src/scraping/buxton/source/Bill_Notes_Bill_Notes_CyKey.docx
Binary files differ
diff --git a/src/scraping/buxton/source/Bill_Notes_Braun_T3.docx b/src/scraping/buxton/source/Bill_Notes_Braun_T3.docx
new file mode 100644
index 000000000..356697092
--- /dev/null
+++ b/src/scraping/buxton/source/Bill_Notes_Braun_T3.docx
Binary files differ
diff --git a/src/scraping/buxton/source/Bill_Notes_CasioC801.docx b/src/scraping/buxton/source/Bill_Notes_CasioC801.docx
new file mode 100644
index 000000000..cd89fb97b
--- /dev/null
+++ b/src/scraping/buxton/source/Bill_Notes_CasioC801.docx
Binary files differ
diff --git a/src/scraping/buxton/source/Bill_Notes_Casio_Mini.docx b/src/scraping/buxton/source/Bill_Notes_Casio_Mini.docx
new file mode 100644
index 000000000..a503cddfc
--- /dev/null
+++ b/src/scraping/buxton/source/Bill_Notes_Casio_Mini.docx
Binary files differ
diff --git a/src/scraping/buxton/source/Bill_Notes_FingerWorks_Prototype.docx b/src/scraping/buxton/source/Bill_Notes_FingerWorks_Prototype.docx
new file mode 100644
index 000000000..4d13a8cf5
--- /dev/null
+++ b/src/scraping/buxton/source/Bill_Notes_FingerWorks_Prototype.docx
Binary files differ
diff --git a/src/scraping/buxton/source/Bill_Notes_Fingerworks_TouchStream.docx b/src/scraping/buxton/source/Bill_Notes_Fingerworks_TouchStream.docx
new file mode 100644
index 000000000..578a1be08
--- /dev/null
+++ b/src/scraping/buxton/source/Bill_Notes_Fingerworks_TouchStream.docx
Binary files differ
diff --git a/src/scraping/buxton/source/Bill_Notes_FrogPad.docx b/src/scraping/buxton/source/Bill_Notes_FrogPad.docx
new file mode 100644
index 000000000..d01e1bf5c
--- /dev/null
+++ b/src/scraping/buxton/source/Bill_Notes_FrogPad.docx
Binary files differ
diff --git a/src/scraping/buxton/source/Bill_Notes_Gavilan_SC.docx b/src/scraping/buxton/source/Bill_Notes_Gavilan_SC.docx
new file mode 100644
index 000000000..7bd28b376
--- /dev/null
+++ b/src/scraping/buxton/source/Bill_Notes_Gavilan_SC.docx
Binary files differ
diff --git a/src/scraping/buxton/source/Bill_Notes_Grandjean_Stenotype.docx b/src/scraping/buxton/source/Bill_Notes_Grandjean_Stenotype.docx
new file mode 100644
index 000000000..0615c4953
--- /dev/null
+++ b/src/scraping/buxton/source/Bill_Notes_Grandjean_Stenotype.docx
Binary files differ
diff --git a/src/scraping/buxton/source/Bill_Notes_Matias.docx b/src/scraping/buxton/source/Bill_Notes_Matias.docx
new file mode 100644
index 000000000..547603256
--- /dev/null
+++ b/src/scraping/buxton/source/Bill_Notes_Matias.docx
Binary files differ
diff --git a/src/scraping/buxton/source/Bill_Notes_MousePen.docx b/src/scraping/buxton/source/Bill_Notes_MousePen.docx
new file mode 100644
index 000000000..4e1056636
--- /dev/null
+++ b/src/scraping/buxton/source/Bill_Notes_MousePen.docx
Binary files differ
diff --git a/src/scraping/buxton/source/Bill_Notes_NewO.docx b/src/scraping/buxton/source/Bill_Notes_NewO.docx
new file mode 100644
index 000000000..a514926d2
--- /dev/null
+++ b/src/scraping/buxton/source/Bill_Notes_NewO.docx
Binary files differ
diff --git a/src/scraping/buxton/source/Bill_Notes_OLPC.docx b/src/scraping/buxton/source/Bill_Notes_OLPC.docx
new file mode 100644
index 000000000..bfca0a9bb
--- /dev/null
+++ b/src/scraping/buxton/source/Bill_Notes_OLPC.docx
Binary files differ
diff --git a/src/scraping/buxton/source/Bill_Notes_PARCkbd.docx b/src/scraping/buxton/source/Bill_Notes_PARCkbd.docx
new file mode 100644
index 000000000..c0cf6ba9a
--- /dev/null
+++ b/src/scraping/buxton/source/Bill_Notes_PARCkbd.docx
Binary files differ
diff --git a/src/scraping/buxton/source/Bill_Notes_Philco_Mystery_Control.docx b/src/scraping/buxton/source/Bill_Notes_Philco_Mystery_Control.docx
new file mode 100644
index 000000000..ad06903f3
--- /dev/null
+++ b/src/scraping/buxton/source/Bill_Notes_Philco_Mystery_Control.docx
Binary files differ
diff --git a/src/scraping/buxton/source/Bill_Notes_TASA_Kbd.docx b/src/scraping/buxton/source/Bill_Notes_TASA_Kbd.docx
new file mode 100644
index 000000000..e4c659de9
--- /dev/null
+++ b/src/scraping/buxton/source/Bill_Notes_TASA_Kbd.docx
Binary files differ
diff --git a/src/scraping/buxton/source/Bill_Notes_The_Tap.docx b/src/scraping/buxton/source/Bill_Notes_The_Tap.docx
new file mode 100644
index 000000000..8ceebc71e
--- /dev/null
+++ b/src/scraping/buxton/source/Bill_Notes_The_Tap.docx
Binary files differ