diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/.DS_Store | bin | 6148 -> 6148 bytes | |||
-rw-r--r-- | src/client/util/DocumentManager.ts | 51 | ||||
-rw-r--r-- | src/client/util/DragManager.ts | 1 | ||||
-rw-r--r-- | src/client/views/.DS_Store | bin | 0 -> 6148 bytes | |||
-rw-r--r-- | src/client/views/DocumentDecorations.scss | 11 | ||||
-rw-r--r-- | src/client/views/DocumentDecorations.tsx | 73 | ||||
-rw-r--r-- | src/client/views/collections/CollectionFreeFormView.tsx | 3 | ||||
-rw-r--r-- | src/client/views/nodes/DocumentView.tsx | 66 | ||||
-rw-r--r-- | src/client/views/nodes/LinkBox.scss | 39 | ||||
-rw-r--r-- | src/client/views/nodes/LinkBox.tsx | 83 | ||||
-rw-r--r-- | src/client/views/nodes/LinkEditor.scss | 16 | ||||
-rw-r--r-- | src/client/views/nodes/LinkEditor.tsx | 37 | ||||
-rw-r--r-- | src/client/views/nodes/LinkMenu.scss | 20 | ||||
-rw-r--r-- | src/client/views/nodes/LinkMenu.tsx | 58 | ||||
-rw-r--r-- | src/fields/Document.ts | 8 | ||||
-rw-r--r-- | src/fields/KeyStore.ts | 4 |
16 files changed, 464 insertions, 6 deletions
diff --git a/src/.DS_Store b/src/.DS_Store Binary files differindex 620e4ebce..f20f36d63 100644 --- a/src/.DS_Store +++ b/src/.DS_Store diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts new file mode 100644 index 000000000..5b99b4ef8 --- /dev/null +++ b/src/client/util/DocumentManager.ts @@ -0,0 +1,51 @@ +import React = require('react') +import { observer } from 'mobx-react'; +import { observable, action } from 'mobx'; +import { Document } from "../../fields/Document" +import { DocumentView } from '../views/nodes/DocumentView'; + + +export class DocumentManager { + + //global holds all of the nodes (regardless of which collection they're in) + @observable + public DocumentViews: DocumentView[] = []; + + // singleton instance + private static _instance: DocumentManager; + + // create one and only one instance of NodeManager + public static get Instance(): DocumentManager { + return this._instance || (this._instance = new this()); + } + + //private constructor so no other class can create a nodemanager + private constructor() { + // this.DocumentViews = new Array<DocumentView>(); + } + + public getDocumentView(toFind: Document): DocumentView | null { + + let toReturn: DocumentView | null; + toReturn = null; + + //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 (Object.is(doc, toFind)) { + // toReturn = view; + // return; + // } + // } + + if (Object.is(doc, toFind)) { + toReturn = view; + return; + } + + }) + + return (toReturn); + } +}
\ No newline at end of file diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 513a6ac9e..bb9a3b793 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -97,7 +97,6 @@ export namespace DragManager { } export function StartDrag(ele: HTMLElement, dragData: { [id: string]: any }, options?: DragOptions) { - DocumentDecorations.Instance.Hidden = true; if (!dragDiv) { dragDiv = document.createElement("div"); DragManager.Root().appendChild(dragDiv); diff --git a/src/client/views/.DS_Store b/src/client/views/.DS_Store Binary files differnew file mode 100644 index 000000000..6bd614c8b --- /dev/null +++ b/src/client/views/.DS_Store diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss index e8b93a18b..f88bf9c14 100644 --- a/src/client/views/DocumentDecorations.scss +++ b/src/client/views/DocumentDecorations.scss @@ -2,7 +2,7 @@ position: absolute; display: grid; z-index: 1000; - grid-template-rows: 20px 1fr 20px; + grid-template-rows: 20px 1fr 20px 0px; grid-template-columns: 20px 1fr 20px; pointer-events: none; #documentDecorations-centerCont { @@ -29,4 +29,13 @@ #documentDecorations-rightResizer { cursor: ew-resize; } + #linkButton { + height: 20px; + width: 20px; + margin-top: 10px; + border-radius: 50%; + opacity: 0.6; + pointer-events: auto; + background-color: #2B6091; + } }
\ No newline at end of file diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 9fd73a33b..8c3913232 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -1,16 +1,23 @@ -import { observable, computed } from "mobx"; +import { observable, computed, action } from "mobx"; import React = require("react"); import { SelectionManager } from "../util/SelectionManager"; import { observer } from "mobx-react"; import './DocumentDecorations.scss' import { KeyStore } from '../../fields/KeyStore' import { NumberField } from "../../fields/NumberField"; +import { props } from "bluebird"; +import { DragManager } from "../util/DragManager"; +import { LinkMenu } from "./nodes/LinkMenu"; +const higflyout = require("@hig/flyout"); +const { anchorPoints } = higflyout; +const Flyout = higflyout.default; @observer export class DocumentDecorations extends React.Component { static Instance: DocumentDecorations private _resizer = "" private _isPointerDown = false; + private _linkButton = React.createRef<HTMLDivElement>(); @observable private _hidden = false; constructor(props: Readonly<{}>) { @@ -52,6 +59,46 @@ export class DocumentDecorations extends React.Component { } } + onLinkButtonDown = (e: React.PointerEvent): void => { + // if () + // let linkMenu = new LinkMenu(SelectionManager.SelectedDocuments()[0]); + // linkMenu.Hidden = false; + console.log("down"); + + 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 => { + console.log("up"); + document.removeEventListener("pointermove", this.onLinkButtonMoved) + document.removeEventListener("pointerup", this.onLinkButtonUp) + e.stopPropagation(); + } + + + onLinkButtonMoved = (e: PointerEvent): void => { + console.log("moved"); + let dragData: { [id: string]: any } = {}; + dragData["linkSourceDoc"] = SelectionManager.SelectedDocuments()[0]; + if (this._linkButton.current != null) { + DragManager.StartDrag(this._linkButton.current, dragData, { + handlers: { + dragComplete: action(() => { }), + }, + hideSource: false + }) + } + document.removeEventListener("pointermove", this.onLinkButtonMoved) + document.removeEventListener("pointerup", this.onLinkButtonUp) + e.stopPropagation(); + } + + onPointerMove = (e: PointerEvent): void => { e.stopPropagation(); e.preventDefault(); @@ -138,6 +185,13 @@ export class DocumentDecorations extends React.Component { } } + changeFlyoutContent = (): void => { + + } + // buttonOnPointerUp = (e: React.PointerEvent): void => { + // e.stopPropagation(); + // } + render() { var bounds = this.Bounds; if (this.Hidden) { @@ -147,6 +201,18 @@ export class DocumentDecorations extends React.Component { console.log("DocumentDecorations: Bounds Error") return (null); } + + let linkButton = null; + if (SelectionManager.SelectedDocuments().length > 0) { + linkButton = (<Flyout + anchorPoint={anchorPoints.RIGHT_TOP} + content={ + <LinkMenu docView={SelectionManager.SelectedDocuments()[0]} changeFlyout={this.changeFlyoutContent}> + </LinkMenu> + }> + <div id="linkButton" onPointerDown={this.onLinkButtonDown} ref={this._linkButton}></div> + </Flyout>); + } return ( <div id="documentDecorations-container" style={{ width: (bounds.r - bounds.x + 40) + "px", @@ -163,7 +229,10 @@ export class DocumentDecorations extends React.Component { <div id="documentDecorations-bottomLeftResizer" className="documentDecorations-resizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div> <div id="documentDecorations-bottomResizer" className="documentDecorations-resizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div> <div id="documentDecorations-bottomRightResizer" className="documentDecorations-resizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div> - </div> + + {linkButton} + + </div > ) } }
\ No newline at end of file diff --git a/src/client/views/collections/CollectionFreeFormView.tsx b/src/client/views/collections/CollectionFreeFormView.tsx index 16002ad9f..e12d3d15c 100644 --- a/src/client/views/collections/CollectionFreeFormView.tsx +++ b/src/client/views/collections/CollectionFreeFormView.tsx @@ -57,6 +57,9 @@ export class CollectionFreeFormView extends CollectionViewBase { @undoBatch @action drop = (e: Event, de: DragManager.DropEvent) => { + if (!("documentView" in de.data)) { + return; + } super.drop(e, de); const docView: DocumentView = de.data["documentView"]; let doc: Document = docView ? docView.props.Document : de.data["document"]; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 263bb31d7..6d903b955 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -1,4 +1,4 @@ -import { action, computed } from "mobx"; +import { action, computed, runInAction } from "mobx"; import { observer } from "mobx-react"; import { Document } from "../../../fields/Document"; import { Field, FieldWaiting, Opt } from "../../../fields/Field"; @@ -24,6 +24,8 @@ import { WebBox } from "../nodes/WebBox"; import { PDFBox } from "../nodes/PDFBox"; import "./DocumentView.scss"; import React = require("react"); +import { TextField } from "../../../fields/TextField"; +import { DocumentManager } from "../../util/DocumentManager"; const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this? @@ -113,6 +115,40 @@ export class DocumentView extends React.Component<DocumentViewProps> { } } } + + private dropDisposer?: DragManager.DragDropDisposer; + protected createDropTarget = (ele: HTMLDivElement) => { + + } + + componentDidMount() { + if (this._mainCont.current) { + this.dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, { handlers: { drop: this.drop.bind(this) } }); + } + runInAction(() => { + DocumentManager.Instance.DocumentViews.push(this); + }) + } + + componentDidUpdate() { + if (this.dropDisposer) { + this.dropDisposer(); + } + if (this._mainCont.current) { + this.dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, { handlers: { drop: this.drop.bind(this) } }); + } + } + + componentWillUnmount() { + if (this.dropDisposer) { + this.dropDisposer(); + } + runInAction(() => { + DocumentManager.Instance.DocumentViews.splice(DocumentManager.Instance.DocumentViews.indexOf(this), 1); + + }) + } + onPointerMove = (e: PointerEvent): void => { if (e.cancelBubble) { return; @@ -172,6 +208,34 @@ export class DocumentView extends React.Component<DocumentViewProps> { } @action + drop = (e: Event, de: DragManager.DropEvent) => { + console.log("drop"); + const sourceDocView: DocumentView = de.data["linkSourceDoc"]; + if (!sourceDocView) { + return; + } + let sourceDoc: Document = sourceDocView.props.Document; + let destDoc: Document = this.props.Document; + if (this.props.isTopMost) { + return; + } + let linkDoc: Document = new Document(); + + linkDoc.Set(KeyStore.Title, new TextField("New Link")); + linkDoc.Set(KeyStore.LinkDescription, new TextField("")); + linkDoc.Set(KeyStore.LinkTags, new TextField("Default")); + + sourceDoc.GetOrCreateAsync(KeyStore.LinkedToDocs, ListField, field => { (field as ListField<Document>).Data.push(linkDoc) }); + linkDoc.Set(KeyStore.LinkedToDocs, destDoc); + destDoc.GetOrCreateAsync(KeyStore.LinkedFromDocs, ListField, field => { (field as ListField<Document>).Data.push(linkDoc) }); + linkDoc.Set(KeyStore.LinkedFromDocs, sourceDoc); + + + + e.stopPropagation(); + } + + @action onContextMenu = (e: React.MouseEvent): void => { e.stopPropagation(); let moved = Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3; diff --git a/src/client/views/nodes/LinkBox.scss b/src/client/views/nodes/LinkBox.scss new file mode 100644 index 000000000..00e5ebb3d --- /dev/null +++ b/src/client/views/nodes/LinkBox.scss @@ -0,0 +1,39 @@ +.link-container { + width: 100%; + height: 30px; + display: flex; + flex-direction: row; + border-top: 0.5px solid #bababa; +} + +.info-container { + width: 60%; + padding-top: 5px; + padding-left: 5px; + display: flex; + flex-direction: column +} + +.link-name { + font-size: 11px; +} + +.doc-name { + font-size: 8px; +} + +.button-container { + width: 40%; + display: flex; + flex-direction: row; +} + +.button { + height: 15px; + width: 15px; + margin: 8px 5px; + border-radius: 50%; + opacity: 0.6; + pointer-events: auto; + background-color: #2B6091; +}
\ No newline at end of file diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx new file mode 100644 index 000000000..fcfb2fcb5 --- /dev/null +++ b/src/client/views/nodes/LinkBox.tsx @@ -0,0 +1,83 @@ +import { observable, computed, action } from "mobx"; +import React = require("react"); +import { SelectionManager } from "../../util/SelectionManager"; +import { observer } from "mobx-react"; +import './LinkBox.scss' +import { KeyStore } from '../../../fields/KeyStore' +import { props } from "bluebird"; +import { DocumentView } from "./DocumentView"; +import { Document } from "../../../fields/Document"; +import { ListField } from "../../../fields/ListField"; +import { DocumentManager } from "../../util/DocumentManager"; +import { LinkEditor } from "./LinkEditor"; + +interface Props { + linkDoc: Document; + linkName: String; + pairedDoc: Document; + type: String; +} + +@observer +export class LinkBox extends React.Component<Props> { + + onViewButtonPressed = (e: React.PointerEvent): void => { + console.log("view down"); + e.stopPropagation(); + let docView = DocumentManager.Instance.getDocumentView(this.props.pairedDoc); + if (docView) { + docView.props.focus(this.props.pairedDoc); + } + } + + onEditButtonPressed = (e: React.PointerEvent): void => { + console.log("edit down"); + e.stopPropagation(); + } + + onDeleteButtonPressed = (e: React.PointerEvent): void => { + console.log("delete down"); + e.stopPropagation(); + this.props.linkDoc.GetTAsync(KeyStore.LinkedFromDocs, Document, field => { + if (field) { + field.GetTAsync<ListField<Document>>(KeyStore.LinkedToDocs, ListField, field => { + if (field) { + field.Data.splice(field.Data.indexOf(this.props.linkDoc)); + } + }) + } + }); + this.props.linkDoc.GetTAsync(KeyStore.LinkedToDocs, Document, field => { + if (field) { + field.GetTAsync<ListField<Document>>(KeyStore.LinkedFromDocs, ListField, field => { + if (field) { + field.Data.splice(field.Data.indexOf(this.props.linkDoc)); + } + }) + } + }); + } + + render() { + + return ( + <LinkEditor linkBox={this} /> + // <div className="link-container"> + // <div className="info-container"> + // <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 className="button" onPointerDown={this.onViewButtonPressed}></div> + // <div className="button" onPointerDown={this.onEditButtonPressed}></div> + // <div className="button" onPointerDown={this.onDeleteButtonPressed}></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 new file mode 100644 index 000000000..b5db19b65 --- /dev/null +++ b/src/client/views/nodes/LinkEditor.scss @@ -0,0 +1,16 @@ +.edit-container { + width: 100%; + height: auto; + display: flex; + flex-direction: column; +} + +.name-input { + margin-bottom: 10px; + font-size: 12px; +} + +.description-input { + height: 100px; + font-size: 10px; +}
\ No newline at end of file diff --git a/src/client/views/nodes/LinkEditor.tsx b/src/client/views/nodes/LinkEditor.tsx new file mode 100644 index 000000000..38cfef239 --- /dev/null +++ b/src/client/views/nodes/LinkEditor.tsx @@ -0,0 +1,37 @@ +import { observable, computed, action } from "mobx"; +import React = require("react"); +import { SelectionManager } from "../../util/SelectionManager"; +import { observer } from "mobx-react"; +import './LinkBox.scss' +import { KeyStore } from '../../../fields/KeyStore' +import { props } from "bluebird"; +import { DocumentView } from "./DocumentView"; +import { Document } from "../../../fields/Document"; +import { ListField } from "../../../fields/ListField"; +import { DocumentManager } from "../../util/DocumentManager"; +import { LinkBox } from "./LinkBox"; + +interface Props { + linkBox: LinkBox; +} + +@observer +export class LinkEditor extends React.Component<Props> { + + onSaveButtonPressed = (e: React.PointerEvent): void => { + console.log("view down"); + e.stopPropagation(); + + } + + render() { + + return ( + <div className="edit-container"> + <input className="name-input" type="text" placeholder="Name..."></input> + <input className="description-input" type="text" placeholder="Description"></input> + </div> + + ) + } +}
\ No newline at end of file diff --git a/src/client/views/nodes/LinkMenu.scss b/src/client/views/nodes/LinkMenu.scss new file mode 100644 index 000000000..af5b84ec6 --- /dev/null +++ b/src/client/views/nodes/LinkMenu.scss @@ -0,0 +1,20 @@ +#menu-container { + width: 100%; + height: auto; + display: flex; + flex-direction: column; +} + +#search-bar { + width: 100%; + padding: 5px; + margin-bottom: 10px; + font-size: 12px; +} + +#link-list { + margin-top: 5px; + width: 100%; + height: 100px; + overflow-y: scroll; +}
\ No newline at end of file diff --git a/src/client/views/nodes/LinkMenu.tsx b/src/client/views/nodes/LinkMenu.tsx new file mode 100644 index 000000000..577aba398 --- /dev/null +++ b/src/client/views/nodes/LinkMenu.tsx @@ -0,0 +1,58 @@ +import { observable, computed, action } from "mobx"; +import React = require("react"); +import { SelectionManager } from "../../util/SelectionManager"; +import { observer } from "mobx-react"; +import './LinkMenu.scss' +import { KeyStore } from '../../../fields/KeyStore' +import { props } from "bluebird"; +import { DocumentView } from "./DocumentView"; +import { LinkBox } from "./LinkBox" +import { Document } from "../../../fields/Document"; +import { ListField } from "../../../fields/ListField"; +import { TextField } from "../../../fields/TextField"; +import { FieldWaiting } from "../../../fields/Field"; + +interface Props { + docView: DocumentView; + changeFlyout: () => void +} + +@observer +export class LinkMenu extends React.Component<Props> { + + render() { + //get list of links from document + let linkFrom: Document[] = this.props.docView.props.Document.GetData(KeyStore.LinkedFromDocs, ListField, []); + let linkTo: Document[] = this.props.docView.props.Document.GetData(KeyStore.LinkedToDocs, ListField, []); + + return ( + <div id="menu-container"> + <input id="search-bar" type="text" placeholder="Search..."></input> + <div id="link-list"> + + {linkTo.map(link => { + let name = link.GetData(KeyStore.Title, TextField, new String); + let doc = link.GetT(KeyStore.LinkedToDocs, Document); + if (doc && doc != FieldWaiting) { + return <LinkBox linkDoc={link} linkName={name} pairedDoc={doc} type={"Destination: "} /> + } else { + return <div></div> + } + + })} + + {linkFrom.map(link => { + let name = link.GetData(KeyStore.Title, TextField, new String); + let doc = link.GetT(KeyStore.LinkedFromDocs, Document); + if (doc && doc != FieldWaiting) { + return <LinkBox linkDoc={link} linkName={name} pairedDoc={doc} type={"Source: "} /> + } else { + return <div></div> + } + })} + </div> + + </div> + ) + } +}
\ No newline at end of file diff --git a/src/fields/Document.ts b/src/fields/Document.ts index 2e873439c..25e239417 100644 --- a/src/fields/Document.ts +++ b/src/fields/Document.ts @@ -1,6 +1,6 @@ import { Key } from "./Key" import { KeyStore } from "./KeyStore"; -import { Field, Cast, FieldWaiting, FieldValue, FieldId } from "./Field" +import { Field, Cast, FieldWaiting, FieldValue, FieldId, Opt } from "./Field" import { NumberField } from "./NumberField"; import { ObservableMap, computed, action } from "mobx"; import { TextField } from "./TextField"; @@ -128,6 +128,12 @@ export class Document extends Field { return false; } + GetTAsync<T extends Field>(key: Key, ctor: { new(): T }, callback: (field: Opt<T>) => void): boolean { + return this.GetAsync(key, (field) => { + callback(Cast(field, ctor)); + }) + } + /** * Same as {@link Document#GetAsync}, except a field of the given type * will be created if there is no field associated with the given key, diff --git a/src/fields/KeyStore.ts b/src/fields/KeyStore.ts index f67257093..f93a68c85 100644 --- a/src/fields/KeyStore.ts +++ b/src/fields/KeyStore.ts @@ -27,6 +27,10 @@ export namespace KeyStore { export const Caption = new Key("Caption"); export const ActiveFrame = new Key("ActiveFrame"); export const DocumentText = new Key("DocumentText"); + export const LinkedToDocs = new Key("LinkedToDocs"); + export const LinkedFromDocs = new Key("LinkedFromDocs"); + export const LinkDescription = new Key("LinkDescription"); + export const LinkTags = new Key("LinkTag"); export const Thumbnail = new Key("Thumbnail"); export const CurPage = new Key("CurPage"); export const NumPages = new Key("NumPages"); |