From 972f76a34e3c1a1aa5f0be59639fbd5763c9c16f Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Thu, 6 Jun 2019 17:45:26 -0400 Subject: links get saved to global table --- src/client/views/nodes/LinkBox.tsx | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) (limited to 'src/client/views/nodes/LinkBox.tsx') diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx index 68b692aad..ebca1dc69 100644 --- a/src/client/views/nodes/LinkBox.tsx +++ b/src/client/views/nodes/LinkBox.tsx @@ -1,5 +1,5 @@ import { library } from '@fortawesome/fontawesome-svg-core'; -import { faEdit, faEye, faTimes } from '@fortawesome/free-solid-svg-icons'; +import { faEdit, faEye, faTimes, faArrowRight } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { observer } from "mobx-react"; import { DocumentManager } from "../../util/DocumentManager"; @@ -16,6 +16,7 @@ import { action } from 'mobx'; library.add(faEye); library.add(faEdit); library.add(faTimes); +library.add(faArrowRight); interface Props { linkDoc: Doc; @@ -29,7 +30,7 @@ interface Props { export class LinkBox extends React.Component { @undoBatch - onViewButtonPressed = async (e: React.PointerEvent): Promise => { + followLink = async (e: React.PointerEvent): Promise => { e.stopPropagation(); DocumentManager.Instance.jumpToDocument(this.props.pairedDoc, e.altKey); } @@ -62,8 +63,8 @@ export class LinkBox extends React.Component { return ( // -
-
+
+

{this.props.linkName}

@@ -72,13 +73,15 @@ export class LinkBox extends React.Component {
-
+
{/*
*/} +
+
-
+
-
+
); -- cgit v1.2.3-70-g09d2 From e1fd270f1806ffd51174c835b335ceb4ebb2fe56 Mon Sep 17 00:00:00 2001 From: Fawn Date: Wed, 12 Jun 2019 16:20:46 -0400 Subject: created remove handlers for link metadata and groups --- src/client/documents/Documents.ts | 14 +- src/client/util/LinkManager.ts | 110 ++++++++++----- src/client/views/nodes/LinkBox.tsx | 38 ++--- src/client/views/nodes/LinkEditor.scss | 32 ++++- src/client/views/nodes/LinkEditor.tsx | 250 +++++++++++++++------------------ src/client/views/nodes/LinkMenu.scss | 2 +- src/client/views/nodes/LinkMenu.tsx | 33 +---- 7 files changed, 251 insertions(+), 228 deletions(-) (limited to 'src/client/views/nodes/LinkBox.tsx') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 9f1501265..9517cbbda 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -89,9 +89,13 @@ export namespace DocUtils { // let protoSrc = source.proto ? source.proto : source; // let protoTarg = target.proto ? target.proto : target; UndoManager.RunInBatch(() => { - let groupDoc = Docs.TextDocument(); - groupDoc.proto!.type = "*"; - groupDoc.proto!.metadata = Docs.TextDocument(); + // let groupDoc1 = Docs.TextDocument(); + // groupDoc1.proto!.type = "*"; + // groupDoc1.proto!.metadata = Docs.TextDocument(); + + // let groupDoc2 = Docs.TextDocument(); + // groupDoc2.proto!.type = "*"; + // groupDoc2.proto!.metadata = Docs.TextDocument(); let linkDoc = Docs.TextDocument({ width: 100, height: 30, borderRounding: -1 }); //let linkDoc = new Doc; @@ -99,11 +103,11 @@ export namespace DocUtils { // linkDoc.proto!.linkDescription = ""; linkDoc.proto!.anchor1 = source; linkDoc.proto!.anchor1Page = source.curPage; - linkDoc.proto!.anchor1Groups = new List([groupDoc]); + linkDoc.proto!.anchor1Groups = new List([]); linkDoc.proto!.anchor2 = target; linkDoc.proto!.anchor2Page = target.curPage; - linkDoc.proto!.anchor2Groups = new List([groupDoc]); + linkDoc.proto!.anchor2Groups = new List([]); // linkDoc.proto!.linkedTo = target; // linkDoc.proto!.linkedToPage = target.curPage; diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts index 99fb4c6c0..cc8617052 100644 --- a/src/client/util/LinkManager.ts +++ b/src/client/util/LinkManager.ts @@ -9,6 +9,7 @@ import { StrCast, Cast } from "../../new_fields/Types"; import { Doc } from "../../new_fields/Doc"; import { listSpec } from "../../new_fields/Schema"; import { List } from "../../new_fields/List"; +import { string } from "prop-types"; export namespace LinkUtils { export function findOppositeAnchor(link: Doc, anchor: Doc): Doc { @@ -40,14 +41,48 @@ export namespace LinkUtils { // } export function setAnchorGroups(link: Doc, anchor: Doc, groups: Doc[]) { + // console.log("setting groups for anchor", anchor["title"]); if (Doc.AreProtosEqual(anchor, Cast(link.anchor1, Doc, new Doc))) { link.anchor1Groups = new List(groups); } else { link.anchor2Groups = new List(groups); } } + + export function removeGroupFromAnchor(link: Doc, anchor: Doc, groupType: string) { + let groups = []; + if (Doc.AreProtosEqual(anchor, Cast(link.anchor1, Doc, new Doc))) { + groups = Cast(link["anchor1Groups"], listSpec(Doc), []); + } else { + groups = Cast(link["anchor2Groups"], listSpec(Doc), []); + } + + let newGroups: Doc[] = []; + groups.forEach(groupDoc => { + if (groupDoc instanceof Doc && StrCast(groupDoc["type"]) !== groupType) { + newGroups.push(groupDoc); + } + }) + LinkUtils.setAnchorGroups(link, anchor, newGroups); + } } +/* + * 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 { @@ -56,60 +91,57 @@ export class LinkManager { private constructor() { } - @observable public allLinks: Array = []; - @observable public allGroups: Map> = new Map(); + @observable public allLinks: Array = []; // list of link docs + @observable public allGroups: Map> = new Map(); // map of group type to list of its metadata keys public findAllRelatedLinks(anchor: Doc): Array { return LinkManager.Instance.allLinks.filter( link => Doc.AreProtosEqual(anchor, Cast(link.anchor1, Doc, new Doc)) || Doc.AreProtosEqual(anchor, Cast(link.anchor2, Doc, new Doc))); } + // returns map of group type to anchor's links in that group type public findRelatedGroupedLinks(anchor: Doc): Map> { let related = this.findAllRelatedLinks(anchor); - let anchorGroups = new Map(); + let anchorGroups = new Map>(); related.forEach(link => { - // get groups of given doc - let oppGroups = (Doc.AreProtosEqual(anchor, Cast(link.anchor1, Doc, new Doc))) ? Cast(link.anchor1Groups, listSpec(Doc), []) : Cast(link.anchor2Groups, listSpec(Doc), []); - if (oppGroups) { - if (oppGroups.length > 0) { - oppGroups.forEach(groupDoc => { - if (groupDoc instanceof Doc) { - let groupType = StrCast(groupDoc.proto!.type); - let group = anchorGroups.get(groupType); // TODO: clean this up lol - if (group) group.push(link); - else group = [link]; - anchorGroups.set(groupType, group); - } else { - // promise doc - } - - }) - } - else { - // if link is in no groups then put it in default group - let group = anchorGroups.get("*"); - if (group) group.push(link); - else group = [link]; - anchorGroups.set("*", group); - } - } + // get groups of given anchor categorizes this link/opposite anchor in + let groups = (Doc.AreProtosEqual(anchor, Cast(link.anchor1, Doc, new Doc))) ? Cast(link.anchor1Groups, listSpec(Doc), []) : Cast(link.anchor2Groups, listSpec(Doc), []); + if (groups.length > 0) { + groups.forEach(groupDoc => { + if (groupDoc instanceof Doc) { + let groupType = StrCast(groupDoc["type"]); + let group = anchorGroups.get(groupType); // TODO: clean this up lol + if (group) group.push(link); + else group = [link]; + anchorGroups.set(groupType, group); + } else { + // promise doc + } + + }) + } + else { + // if link is in no groups then put it in default group + let group = anchorGroups.get("*"); + if (group) group.push(link); + else group = [link]; + anchorGroups.set("*", group); + } - // let anchor = this.findOppositeAnchor(link, source); - // let group = categories.get(link.linkTags); - // if (group) group.push(link); - // else group = [link]; - // categories.set(link.linkTags, group); }) return anchorGroups; } - - - - // public findAnchorTags(link: Doc, source: Doc): Doc[] { - // if (source) - // } + public deleteGroup(groupType: string) { + let deleted = LinkManager.Instance.allGroups.delete(groupType); + if (deleted) { + LinkManager.Instance.allLinks.forEach(linkDoc => { + LinkUtils.removeGroupFromAnchor(linkDoc, Cast(linkDoc["anchor1"], Doc, new Doc), groupType); + LinkUtils.removeGroupFromAnchor(linkDoc, Cast(linkDoc["anchor2"], Doc, new Doc), groupType); + }) + } + } } \ No newline at end of file diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx index ebca1dc69..5597bb1aa 100644 --- a/src/client/views/nodes/LinkBox.tsx +++ b/src/client/views/nodes/LinkBox.tsx @@ -41,23 +41,23 @@ export class LinkBox extends React.Component { this.props.showEditor(); } - @action - onDeleteButtonPressed = async (e: React.PointerEvent): Promise => { - 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); - } - } - } + // @action + // onDeleteButtonPressed = async (e: React.PointerEvent): Promise => { + // 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() { @@ -80,8 +80,8 @@ export class LinkBox extends React.Component {
-
-
+ {/*
+
*/} ); diff --git a/src/client/views/nodes/LinkEditor.scss b/src/client/views/nodes/LinkEditor.scss index d3ac8cf19..b56f3046a 100644 --- a/src/client/views/nodes/LinkEditor.scss +++ b/src/client/views/nodes/LinkEditor.scss @@ -74,6 +74,7 @@ } .linkEditor-group-row { display: flex; + margin-bottom: 6px; .linkEditor-group-row-label { margin-right: 6px; } @@ -81,8 +82,17 @@ .linkEditor-metadata-row { display: flex; justify-content: space-between; + margin-bottom: 6px; input { - width: calc(50% - 2px); + width: calc(50% - 18px); + height: 20px; + } + button { + width: 20px; + height: 20px; + margin-left: 6px; + padding: 0; + font-size: 14px; } } } @@ -90,6 +100,7 @@ .linkEditor-dropdown { width: 100%; position: relative; + z-index: 999; .linkEditor-options-wrapper { width: 100%; position: absolute; @@ -107,4 +118,23 @@ background-color: $light-color-secondary; } } +} + +.linkEditor-group-buttons { + height: 20px; + display: flex; + justify-content: space-between; + button { + width: 31%; + height: 20px; + margin-left: 6px; + padding: 0; + font-size: 10px; + &:first-child { // delete + font-size: 14px; + } + &:disabled { // delete + 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 8d12bc30f..05e05c2ee 100644 --- a/src/client/views/nodes/LinkEditor.tsx +++ b/src/client/views/nodes/LinkEditor.tsx @@ -1,11 +1,7 @@ import { observable, computed, action } 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, Cast } from "../../../new_fields/Types"; import { Doc } from "../../../new_fields/Doc"; import { List } from "../../../new_fields/List"; @@ -21,7 +17,7 @@ import { string } from "prop-types"; library.add(faArrowLeft); library.add(faEllipsisV); -// this dropdown could be generalizeds +// this dropdown could be generalized @observer class LinkGroupsDropdown extends React.Component<{ groupId: string, groupType: string, setGroup: (groupId: string, group: string) => void }> { @observable private _searchTerm: string = ""; @@ -63,7 +59,7 @@ class LinkGroupsDropdown extends React.Component<{ groupId: string, groupType: s if (!exactFound && this._searchTerm !== "") { options.push(
{ this.createGroup(this._searchTerm); this.setGroupType(this._searchTerm); this.setSearchTerm("") }}>Create new "{this._searchTerm}" group
) + onClick={() => { this.createGroup(this._searchTerm); this.setGroupType(this._searchTerm); this.setSearchTerm("") }}>Define new "{this._searchTerm}" relationship) } return options; @@ -82,6 +78,8 @@ class LinkGroupsDropdown extends React.Component<{ groupId: string, groupType: s } } + + @observer class LinkMetadataEditor extends React.Component<{ groupType: string, mdDoc: Doc, mdKey: string, mdValue: string }> { @observable private _key: string = this.props.mdKey; @@ -111,11 +109,28 @@ class LinkMetadataEditor extends React.Component<{ groupType: string, mdDoc: Doc this._value = value; } + @action + removeMetadata = (): void => { + let groupMdKeys = new Array(...LinkManager.Instance.allGroups.get(this.props.groupType)!); + if (groupMdKeys) { + let index = groupMdKeys.indexOf(this._key); + if (index > -1) { + groupMdKeys.splice(index, 1); + } + else { + console.log("OLD KEY WAS NOT FOUND", ...groupMdKeys) + } + } + this._key = ""; + LinkManager.Instance.allGroups.set(this.props.groupType, groupMdKeys); + } + render() { return (
this.editMetadataKey(e.target.value)}>: this.editMetadataValue(e.target.value)}> +
) } @@ -131,85 +146,129 @@ interface LinkEditorProps { @observer export class LinkEditor extends React.Component { - @observable private _groups: Map = new Map(); - @observable private _metadata: Map> = new Map(); + @observable private _groups: Map = new Map(); // map of temp group id to the corresponding group doc constructor(props: LinkEditorProps) { super(props); let groups = new Map(); - let metadata: Map> = new Map(); let groupList = (Doc.AreProtosEqual(props.sourceDoc, Cast(props.linkDoc.anchor1, Doc, new Doc))) ? Cast(props.linkDoc.anchor1Groups, listSpec(Doc), []) : Cast(props.linkDoc.anchor2Groups, listSpec(Doc), []); groupList.forEach(groupDoc => { if (groupDoc instanceof Doc) { let id = Utils.GenerateGuid(); groups.set(id, groupDoc); - - let metadataMap = new Map(); - let metadataDocs = Cast(groupDoc.proto!.metadata, listSpec(Doc), []); - metadataDocs.forEach(mdDoc => { - if (mdDoc && mdDoc instanceof Doc) { // TODO: handle promise doc - metadataMap.set(Utils.GenerateGuid(), mdDoc); - } - }) - metadata.set(id, metadataMap); } else { // promise doc } }) this._groups = groups; - this._metadata = metadata; } - // @action - // editGroup(groupId: string, value: string) { - // let linkDoc = this.props.linkDoc.proto ? this.props.linkDoc.proto : this.props.linkDoc; - // let groupDoc = this._groups.get(groupId); - // if (groupDoc) { - // groupDoc.proto!.type = value; - // LinkUtils.setAnchorGroups(linkDoc, this.props.sourceDoc, [groupDoc]); - // } - // } - @action - addGroup = (e: React.MouseEvent): void => { - // create new document for group - let groupDoc = Docs.TextDocument(); - groupDoc.proto!.title = ""; - groupDoc.proto!.metadata = new List([]); + addGroup = (): void => { + console.log("before adding", ...Array.from(this._groups.keys())); + + let index = Array.from(this._groups.values()).findIndex(groupDoc => { + return groupDoc["type"] === "New Group"; + }) + if (index === -1) { + // create new document for group + let groupDoc = Docs.TextDocument(); + groupDoc.proto!.type = "New Group"; + groupDoc.proto!.metadata = Docs.TextDocument(); - this._groups.set(Utils.GenerateGuid(), groupDoc); + this._groups.set(Utils.GenerateGuid(), groupDoc); - let linkDoc = this.props.linkDoc.proto ? this.props.linkDoc.proto : this.props.linkDoc; - LinkUtils.setAnchorGroups(linkDoc, this.props.sourceDoc, Array.from(this._groups.values())); + let linkDoc = this.props.linkDoc.proto ? this.props.linkDoc.proto : this.props.linkDoc; + LinkUtils.setAnchorGroups(linkDoc, this.props.sourceDoc, Array.from(this._groups.values())); + } + + + // console.log("set anchor groups for", this.props.sourceDoc["title"]); + console.log("after adding", ...Array.from(this._groups.keys())); } @action - setGroup = (groupId: string, group: string): void => { + setGroupType = (groupId: string, groupType: string): void => { + console.log("setting for ", groupId); let linkDoc = this.props.linkDoc.proto ? this.props.linkDoc.proto : this.props.linkDoc; let groupDoc = this._groups.get(groupId); if (groupDoc) { - groupDoc.proto!.type = group; - LinkUtils.setAnchorGroups(linkDoc, this.props.sourceDoc, [groupDoc]); + console.log("found group doc"); + groupDoc.proto!.type = groupType; + + this._groups.set(groupId, groupDoc); + + let gd = this._groups.get(groupId); + if (gd) + console.log("just created", StrCast(gd["type"])); + + LinkUtils.setAnchorGroups(linkDoc, this.props.sourceDoc, Array.from(this._groups.values())); + console.log("set", Array.from(this._groups.values()).length) + } + + let anchor1groups: string[] = []; + Cast(this.props.linkDoc.anchor1Groups, listSpec(Doc), []).forEach(doc => { + if (doc instanceof Doc) { + anchor1groups.push(StrCast(doc.proto!.type)); + } else { + console.log("promise"); + } + }) + let anchor2groups: string[] = []; + Cast(this.props.linkDoc.anchor2Groups, listSpec(Doc), []).forEach(doc => { + if (doc instanceof Doc) { + anchor2groups.push(StrCast(doc.proto!.type)); + } else { + console.log("promise"); + } + }) + console.log("groups for anchors; anchor1: [", ...anchor1groups, "] ; anchor2: [", ...anchor2groups, "]") + } + + removeGroupFromLink = (groupId: string, groupType: string) => { + // this._groups.delete(groupId); + let groupDoc = this._groups.get(groupId); + if (groupDoc) { + LinkUtils.removeGroupFromAnchor(this.props.linkDoc, this.props.sourceDoc, groupType); + this._groups.delete(groupId); + } + // LinkUtils.setAnchorGroups(this.props.linkDoc, this.props.sourceDoc, Array.from(this._groups.values())); + console.log("\nremoved", groupId, "remaining", ...Array.from(this._groups.keys()), "\n"); + } + + deleteGroup = (groupId: string, groupType: string) => { + let groupDoc = this._groups.get(groupId); + if (groupDoc) { + LinkManager.Instance.deleteGroup(groupType); + this._groups.delete(groupId); } } renderGroup(groupId: string, groupDoc: Doc) { - return ( -
-
-

type:

- - {/* this.editGroup(groupId, e.target.value)}> */} - {/* this.editGroup(groupId, e.target.value)}> */} + let type = StrCast(groupDoc["type"]); + if ((type && LinkManager.Instance.allGroups.get(type)) || type === "New Group") { + return ( +
+
+

type:

+ +
+ {this.renderMetadata(groupId)} +
+ {groupDoc["type"] === "New Group" ? : + } + {/* */} + + + +
- {this.renderMetadata(groupId)} - {groupDoc["type"] === "*" ? <> : } - - {/* */} -
- ) + ) + } else { + return <> + } } viewGroupAsTable = (): void => { @@ -228,65 +287,6 @@ export class LinkEditor extends React.Component { } LinkManager.Instance.allGroups.set(groupType, mdKeys); - // // create new metadata doc - // let mdDoc = Docs.TextDocument(); - // mdDoc.proto!.title = ""; - // mdDoc.proto!.value = ""; - - // // append to map - // let mdMap = this._metadata.get(groupId); - // if (mdMap) { - // mdMap.set(Utils.GenerateGuid(), mdDoc); - // } else { - // mdMap = new Map(); - // mdMap.set(Utils.GenerateGuid(), mdDoc); - // } - - // // add to internal representation of metadata - // this._metadata.set(groupId, mdMap); - - // // add to internatal representation of group - // let groupDoc = this._groups.get(groupId); - // if (groupDoc) { - // groupDoc.proto!.metadata = new List(Array.from(mdMap.values())); - // this._groups.set(groupId, groupDoc); - // } - - // // add to link doc - // let linkDoc = this.props.linkDoc.proto ? this.props.linkDoc.proto : this.props.linkDoc; - // LinkUtils.setAnchorGroups(linkDoc, this.props.sourceDoc, Array.from(this._groups.values())); - } - - // @action - // editMetadataTitle(groupId: string, mdId: string, value: string) { - // let groupMd = this._metadata.get(groupId); - // if (groupMd) { - // let mdDoc = groupMd.get(mdId); - // if (mdDoc) { - // mdDoc.proto!.title = value; - // } - // } - // // set group and link? - // } - - // @action - // editMetadataValue(groupId: string, mdId: string, value: string) { - // let groupMd = this._metadata.get(groupId); - // if (groupMd) { - // let mdDoc = groupMd.get(mdId); - // if (mdDoc) { - // mdDoc.proto!.value = value; - // } - // } - // // set group and link? - // } - - @action - editMetadataKey(groupId: string, value: string) { - let groupDoc = this._groups.get(groupId); - if (groupDoc) { - - } } renderMetadata(groupId: string) { @@ -300,30 +300,10 @@ export class LinkEditor extends React.Component { groupMdKeys.forEach((key, index) => { metadata.push( - //
- // - // : - // - //
) }) } } - - - // let metadataMap = this._metadata.get(groupId); - // if (metadataMap) { - // metadataMap.forEach((mdDoc, mdId) => { - // metadata.push( - //
- // this.editMetadataTitle(groupId, mdId, e.target.value)}> - // : - // this.editMetadataValue(groupId, mdId, e.target.value)}> - //
- // ) - // }) - // } - return metadata; } @@ -340,10 +320,10 @@ export class LinkEditor extends React.Component {

editing link to: {destination.proto!.title}

- Groups: - + Relationships: +
- {groups} + {groups.length > 0 ? groups :
There are currently no relationships associated with this link.
}
); diff --git a/src/client/views/nodes/LinkMenu.scss b/src/client/views/nodes/LinkMenu.scss index 860f31d8a..7e031b897 100644 --- a/src/client/views/nodes/LinkMenu.scss +++ b/src/client/views/nodes/LinkMenu.scss @@ -40,7 +40,7 @@ display: flex; } &:hover .link-menu-item-content { - width: calc(100% - 72px); + width: calc(100% - 42px); } } .link-menu-item-buttons { diff --git a/src/client/views/nodes/LinkMenu.tsx b/src/client/views/nodes/LinkMenu.tsx index 21b5807ae..47bd6c3eb 100644 --- a/src/client/views/nodes/LinkMenu.tsx +++ b/src/client/views/nodes/LinkMenu.tsx @@ -32,7 +32,7 @@ export class LinkMenu extends React.Component { // }); // } - renderLinkGroupItems(links: Doc[]) { + renderGroup(links: Doc[]) { let source = this.props.docView.Document; return links.map(link => { let destination = LinkUtils.findOppositeAnchor(link, source); @@ -43,7 +43,7 @@ export class LinkMenu extends React.Component { }); } - renderLinkItems = (links: Map>): Array => { + renderLinks = (links: Map>): Array => { let linkItems: Array = []; links.forEach((links, group) => { @@ -51,7 +51,7 @@ export class LinkMenu extends React.Component {

{group}:

- {this.renderLinkGroupItems(links)} + {this.renderGroup(links)}
) @@ -65,10 +65,7 @@ export class LinkMenu extends React.Component { } 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 related = LinkManager.Instance.findRelatedGroupedLinks(this.props.docView.props.Document); + let related: Map = LinkManager.Instance.findRelatedGroupedLinks(this.props.docView.props.Document); if (this._editingLink === undefined) { return (
@@ -76,31 +73,11 @@ export class LinkMenu extends React.Component {
{/* {this.renderLinkItems(linkTo, "linkedTo", "Destination: ")} {this.renderLinkItems(linkFrom, "linkedFrom", "Source: ")} */} - {this.renderLinkItems(related)} + {this.renderLinks(related)}
); } else { - // let groups = new Map(); - // let metadata: Map> = new Map(); - // let groupList = (Doc.AreProtosEqual(this.props.docView.props.Document, Cast(this._editingLink.anchor1, Doc, new Doc))) ? - // Cast(this._editingLink.anchor1Groups, listSpec(Doc), []) : Cast(this._editingLink.anchor2Groups, listSpec(Doc), []); - // groupList.forEach(groupDoc => { - // if (groupDoc instanceof Doc) { - // let id = Utils.GenerateGuid(); - // groups.set(id, groupDoc); - - // let metadataMap = new Map(); - // let metadataDocs = Cast(groupDoc.proto!.metadata, listSpec(Doc), []); - // metadataDocs.forEach(mdDoc => { - // if (mdDoc && mdDoc instanceof Doc) { // TODO: handle promise doc - // metadataMap.set(Utils.GenerateGuid(), mdDoc); - // } - // }) - // metadata.set(id, metadataMap); - // } - // }) - return ( this._editingLink = undefined)}> ); -- cgit v1.2.3-70-g09d2 From f54496c7aa930e385e77aaf37df8d51db733e3a2 Mon Sep 17 00:00:00 2001 From: Fawn Date: Fri, 14 Jun 2019 15:24:35 -0400 Subject: cleaned up link code --- src/client/util/LinkManager.ts | 166 ++++++----------- src/client/views/nodes/LinkBox.scss | 122 ++++++------- src/client/views/nodes/LinkBox.tsx | 52 +----- src/client/views/nodes/LinkEditor.scss | 106 +++++------ src/client/views/nodes/LinkEditor.tsx | 319 +++++++++++++-------------------- src/client/views/nodes/LinkMenu.scss | 76 +------- src/client/views/nodes/LinkMenu.tsx | 54 ++---- 7 files changed, 311 insertions(+), 584 deletions(-) (limited to 'src/client/views/nodes/LinkBox.tsx') diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts index 929fcbf21..aaed7388d 100644 --- a/src/client/util/LinkManager.ts +++ b/src/client/util/LinkManager.ts @@ -1,71 +1,9 @@ -import { observable, computed, action } from "mobx"; -import React = require("react"); -import { SelectionManager } from "./SelectionManager"; -import { observer } from "mobx-react"; -import { props } from "bluebird"; -import { DocumentView } from "../views/nodes/DocumentView"; -import { link } from "fs"; +import { observable } from "mobx"; import { StrCast, Cast } from "../../new_fields/Types"; -import { Doc } from "../../new_fields/Doc"; +import { Doc, DocListCast } from "../../new_fields/Doc"; import { listSpec } from "../../new_fields/Schema"; import { List } from "../../new_fields/List"; -import { string } from "prop-types"; -import { Docs } from "../documents/Documents"; -export namespace LinkUtils { - export function findOppositeAnchor(link: Doc, anchor: Doc): Doc { - if (Doc.AreProtosEqual(anchor, Cast(link.anchor1, Doc, new Doc))) { - return Cast(link.anchor2, Doc, new Doc); - } else { - return Cast(link.anchor1, Doc, new Doc); - } - } - - export function setAnchorGroups(link: Doc, anchor: Doc, groups: Doc[]) { - // console.log("setting groups for anchor", anchor["title"]); - if (Doc.AreProtosEqual(anchor, Cast(link.anchor1, Doc, new Doc))) { - link.anchor1Groups = new List(groups); - - let print: string[] = []; - Cast(link.anchor1Groups, listSpec(Doc), []).forEach(doc => { - if (doc instanceof Doc) { - print.push(StrCast(doc.type)); - } - }); - console.log("set anchor's groups as", print); - } else { - link.anchor2Groups = new List(groups); - - let print: string[] = []; - Cast(link.anchor2Groups, listSpec(Doc), []).forEach(doc => { - if (doc instanceof Doc) { - print.push(StrCast(doc.type)); - } - }); - console.log("set anchor's groups as", print); - } - } - - export function removeGroupFromAnchor(link: Doc, anchor: Doc, groupType: string) { - let groups = Doc.AreProtosEqual(anchor, Cast(link.anchor1, Doc, new Doc)) ? - Cast(link.proto!.anchor1Groups, listSpec(Doc), []) : Cast(link.proto!.anchor2Groups, listSpec(Doc), []); - - let newGroups: Doc[] = []; - groups.forEach(groupDoc => { - if (groupDoc instanceof Doc && StrCast(groupDoc.type) !== groupType) { - newGroups.push(groupDoc); - } // TODO: promise - }); - - // let grouptypes: string[] = []; - // newGroups.forEach(groupDoc => { - // grouptypes.push(StrCast(groupDoc.type)); - // }); - // console.log("remove anchor's groups as", grouptypes); - - LinkUtils.setAnchorGroups(link, anchor, newGroups); - } -} /* * link doc: @@ -94,6 +32,7 @@ export class LinkManager { @observable public allLinks: Array = []; // list of link docs @observable public groupMetadataKeys: Map> = new Map(); // map of group type to list of its metadata keys + // finds all links that contain the given anchor public findAllRelatedLinks(anchor: Doc): Array { return LinkManager.Instance.allLinks.filter( link => Doc.AreProtosEqual(anchor, Cast(link.anchor1, Doc, new Doc)) || Doc.AreProtosEqual(anchor, Cast(link.anchor2, Doc, new Doc))); @@ -102,27 +41,19 @@ export class LinkManager { // returns map of group type to anchor's links in that group type public findRelatedGroupedLinks(anchor: Doc): Map> { let related = this.findAllRelatedLinks(anchor); - let anchorGroups = new Map>(); related.forEach(link => { + let groups = LinkManager.Instance.getAnchorGroups(link, anchor); - // get groups of given anchor categorizes this link/opposite anchor in - let groups = (Doc.AreProtosEqual(anchor, Cast(link.anchor1, Doc, new Doc))) ? Cast(link.anchor1Groups, listSpec(Doc), []) : Cast(link.anchor2Groups, listSpec(Doc), []); if (groups.length > 0) { groups.forEach(groupDoc => { - if (groupDoc instanceof Doc) { - let groupType = StrCast(groupDoc.type); - let group = anchorGroups.get(groupType); // TODO: clean this up lol - if (group) group.push(link); - else group = [link]; - anchorGroups.set(groupType, group); - } else { - // promise doc - } - + let groupType = StrCast(groupDoc.type); + let group = anchorGroups.get(groupType); + if (group) group.push(link); + else group = [link]; + anchorGroups.set(groupType, group); }); - } - else { + } else { // if link is in no groups then put it in default group let group = anchorGroups.get("*"); if (group) group.push(link); @@ -134,52 +65,38 @@ export class LinkManager { return anchorGroups; } - public findMetadataInGroup(groupType: string) { + // returns a list of all metadata docs associated with the given group type + public findAllMetadataDocsInGroup(groupType: string): Array { let md: Doc[] = []; let allLinks = LinkManager.Instance.allLinks; - // for every link find its groups - // allLinks.forEach(linkDoc => { - // let anchor1groups = LinkManager.Instance.findRelatedGroupedLinks(Cast(linkDoc["anchor1"], Doc, new Doc)); - // if (anchor1groups.get(groupType)) { - // md.push(linkDoc["anchor1"]["group"]) - // } - // }) allLinks.forEach(linkDoc => { - let anchor1Groups = Cast(linkDoc.anchor1Groups, listSpec(Doc), []); - let anchor2Groups = Cast(linkDoc.anchor2Groups, listSpec(Doc), []); - anchor1Groups.forEach(groupDoc => { - if (groupDoc instanceof Doc) { - if (StrCast(groupDoc.type) === groupType) { - md.push(Cast(groupDoc.metadata, Doc, new Doc)); - } - } else { - // TODO: promise - } - }); - anchor2Groups.forEach(groupDoc => { - if (groupDoc instanceof Doc) { - if (StrCast(groupDoc.type) === groupType) { - md.push(Cast(groupDoc.metadata, Doc, new Doc)); - } - } else { - // TODO: promise - } - }); - + 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; } - public deleteGroup(groupType: string) { + // removes all group docs from all links with the given group type + public deleteGroup(groupType: string): void { let deleted = LinkManager.Instance.groupMetadataKeys.delete(groupType); if (deleted) { LinkManager.Instance.allLinks.forEach(linkDoc => { - LinkUtils.removeGroupFromAnchor(linkDoc, Cast(linkDoc.anchor1, Doc, new Doc), groupType); - LinkUtils.removeGroupFromAnchor(linkDoc, Cast(linkDoc.anchor2, Doc, new Doc), groupType); + LinkManager.Instance.removeGroupFromAnchor(linkDoc, Cast(linkDoc.anchor1, Doc, new Doc), groupType); + LinkManager.Instance.removeGroupFromAnchor(linkDoc, Cast(linkDoc.anchor2, Doc, new Doc), groupType); }); } } + // 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); + } + + // checks if a link with the given anchors exists public doesLinkExist(anchor1: Doc, anchor2: Doc) { let allLinks = LinkManager.Instance.allLinks; let index = allLinks.findIndex(linkDoc => { @@ -189,4 +106,31 @@ export class LinkManager { return index !== -1; } + // finds the opposite anchor of a given anchor in a link + public findOppositeAnchor(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); + } + } + + // gets the groups associates with an anchor in a link + public getAnchorGroups(linkDoc: Doc, anchor: Doc): Array { + 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(groups); + } else { + linkDoc.anchor2Groups = new List(groups); + } + } + } \ No newline at end of file diff --git a/src/client/views/nodes/LinkBox.scss b/src/client/views/nodes/LinkBox.scss index 639f83b38..08fefaf4d 100644 --- a/src/client/views/nodes/LinkBox.scss +++ b/src/client/views/nodes/LinkBox.scss @@ -1,66 +1,62 @@ @import "../globalCssVariables"; -.link-container { - width: 100%; - height: 50px; +.link-menu-item { + border-top: 0.5px solid $light-color-secondary; + padding: 6px; + position: relative; 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; + font-size: 12px; + + .link-menu-item-content { + width: 100%; + } + + &:last-child { + border-bottom: 0.5px solid $light-color-secondary; + } + &:hover { + .link-menu-item-buttons { + display: flex; + } + .link-menu-item-content { + width: calc(100% - 42px); + } + } +} + +.link-menu-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; + } + } } \ No newline at end of file diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx index 5597bb1aa..1a7cce4a3 100644 --- a/src/client/views/nodes/LinkBox.tsx +++ b/src/client/views/nodes/LinkBox.tsx @@ -4,13 +4,10 @@ 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'; +import { StrCast } from '../../../new_fields/Types'; library.add(faEye); @@ -20,9 +17,8 @@ library.add(faArrowRight); interface Props { linkDoc: Doc; - linkName: String; - pairedDoc: Doc; - type: String; + sourceDoc: Doc; + destinationDoc: Doc; showEditor: () => void; } @@ -30,58 +26,28 @@ interface Props { export class LinkBox extends React.Component { @undoBatch - followLink = async (e: React.PointerEvent): Promise => { + onFollowLink = async (e: React.PointerEvent): Promise => { e.stopPropagation(); - DocumentManager.Instance.jumpToDocument(this.props.pairedDoc, e.altKey); + DocumentManager.Instance.jumpToDocument(this.props.destinationDoc, e.altKey); } - onEditButtonPressed = (e: React.PointerEvent): void => { + onEdit = (e: React.PointerEvent): void => { e.stopPropagation(); - this.props.showEditor(); } - // @action - // onDeleteButtonPressed = async (e: React.PointerEvent): Promise => { - // 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 ( - //
-

{this.props.linkName}

-
-
-

{this.props.type}{this.props.pairedDoc.Title}

+

{StrCast(this.props.destinationDoc.title)}

- {/*
-
*/} -
-
-
-
- {/*
-
*/} +
+
); diff --git a/src/client/views/nodes/LinkEditor.scss b/src/client/views/nodes/LinkEditor.scss index 01f0cb82a..6aac39f45 100644 --- a/src/client/views/nodes/LinkEditor.scss +++ b/src/client/views/nodes/LinkEditor.scss @@ -1,92 +1,63 @@ @import "../globalCssVariables"; -.edit-container { + +.linkEditor { width: 100%; height: auto; - display: flex; - flex-direction: column; + font-size: 12px; // TODO } -.name-input { - margin-bottom: 10px; - padding: 5px; - font-size: 12px; - border: 1px solid #bababa; +.linkEditor-back { + margin-bottom: 6px; } -.description-input { - font-size: 11px; - padding: 5px; - margin-bottom: 10px; - border: 1px solid #bababa; -} +.linkEditor-groupsLabel { + display: flex; + justify-content: space-between; -.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; + button { + width: 20px; + height: 20px; + margin-left: 6px; + padding: 0; + font-size: 14px; + } } -.save-button:hover { - background: $main-accent; - cursor: pointer; +.linkEditor-linkedTo { + border-bottom: 0.5px solid $light-color-secondary; + padding-bottom: 6px; + margin-bottom: 6px; } -.linkEditor { - font-size: 12px; // TODO - - .linkEditor-back { - // background-color: $dark-color; - // color: $light-color; - margin-bottom: 6px; - } +.linkEditor-group { + background-color: $light-color-secondary; + padding: 6px; + margin: 3px 0; + border-radius: 3px; - .linkEditor-groupsLabel { - display: flex; - justify-content: space-between; - button { - width: 20px; - height: 20px; - margin-left: 6px; - padding: 0; - font-size: 14px; - } - } - .linkEditor-linkedTo { - border-bottom: 0.5px solid $light-color-secondary; - padding-bottom: 6px; - margin-bottom: 6px; - } - .linkEditor-group { - background-color: $light-color-secondary; - padding: 6px; - margin: 3px 0; - border-radius: 3px; - } .linkEditor-group-row { display: flex; margin-bottom: 6px; + .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% - 18px); height: 20px; } + button { width: 20px; height: 20px; @@ -97,10 +68,12 @@ } } + .linkEditor-dropdown { width: 100%; position: relative; z-index: 999; + .linkEditor-options-wrapper { width: 100%; position: absolute; @@ -109,12 +82,14 @@ 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: $intermediate-color; font-weight: bold; @@ -126,19 +101,18 @@ height: 20px; display: flex; justify-content: space-between; + .linkEditor-groupOpts { width: calc(20% - 3px); height: 20px; - // margin-left: 6px; padding: 0; font-size: 10px; - &:first-child { // delete - font-size: 14px; - } - &:disabled { // delete + + &:disabled { background-color: gray; } } + .linkEditor-groupOpts button { width: 100%; height: 20px; diff --git a/src/client/views/nodes/LinkEditor.tsx b/src/client/views/nodes/LinkEditor.tsx index 5097d625e..484682c22 100644 --- a/src/client/views/nodes/LinkEditor.tsx +++ b/src/client/views/nodes/LinkEditor.tsx @@ -4,61 +4,52 @@ import { observer } from "mobx-react"; import './LinkEditor.scss'; import { StrCast, Cast } from "../../../new_fields/Types"; import { Doc } from "../../../new_fields/Doc"; -import { List } from "../../../new_fields/List"; -import { listSpec } from "../../../new_fields/Schema"; -import { LinkManager, LinkUtils } from "../../util/LinkManager"; +import { LinkManager } from "../../util/LinkManager"; import { Docs } from "../../documents/Documents"; import { Utils } from "../../../Utils"; import { faArrowLeft, faEllipsisV, faTable } from '@fortawesome/free-solid-svg-icons'; import { library } from "@fortawesome/fontawesome-svg-core"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { string } from "prop-types"; import { SetupDrag } from "../../util/DragManager"; library.add(faArrowLeft); library.add(faEllipsisV); library.add(faTable); + +interface GroupTypesDropdownProps { + groupId: string; + groupType: string; + setGroup: (groupId: string, group: string) => void; +} // this dropdown could be generalized @observer -class LinkGroupsDropdown extends React.Component<{ groupId: string, groupType: string, setGroup: (groupId: string, group: string) => void }> { +class GroupTypesDropdown extends React.Component { @observable private _searchTerm: string = ""; @observable private _groupType: string = this.props.groupType; - @action - setSearchTerm(value: string) { - this._searchTerm = value; - } + @action setSearchTerm = (value: string): void => { this._searchTerm = value; }; + @action setGroupType = (value: string): void => { this._groupType = value; }; @action - setGroupType(value: string) { - this._groupType = value; + createGroup = (groupType: string): void => { + this.props.setGroup(this.props.groupId, groupType); + LinkManager.Instance.groupMetadataKeys.set(groupType, []); } - @action - createGroup(value: string) { - LinkManager.Instance.groupMetadataKeys.set(value, []); - this.props.setGroup(this.props.groupId, value); - } + renderOptions = (): JSX.Element[] | JSX.Element => { + if (this._searchTerm === "") return <>; - renderOptions = (): JSX.Element[] => { - let allGroups: string[], searchTerm: string, results: string[], exactFound: boolean; - if (this._searchTerm !== "") { - allGroups = Array.from(LinkManager.Instance.groupMetadataKeys.keys()); - searchTerm = this._searchTerm.toUpperCase(); - results = allGroups.filter(group => group.toUpperCase().indexOf(searchTerm) > -1); - exactFound = results.findIndex(group => group.toUpperCase() === searchTerm) > -1; - } else { - results = []; - exactFound = false; - } + let allGroupTypes = Array.from(LinkManager.Instance.groupMetadataKeys.keys()); + 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 = []; - results.forEach(result => { - options.push(
{ this.props.setGroup(this.props.groupId, result); this.setGroupType(result); this.setSearchTerm(""); }}>{result}
); + let options = groupOptions.map(groupType => { + return
{ this.props.setGroup(this.props.groupId, groupType); this.setGroupType(groupType); this.setSearchTerm(""); }}>{groupType}
; }); + // if search term does not already exist as a group type, give option to create new group type if (!exactFound && this._searchTerm !== "") { options.push(
{ this.createGroup(this._searchTerm); this.setGroupType(this._searchTerm); this.setSearchTerm(""); }}>Define new "{this._searchTerm}" relationship
); @@ -81,57 +72,66 @@ class LinkGroupsDropdown extends React.Component<{ groupId: string, groupType: s } - +interface LinkMetadataEditorProps { + groupType: string; + mdDoc: Doc; + mdKey: string; + mdValue: string; +} @observer -class LinkMetadataEditor extends React.Component<{ groupType: string, mdDoc: Doc, mdKey: string, mdValue: string }> { +class LinkMetadataEditor extends React.Component { @observable private _key: string = this.props.mdKey; @observable private _value: string = this.props.mdValue; + @observable private _keyError: boolean = false; @action - editMetadataKey = (value: string): void => { - // TODO: check that metadata doesnt already exist in group + setMetadataKey = (value: string): void => { let groupMdKeys = new Array(...LinkManager.Instance.groupMetadataKeys.get(this.props.groupType)!); - if (groupMdKeys) { - let index = groupMdKeys.indexOf(this._key); - if (index > -1) { - groupMdKeys[index] = value; - } - else { - console.log("OLD KEY WAS NOT FOUND", ...groupMdKeys); - } + + // 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; } + // set new value for key + let currIndex = groupMdKeys.findIndex(key => key.toUpperCase() === this._key.toUpperCase()); + if (currIndex === -1) console.error("LinkMetadataEditor: key was not found"); + groupMdKeys[currIndex] = value; + this._key = value; LinkManager.Instance.groupMetadataKeys.set(this.props.groupType, groupMdKeys); } @action - editMetadataValue = (value: string): void => { - this.props.mdDoc[this._key] = value; - this._value = value; + setMetadataValue = (value: string): void => { + if (!this._keyError) { + this._value = value; + this.props.mdDoc[this._key] = value; + } } @action removeMetadata = (): void => { let groupMdKeys = new Array(...LinkManager.Instance.groupMetadataKeys.get(this.props.groupType)!); - if (groupMdKeys) { - let index = groupMdKeys.indexOf(this._key); - if (index > -1) { - groupMdKeys.splice(index, 1); - } - else { - console.log("OLD KEY WAS NOT FOUND", ...groupMdKeys); - } - } - this._key = ""; + + let index = groupMdKeys.findIndex(key => key.toUpperCase() === this._key.toUpperCase()); + if (index === -1) console.error("LinkMetadataEditor: key was not found"); + groupMdKeys.splice(index, 1); + LinkManager.Instance.groupMetadataKeys.set(this.props.groupType, groupMdKeys); + this._key = ""; } render() { return (
- this.editMetadataKey(e.target.value)}>: - this.editMetadataValue(e.target.value)}> + this.setMetadataKey(e.target.value)}>: + this.setMetadataValue(e.target.value)}>
); @@ -144,175 +144,127 @@ interface LinkEditorProps { linkDoc: Doc; showLinks: () => void; } - @observer export class LinkEditor extends React.Component { - @observable private _groups: Map = new Map(); // map of temp group id to the corresponding group doc + // map of temporary group id to the corresponding group doc + @observable private _groups: Map = new Map(); constructor(props: LinkEditorProps) { super(props); let groups = new Map(); - let groupList = (Doc.AreProtosEqual(props.sourceDoc, Cast(props.linkDoc.anchor1, Doc, new Doc))) ? - Cast(props.linkDoc.anchor1Groups, listSpec(Doc), []) : Cast(props.linkDoc.anchor2Groups, listSpec(Doc), []); + let groupList = LinkManager.Instance.getAnchorGroups(props.linkDoc, props.sourceDoc); groupList.forEach(groupDoc => { - if (groupDoc instanceof Doc) { - let id = Utils.GenerateGuid(); - groups.set(id, groupDoc); - } else { - // promise doc - } + let id = Utils.GenerateGuid(); + groups.set(id, groupDoc); }); this._groups = groups; } @action addGroup = (): void => { - console.log("before adding", ...Array.from(this._groups.keys())); - // new group only gets added if there is not already a group with type "new group" let index = Array.from(this._groups.values()).findIndex(groupDoc => { return groupDoc.type === "New Group"; }); - if (index === -1) { - // create new document for group - let mdDoc = Docs.TextDocument(); - mdDoc.proto!.anchor1 = this.props.sourceDoc.title; - mdDoc.proto!.anchor2 = LinkUtils.findOppositeAnchor(this.props.linkDoc, this.props.sourceDoc).title; + if (index > -1) return; - let groupDoc = Docs.TextDocument(); - groupDoc.proto!.type = "New Group"; - groupDoc.proto!.metadata = mdDoc; + // create new metadata document for group + let mdDoc = Docs.TextDocument(); + mdDoc.proto!.anchor1 = this.props.sourceDoc.title; + mdDoc.proto!.anchor2 = LinkManager.Instance.findOppositeAnchor(this.props.linkDoc, this.props.sourceDoc).title; - this._groups.set(Utils.GenerateGuid(), groupDoc); + // create new group document + let groupDoc = Docs.TextDocument(); + groupDoc.proto!.type = "New Group"; + groupDoc.proto!.metadata = mdDoc; - let linkDoc = this.props.linkDoc.proto ? this.props.linkDoc.proto : this.props.linkDoc; - LinkUtils.setAnchorGroups(linkDoc, this.props.sourceDoc, Array.from(this._groups.values())); - } + this._groups.set(Utils.GenerateGuid(), groupDoc); - - // console.log("set anchor groups for", this.props.sourceDoc["title"]); - console.log("after adding", ...Array.from(this._groups.keys())); + let linkDoc = this.props.linkDoc.proto ? this.props.linkDoc.proto : this.props.linkDoc; + LinkManager.Instance.setAnchorGroups(linkDoc, this.props.sourceDoc, Array.from(this._groups.values())); } @action setGroupType = (groupId: string, groupType: string): void => { - console.log("setting for ", groupId); - // let linkDoc = this.props.linkDoc.proto ? this.props.linkDoc.proto : this.props.linkDoc; let groupDoc = this._groups.get(groupId); if (groupDoc) { - console.log("found group doc"); groupDoc.proto!.type = groupType; - this._groups.set(groupId, groupDoc); - - - LinkUtils.setAnchorGroups(this.props.linkDoc, this.props.sourceDoc, Array.from(this._groups.values())); - console.log("set", Array.from(this._groups.values()).length); + LinkManager.Instance.setAnchorGroups(this.props.linkDoc, this.props.sourceDoc, Array.from(this._groups.values())); } - - let anchor1groups: string[] = []; - Cast(this.props.linkDoc.anchor1Groups, listSpec(Doc), []).forEach(doc => { - if (doc instanceof Doc) { - anchor1groups.push(StrCast(doc.proto!.type)); - } else { - console.log("promise"); - } - }); - let anchor2groups: string[] = []; - Cast(this.props.linkDoc.anchor2Groups, listSpec(Doc), []).forEach(doc => { - if (doc instanceof Doc) { - anchor2groups.push(StrCast(doc.proto!.type)); - } else { - console.log("promise"); - } - }); - console.log("groups for anchors; anchor1: [", ...anchor1groups, "] ; anchor2: [", ...anchor2groups, "]"); } - removeGroupFromLink = (groupId: string, groupType: string) => { - // this._groups.delete(groupId); + removeGroupFromLink = (groupId: string, groupType: string): void => { let groupDoc = this._groups.get(groupId); - if (groupDoc) { - LinkUtils.removeGroupFromAnchor(this.props.linkDoc, this.props.sourceDoc, groupType); - this._groups.delete(groupId); - } - // LinkUtils.setAnchorGroups(this.props.linkDoc, this.props.sourceDoc, Array.from(this._groups.values())); - console.log("\nremoved", groupId, "remaining", ...Array.from(this._groups.keys()), "\n"); + if (!groupDoc) console.error("LinkEditor: group not found"); + LinkManager.Instance.removeGroupFromAnchor(this.props.linkDoc, this.props.sourceDoc, groupType); + this._groups.delete(groupId); } - deleteGroup = (groupId: string, groupType: string) => { + deleteGroup = (groupId: string, groupType: string): void => { let groupDoc = this._groups.get(groupId); - if (groupDoc) { - LinkManager.Instance.deleteGroup(groupType); - this._groups.delete(groupId); - } + if (!groupDoc) console.error("LinkEditor: group not found"); + LinkManager.Instance.deleteGroup(groupType); + this._groups.delete(groupId); } - copyGroup = (groupId: string, groupType: string) => { - let oppAnchor = LinkUtils.findOppositeAnchor(this.props.linkDoc, this.props.sourceDoc); - let groupList = (Doc.AreProtosEqual(oppAnchor, Cast(this.props.linkDoc.anchor1, Doc, new Doc))) ? - Cast(this.props.linkDoc.anchor1Groups, listSpec(Doc), []) : Cast(this.props.linkDoc.anchor2Groups, listSpec(Doc), []); - // if group already exists on opposite anchor, copy value - let index = groupList.findIndex(groupDoc => { - if (groupDoc instanceof Doc) { - return StrCast(groupDoc.type) === groupType; - } else { - return false; - } - }); - // TODO: clean - // if (index > 0) { - // let thisGroupDoc = this._groups.get(groupId); - // let oppGroupDoc = groupList[index]; - // let keys = LinkManager.Instance.allGroups.get(groupType); - // if (keys) { - // keys.forEach(key => { - // if (thisGroupDoc && oppGroupDoc instanceof Doc) { // TODO: clean - // let val = thisGroupDoc[key] === undefined ? "" : StrCast(thisGroupDoc[key]); - // oppGroupDoc[key] = val; - // } - // // mdDoc[key] === undefined) ? "" : StrCast(mdDoc[key]) - // // oppGroupDoc[key] = thisGroupDoc[key]; - // }) - // } - // // LinkUtils.setAnchorGroups(this.props.linkDoc, oppAnchor, [oppGroupDoc]); - // } else { - let thisGroupDoc = this._groups.get(groupId); - let thisMdDoc = Cast(thisGroupDoc!.metadata, Doc, new Doc); - let newGroupDoc = Docs.TextDocument(); - let newMdDoc = Docs.TextDocument(); - newMdDoc.proto!.anchor1 = StrCast(thisMdDoc.anchor2); - newMdDoc.proto!.anchor2 = StrCast(thisMdDoc.anchor1); + copyGroup = (groupId: string, groupType: string): void => { + let sourceGroupDoc = this._groups.get(groupId); + let sourceMdDoc = Cast(sourceGroupDoc!.metadata, Doc, new Doc); + let destDoc = LinkManager.Instance.findOppositeAnchor(this.props.linkDoc, this.props.sourceDoc); + let destGroupList = LinkManager.Instance.getAnchorGroups(this.props.linkDoc, destDoc); let keys = LinkManager.Instance.groupMetadataKeys.get(groupType); + + // create new metadata doc with copied kvp + let destMdDoc = Docs.TextDocument(); + destMdDoc.proto!.anchor1 = StrCast(sourceMdDoc.anchor2); + destMdDoc.proto!.anchor2 = StrCast(sourceMdDoc.anchor1); if (keys) { keys.forEach(key => { - if (thisGroupDoc) { // TODO: clean - let val = thisMdDoc[key] === undefined ? "" : StrCast(thisMdDoc[key]); - newMdDoc[key] = val; - } - // mdDoc[key] === undefined) ? "" : StrCast(mdDoc[key]) - // oppGroupDoc[key] = thisGroupDoc[key]; + let val = sourceMdDoc[key] === undefined ? "" : StrCast(sourceMdDoc[key]); + destMdDoc[key] = val; }); } - newGroupDoc.proto!.type = groupType; - newGroupDoc.proto!.metadata = newMdDoc; - LinkUtils.setAnchorGroups(this.props.linkDoc, oppAnchor, [newGroupDoc]); // TODO: fix to append to list - // } + // create new group doc with new metadata doc + let destGroupDoc = Docs.TextDocument(); + destGroupDoc.proto!.type = groupType; + destGroupDoc.proto!.metadata = destMdDoc; - // else create group on opposite anchor + // if group does not already exist on opposite anchor, create group doc + let index = destGroupList.findIndex(groupDoc => { StrCast(groupDoc.type).toUpperCase() === groupType.toUpperCase(); }); + if (index > -1) { + destGroupList[index] = destGroupDoc; + } else { + destGroupList.push(destGroupDoc); + } + + LinkManager.Instance.setAnchorGroups(this.props.linkDoc, destDoc, destGroupList); + } + + viewGroupAsTable = (groupId: string, groupType: string): JSX.Element => { + let keys = LinkManager.Instance.groupMetadataKeys.get(groupType); + let groupDoc = this._groups.get(groupId); + if (keys && groupDoc) { + let docs: Doc[] = LinkManager.Instance.findAllMetadataDocsInGroup(groupType); + let createTable = action(() => Docs.SchemaDocument(["anchor1", "anchor2", ...keys!], docs, { width: 200, height: 200, title: groupType + " table" })); + let ref = React.createRef(); + return
; + } else { + return ; + } } - renderGroup(groupId: string, groupDoc: Doc) { + renderGroup = (groupId: string, groupDoc: Doc): JSX.Element => { let type = StrCast(groupDoc.type); if ((type && LinkManager.Instance.groupMetadataKeys.get(type)) || type === "New Group") { return (

type:

- +
{this.renderMetadata(groupId)}
@@ -330,35 +282,20 @@ export class LinkEditor extends React.Component { } } - viewGroupAsTable(groupId: string, groupType: string) { - let keys = LinkManager.Instance.groupMetadataKeys.get(groupType); - let groupDoc = this._groups.get(groupId); - if (keys && groupDoc) { - console.log("keys:", ...keys); - let docs: Doc[] = LinkManager.Instance.findMetadataInGroup(groupType); - let createTable = action(() => Docs.SchemaDocument(["anchor1", "anchor2", ...keys!], docs, { width: 200, height: 200, title: groupType + " table" })); - let ref = React.createRef(); - return
; - } else { - return ; - } - } - @action addMetadata = (groupType: string): void => { let mdKeys = LinkManager.Instance.groupMetadataKeys.get(groupType); if (mdKeys) { - if (mdKeys.indexOf("new key") === -1) { - mdKeys.push("new key"); - } + // 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"); } else { mdKeys = ["new key"]; } LinkManager.Instance.groupMetadataKeys.set(groupType, mdKeys); } - renderMetadata(groupId: string) { + renderMetadata = (groupId: string): JSX.Element[] => { let metadata: Array = []; let groupDoc = this._groups.get(groupId); if (groupDoc) { @@ -377,7 +314,7 @@ export class LinkEditor extends React.Component { } render() { - let destination = LinkUtils.findOppositeAnchor(this.props.linkDoc, this.props.sourceDoc); + let destination = LinkManager.Instance.findOppositeAnchor(this.props.linkDoc, this.props.sourceDoc); let groups: Array = []; this._groups.forEach((groupDoc, groupId) => { diff --git a/src/client/views/nodes/LinkMenu.scss b/src/client/views/nodes/LinkMenu.scss index 7e031b897..09a830c9e 100644 --- a/src/client/views/nodes/LinkMenu.scss +++ b/src/client/views/nodes/LinkMenu.scss @@ -1,84 +1,14 @@ @import "../globalCssVariables"; -#linkMenu-container { +.linkMenu { width: 100%; height: auto; - display: flex; - flex-direction: column; } -#linkMenu-searchBar { - width: 100%; - padding: 5px; - margin-bottom: 10px; - font-size: 12px; - border: 1px solid #bababa; -} - -#linkMenu-list { - margin-top: 5px; - width: 100%; - height: 100px; +.linkMenu-list { + max-height: 200px; overflow-y: scroll; } -.link-menu-group { - .link-menu-item { - border-top: 0.5px solid $light-color-secondary; - padding: 6px; - position: relative; - display: flex; - - .link-menu-item-content { - width: 100%; - } - &:last-child { - border-bottom: 0.5px solid $light-color-secondary; - } - &:hover .link-menu-item-buttons { - display: flex; - } - &:hover .link-menu-item-content { - width: calc(100% - 42px); - } - } - .link-menu-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 47bd6c3eb..ebca54c92 100644 --- a/src/client/views/nodes/LinkMenu.tsx +++ b/src/client/views/nodes/LinkMenu.tsx @@ -6,12 +6,8 @@ import { LinkEditor } from "./LinkEditor"; import './LinkMenu.scss'; import React = require("react"); import { Doc, DocListCast } from "../../../new_fields/Doc"; -import { Cast, FieldValue, StrCast } from "../../../new_fields/Types"; import { Id } from "../../../new_fields/FieldSymbols"; import { LinkManager, LinkUtils } from "../../util/LinkManager"; -import { number, string } from "prop-types"; -import { listSpec } from "../../../new_fields/Schema"; -import { Utils } from "../../../Utils"; interface Props { docView: DocumentView; @@ -23,57 +19,42 @@ export class LinkMenu extends React.Component { @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 this._editingLink = link)} type={type} />; - // } - // }); - // } - - renderGroup(links: Doc[]) { + renderGroup = (group: Doc[]): Array => { let source = this.props.docView.Document; - return links.map(link => { - let destination = LinkUtils.findOppositeAnchor(link, source); - let doc = FieldValue(Cast(destination, Doc)); - if (doc) { - return this._editingLink = link)} type={""} />; - } + return group.map(linkDoc => { + let destination = LinkUtils.findOppositeAnchor(linkDoc, source); + return this._editingLink = linkDoc)} />; }); } - renderLinks = (links: Map>): Array => { + renderAllGroups = (groups: Map>): Array => { let linkItems: Array = []; - - links.forEach((links, group) => { + groups.forEach((group, groupType) => { linkItems.push( -
-

{group}:

+
+

{groupType}:

- {this.renderGroup(links)} + {this.renderGroup(group)}
- ) + ); }); - if (linkItems.length === 0) { - linkItems.push(

no links have been created yet

); - } + // source doc has no links + if (linkItems.length === 0) linkItems.push(

No links have been created yet. Drag the linking button onto another document to create a link.

); return linkItems; } render() { - let related: Map = LinkManager.Instance.findRelatedGroupedLinks(this.props.docView.props.Document); + let sourceDoc = this.props.docView.props.Document; + let groups: Map = LinkManager.Instance.findRelatedGroupedLinks(sourceDoc); if (this._editingLink === undefined) { return ( -
+
{/* */} -
- {/* {this.renderLinkItems(linkTo, "linkedTo", "Destination: ")} - {this.renderLinkItems(linkFrom, "linkedFrom", "Source: ")} */} - {this.renderLinks(related)} +
+ {this.renderAllGroups(groups)}
); @@ -82,6 +63,5 @@ export class LinkMenu extends React.Component { this._editingLink = undefined)}> ); } - } } \ No newline at end of file -- cgit v1.2.3-70-g09d2 From 1afb8d18e0f63e7e9ab05ccf79f5f34533fdec05 Mon Sep 17 00:00:00 2001 From: Fawn Date: Fri, 14 Jun 2019 16:38:25 -0400 Subject: links can be expanded in menu to view metadata --- src/client/views/nodes/LinkBox.scss | 30 ++++++++++++++++------ src/client/views/nodes/LinkBox.tsx | 51 +++++++++++++++++++++++++++---------- src/client/views/nodes/LinkMenu.tsx | 8 +++--- 3 files changed, 64 insertions(+), 25 deletions(-) (limited to 'src/client/views/nodes/LinkBox.tsx') diff --git a/src/client/views/nodes/LinkBox.scss b/src/client/views/nodes/LinkBox.scss index 08fefaf4d..77462f611 100644 --- a/src/client/views/nodes/LinkBox.scss +++ b/src/client/views/nodes/LinkBox.scss @@ -1,29 +1,43 @@ @import "../globalCssVariables"; -.link-menu-item { - border-top: 0.5px solid $light-color-secondary; +.linkMenu-item { + border-top: 0.5px solid $main-accent; padding: 6px; position: relative; display: flex; font-size: 12px; - .link-menu-item-content { + .linkMenu-item-content { width: 100%; } + + .link-metadata { + margin-top: 6px; + padding: 3px; + border-top: 0.5px solid $light-color-secondary; + .link-metadata-row { + margin-left: 6px; + } + } &:last-child { - border-bottom: 0.5px solid $light-color-secondary; + border-bottom: 0.5px solid $main-accent; } &:hover { - .link-menu-item-buttons { + .linkMenu-item-buttons { display: flex; } - .link-menu-item-content { - width: calc(100% - 42px); + .linkMenu-item-content { + &.expand-two { + width: calc(100% - 46px); + } + &.expand-three { + width: calc(100% - 78px); + } } } } -.link-menu-item-buttons { +.linkMenu-item-buttons { display: none; position: absolute; top: 50%; diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx index 1a7cce4a3..9fbe83126 100644 --- a/src/client/views/nodes/LinkBox.tsx +++ b/src/client/views/nodes/LinkBox.tsx @@ -1,5 +1,5 @@ import { library } from '@fortawesome/fontawesome-svg-core'; -import { faEdit, faEye, faTimes, faArrowRight } from '@fortawesome/free-solid-svg-icons'; +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"; @@ -7,15 +7,14 @@ import { undoBatch } from "../../util/UndoManager"; import './LinkBox.scss'; import React = require("react"); import { Doc } from '../../../new_fields/Doc'; -import { StrCast } from '../../../new_fields/Types'; +import { StrCast, Cast } from '../../../new_fields/Types'; +import { observable, action } from 'mobx'; +import { LinkManager } from '../../util/LinkManager'; +library.add(faEye, faEdit, faTimes, faArrowRight, faChevronDown, faChevronUp); -library.add(faEye); -library.add(faEdit); -library.add(faTimes); -library.add(faArrowRight); - interface Props { + groupType: string; linkDoc: Doc; sourceDoc: Doc; destinationDoc: Doc; @@ -24,6 +23,8 @@ interface Props { @observer export class LinkBox extends React.Component { + @observable private _showMore: boolean = false; + @action toggleShowMore() { this._showMore = !this._showMore; } @undoBatch onFollowLink = async (e: React.PointerEvent): Promise => { @@ -36,19 +37,43 @@ export class LinkBox extends React.Component { this.props.showEditor(); } + 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 = []; + if (groupDoc) { + let mdDoc = Cast(groupDoc.metadata, Doc, new Doc); + let keys = LinkManager.Instance.groupMetadataKeys.get(this.props.groupType); + mdRows = keys!.map(key => { + return (
{key}: {StrCast(mdDoc[key])}
); + }); + } + + return (
{mdRows}
); + } + render() { + + let keys = LinkManager.Instance.groupMetadataKeys.get(this.props.groupType); + let canExpand = keys ? keys.length > 0 : false; + return ( -
-
+
+

{StrCast(this.props.destinationDoc.title)}

+
+ {canExpand ?
this.toggleShowMore()}> +
: <>} +
+
+
+ {this._showMore ? this.renderMetadata() : <>}
-
-
-
-
); } diff --git a/src/client/views/nodes/LinkMenu.tsx b/src/client/views/nodes/LinkMenu.tsx index 2fcbd25fa..aef56df67 100644 --- a/src/client/views/nodes/LinkMenu.tsx +++ b/src/client/views/nodes/LinkMenu.tsx @@ -19,11 +19,11 @@ export class LinkMenu extends React.Component { @observable private _editingLink?: Doc; - renderGroup = (group: Doc[]): Array => { + renderGroup = (group: Doc[], groupType: string): Array => { let source = this.props.docView.Document; return group.map(linkDoc => { let destination = LinkManager.Instance.findOppositeAnchor(linkDoc, source); - return this._editingLink = linkDoc)} />; + return this._editingLink = linkDoc)} />; }); } @@ -34,13 +34,13 @@ export class LinkMenu extends React.Component {

{groupType}:

- {this.renderGroup(group)} + {this.renderGroup(group, groupType)}
); }); - // source doc has no links + // if source doc has no links push message if (linkItems.length === 0) linkItems.push(

No links have been created yet. Drag the linking button onto another document to create a link.

); return linkItems; -- cgit v1.2.3-70-g09d2 From d3e734140c52e69f3d07f1ebe145e554a062addb Mon Sep 17 00:00:00 2001 From: Fawn Date: Fri, 14 Jun 2019 17:08:05 -0400 Subject: can drag follow button on link to move anchor --- src/client/views/nodes/LinkBox.tsx | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) (limited to 'src/client/views/nodes/LinkBox.tsx') diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx index 9fbe83126..09df1eac5 100644 --- a/src/client/views/nodes/LinkBox.tsx +++ b/src/client/views/nodes/LinkBox.tsx @@ -10,6 +10,8 @@ import { Doc } from '../../../new_fields/Doc'; import { StrCast, Cast } from '../../../new_fields/Types'; import { observable, action } from 'mobx'; import { LinkManager } from '../../util/LinkManager'; +import { DragLinksAsDocuments } from '../../util/DragManager'; +import { SelectionManager } from '../../util/SelectionManager'; library.add(faEye, faEdit, faTimes, faArrowRight, faChevronDown, faChevronUp); @@ -23,6 +25,7 @@ interface Props { @observer export class LinkBox extends React.Component { + private _drag = React.createRef(); @observable private _showMore: boolean = false; @action toggleShowMore() { this._showMore = !this._showMore; } @@ -54,6 +57,30 @@ export class LinkBox extends React.Component { return (
{mdRows}
); } + 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); + + DragLinksAsDocuments(this._drag.current, e.x, e.y, SelectionManager.SelectedDocuments()[0].props.Document); + } + e.stopPropagation(); + } + render() { let keys = LinkManager.Instance.groupMetadataKeys.get(this.props.groupType); @@ -68,13 +95,13 @@ export class LinkBox extends React.Component { {canExpand ?
this.toggleShowMore()}>
: <>}
-
+
{this._showMore ? this.renderMetadata() : <>}
-
+
); } } \ No newline at end of file -- cgit v1.2.3-70-g09d2 From 70eaadb2773ae78f99d856c4986b8f27ebbb36ad Mon Sep 17 00:00:00 2001 From: Fawn Date: Mon, 17 Jun 2019 11:32:38 -0400 Subject: single link can be dragged from link menu --- src/client/util/DragManager.ts | 12 ++++++++++++ src/client/views/nodes/LinkBox.tsx | 4 ++-- 2 files changed, 14 insertions(+), 2 deletions(-) (limited to 'src/client/views/nodes/LinkBox.tsx') diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index b32c25b0a..82d30e0e6 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -43,6 +43,18 @@ export function SetupDrag(_reference: React.RefObject, docFunc: () return onItemDown; } +export async function DragLinkAsDocument(dragEle: HTMLElement, x: number, y: number, linkDoc: Doc, sourceDoc: Doc) { + let draggeddoc = LinkManager.Instance.findOppositeAnchor(linkDoc, sourceDoc); + let moddrag = await Cast(draggeddoc.annotationOn, Doc); + let dragData = new DragManager.DocumentDragData(moddrag ? [moddrag] : [draggeddoc]); + DragManager.StartDocumentDrag([dragEle], 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[] = []; diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx index 09df1eac5..8d07547ed 100644 --- a/src/client/views/nodes/LinkBox.tsx +++ b/src/client/views/nodes/LinkBox.tsx @@ -10,7 +10,7 @@ import { Doc } from '../../../new_fields/Doc'; import { StrCast, Cast } from '../../../new_fields/Types'; import { observable, action } from 'mobx'; import { LinkManager } from '../../util/LinkManager'; -import { DragLinksAsDocuments } from '../../util/DragManager'; +import { DragLinksAsDocuments, DragLinkAsDocument } from '../../util/DragManager'; import { SelectionManager } from '../../util/SelectionManager'; library.add(faEye, faEdit, faTimes, faArrowRight, faChevronDown, faChevronUp); @@ -76,7 +76,7 @@ export class LinkBox extends React.Component { document.removeEventListener("pointermove", this.onLinkButtonMoved); document.removeEventListener("pointerup", this.onLinkButtonUp); - DragLinksAsDocuments(this._drag.current, e.x, e.y, SelectionManager.SelectedDocuments()[0].props.Document); + DragLinkAsDocument(this._drag.current, e.x, e.y, this.props.linkDoc, this.props.sourceDoc); } e.stopPropagation(); } -- cgit v1.2.3-70-g09d2 From c5e401cb0a7fec2279ceecbc8d1429dcdd2f04b9 Mon Sep 17 00:00:00 2001 From: Fawn Date: Wed, 19 Jun 2019 22:27:21 -0400 Subject: buttons on cut links functional except for when dragged from link menu --- src/client/documents/Documents.ts | 47 +++++++-- src/client/util/DragManager.ts | 7 ++ src/client/util/LinkManager.ts | 15 ++- .../CollectionFreeFormLinkView.scss | 59 ++++++++--- .../CollectionFreeFormLinkView.tsx | 2 +- .../CollectionFreeFormLinkWithProxyView.tsx | 109 +++++++++++++++++---- .../CollectionFreeFormLinksView.tsx | 42 +++++--- src/client/views/nodes/DocumentContentsView.tsx | 3 +- src/client/views/nodes/DocumentView.tsx | 14 ++- src/client/views/nodes/FieldView.tsx | 6 ++ src/client/views/nodes/LinkBox.scss | 76 -------------- src/client/views/nodes/LinkBox.tsx | 107 -------------------- src/client/views/nodes/LinkButtonBox.scss | 18 ++++ src/client/views/nodes/LinkButtonBox.tsx | 63 ++++++++++++ src/client/views/nodes/LinkMenu.tsx | 4 +- src/client/views/nodes/LinkMenuItem.scss | 76 ++++++++++++++ src/client/views/nodes/LinkMenuItem.tsx | 107 ++++++++++++++++++++ src/new_fields/LinkButtonField.ts | 35 +++++++ 18 files changed, 548 insertions(+), 242 deletions(-) delete mode 100644 src/client/views/nodes/LinkBox.scss delete mode 100644 src/client/views/nodes/LinkBox.tsx create mode 100644 src/client/views/nodes/LinkButtonBox.scss create mode 100644 src/client/views/nodes/LinkButtonBox.tsx create mode 100644 src/client/views/nodes/LinkMenuItem.scss create mode 100644 src/client/views/nodes/LinkMenuItem.tsx create mode 100644 src/new_fields/LinkButtonField.ts (limited to 'src/client/views/nodes/LinkBox.tsx') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 79ba433c8..4d4fa2645 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -26,7 +26,7 @@ import { OmitKeys } from "../../Utils"; import { ImageField, VideoField, AudioField, PdfField, WebField } from "../../new_fields/URLField"; import { HtmlField } from "../../new_fields/HtmlField"; import { List } from "../../new_fields/List"; -import { Cast, NumCast } from "../../new_fields/Types"; +import { Cast, NumCast, StrCast } from "../../new_fields/Types"; import { IconField } from "../../new_fields/IconField"; import { listSpec } from "../../new_fields/Schema"; import { DocServer } from "../DocServer"; @@ -36,6 +36,10 @@ import { DateField } from "../../new_fields/DateField"; import { UndoManager } from "../util/UndoManager"; import { RouteStore } from "../../server/RouteStore"; import { LinkManager } from "../util/LinkManager"; +import { LinkButtonBox } from "../views/nodes/LinkButtonBox"; +import { LinkButtonField, LinkButtonData } from "../../new_fields/LinkButtonField"; +import { DocumentManager } from "../util/DocumentManager"; +import { Id } from "../../new_fields/FieldSymbols"; var requestImageSize = require('request-image-size'); var path = require('path'); @@ -102,14 +106,32 @@ export namespace DocUtils { linkDocProto.context = targetContext; - let proxy1 = Docs.TextDocument({ width: 300, height: 100, borderRounding: 0 }); - let proxy1Proto = Doc.GetProto(proxy1); - let proxy2 = Docs.TextDocument({ width: 300, height: 100, borderRounding: 0 }); - let proxy2Proto = Doc.GetProto(proxy2); + let sourceViews = DocumentManager.Instance.getDocumentViews(source); + let targetViews = DocumentManager.Instance.getDocumentViews(target); + sourceViews.forEach(sv => { + targetViews.forEach(tv => { - linkDocProto.proxy1 = proxy1; // src: 1 targ: 2 - linkDocProto.proxy2 = proxy2; // src: 2 targ: 1 + // TODO: do only for when diff contexts + let proxy1 = Docs.LinkButtonDocument( + { sourceViewId: StrCast(sv.props.Document[Id]), targetViewId: StrCast(tv.props.Document[Id]) }, + { width: 200, height: 100, borderRounding: 0 }); + let proxy1Proto = Doc.GetProto(proxy1); + proxy1Proto.sourceViewId = StrCast(sv.props.Document[Id]); + proxy1Proto.targetViewId = StrCast(tv.props.Document[Id]); + proxy1Proto.isLinkButton = true; + let proxy2 = Docs.LinkButtonDocument( + { sourceViewId: StrCast(tv.props.Document[Id]), targetViewId: StrCast(sv.props.Document[Id]) }, + { width: 200, height: 100, borderRounding: 0 }); + let proxy2Proto = Doc.GetProto(proxy2); + proxy2Proto.sourceViewId = StrCast(tv.props.Document[Id]); + proxy2Proto.targetViewId = StrCast(sv.props.Document[Id]); + proxy2Proto.isLinkButton = true; + + LinkManager.Instance.linkProxies.push(proxy1); + LinkManager.Instance.linkProxies.push(proxy2); + }); + }); LinkManager.Instance.allLinks.push(linkDoc); @@ -131,6 +153,7 @@ export namespace Docs { let audioProto: Doc; let pdfProto: Doc; let iconProto: Doc; + let linkProto: Doc; const textProtoId = "textProto"; const histoProtoId = "histoProto"; const pdfProtoId = "pdfProto"; @@ -141,6 +164,7 @@ export namespace Docs { const videoProtoId = "videoProto"; const audioProtoId = "audioProto"; const iconProtoId = "iconProto"; + const linkProtoId = "linkProto"; export function initProtos(): Promise { return DocServer.GetRefFields([textProtoId, histoProtoId, collProtoId, imageProtoId, webProtoId, kvpProtoId, videoProtoId, audioProtoId, pdfProtoId, iconProtoId]).then(fields => { @@ -154,6 +178,7 @@ export namespace Docs { audioProto = fields[audioProtoId] as Doc || CreateAudioPrototype(); pdfProto = fields[pdfProtoId] as Doc || CreatePdfPrototype(); iconProto = fields[iconProtoId] as Doc || CreateIconPrototype(); + linkProto = fields[linkProtoId] as Doc || CreateLinkPrototype(); }); } @@ -186,6 +211,11 @@ export namespace Docs { { x: 0, y: 0, width: Number(MINIMIZED_ICON_SIZE), height: Number(MINIMIZED_ICON_SIZE) }); return iconProto; } + function CreateLinkPrototype(): Doc { + let linkProto = setupPrototypeOptions(linkProtoId, "LINK_PROTO", LinkButtonBox.LayoutString(), + { x: 0, y: 0, width: 300 }); + return linkProto; + } function CreateTextPrototype(): Doc { let textProto = setupPrototypeOptions(textProtoId, "TEXT_PROTO", FormattedTextBox.LayoutString(), { x: 0, y: 0, width: 300, backgroundColor: "#f1efeb" }); @@ -272,6 +302,9 @@ export namespace Docs { export function IconDocument(icon: string, options: DocumentOptions = {}) { return CreateInstance(iconProto, new IconField(icon), options); } + export function LinkButtonDocument(data: LinkButtonData, options: DocumentOptions = {}) { + return CreateInstance(linkProto, new LinkButtonField(data), options); + } export function PdfDocument(url: string, options: DocumentOptions = {}) { return CreateInstance(pdfProto, new PdfField(new URL(url)), options); } diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 4787ac40f..be00778e7 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -7,6 +7,8 @@ import * as globalCssVariables from "../views/globalCssVariables.scss"; import { LinkManager } from "./LinkManager"; import { URLField } from "../../new_fields/URLField"; import { SelectionManager } from "./SelectionManager"; +import { Docs } from "../documents/Documents"; +import { DocumentManager } from "./DocumentManager"; export type dropActionType = "alias" | "copy" | undefined; export function SetupDrag(_reference: React.RefObject, docFunc: () => Doc | Promise, moveFunc?: DragManager.MoveFunction, dropAction?: dropActionType, options?: any, dontHideOnDrop?: boolean) { @@ -223,6 +225,11 @@ export namespace DragManager { StartDrag([ele], dragData, downX, downY, options); } + export function StartLinkProxyDrag(ele: HTMLElement, dragData: DocumentDragData, downX: number, downY: number, options?: DragOptions) { + runInAction(() => StartDragFunctions.map(func => func())); + StartDrag([ele], dragData, downX, downY, options); + } + export let AbortDrag: () => void = emptyFunction; function StartDrag(eles: HTMLElement[], dragData: { [id: string]: any }, downX: number, downY: number, options?: DragOptions, finishDrag?: (dropData: { [id: string]: any }) => void) { diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts index 23ba9d2e4..544f2edda 100644 --- a/src/client/util/LinkManager.ts +++ b/src/client/util/LinkManager.ts @@ -1,4 +1,4 @@ -import { observable } from "mobx"; +import { observable, action } from "mobx"; import { StrCast, Cast } from "../../new_fields/Types"; import { Doc, DocListCast } from "../../new_fields/Doc"; import { listSpec } from "../../new_fields/Schema"; @@ -32,6 +32,7 @@ export class LinkManager { @observable public allLinks: Array = []; // list of link docs @observable public groupMetadataKeys: Map> = new Map(); // map of group type to list of its metadata keys; serves as a dictionary of groups to what kind of metadata it hodls + @observable public linkProxies: Array = []; // list of linkbutton docs - used to visualize link when an anchors are not in the same context // finds all links that contain the given anchor public findAllRelatedLinks(anchor: Doc): Array { @@ -134,4 +135,16 @@ export class LinkManager { } } + @action + public addLinkProxy(proxy: Doc) { + LinkManager.Instance.linkProxies.push(proxy); + } + + public findLinkProxy(sourceViewId: string, targetViewId: string): Doc | undefined { + let index = LinkManager.Instance.linkProxies.findIndex(p => { + return StrCast(p.sourceViewId) === sourceViewId && StrCast(p.targetViewId) === targetViewId; + }); + return index > -1 ? LinkManager.Instance.linkProxies[index] : undefined; + } + } \ No newline at end of file diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss index fc5212edd..d8d518147 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss @@ -1,19 +1,50 @@ -.collectionfreeformlinkview-linkLine { - stroke: black; +// .collectionfreeformlinkview-linkLine { +// stroke: black; +// transform: translate(10000px,10000px); +// opacity: 0.5; +// pointer-events: all; +// } +// .collectionfreeformlinkview-linkCircle { +// stroke: rgb(0,0,0); +// opacity: 0.5; +// transform: translate(10000px,10000px); +// pointer-events: all; +// cursor: pointer; +// } +// .collectionfreeformlinkview-linkText { +// stroke: rgb(0,0,0); +// opacity: 0.5; +// transform: translate(10000px,10000px); +// pointer-events: all; +// } + +.linkview-ele { transform: translate(10000px,10000px); - opacity: 0.5; pointer-events: all; + + &.linkview-line { + stroke: black; + stroke-width: 2px; + opacity: 0.5; + } } -.collectionfreeformlinkview-linkCircle { - stroke: rgb(0,0,0); - opacity: 0.5; - transform: translate(10000px,10000px); - pointer-events: all; + +.linkview-button { + width: 200px; + height: 100px; + border-radius: 5px; + padding: 10px; + position: relative; + background-color: black; cursor: pointer; -} -.collectionfreeformlinkview-linkText { - stroke: rgb(0,0,0); - opacity: 0.5; - transform: translate(10000px,10000px); - pointer-events: all; + + p { + width: calc(100% - 20px); + color: white; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + } + } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx index f995a35e3..13b5dc575 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx @@ -57,7 +57,7 @@ export class CollectionFreeFormLinkView extends React.Component - diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkWithProxyView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkWithProxyView.tsx index 81a00ba95..a4d122af2 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkWithProxyView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkWithProxyView.tsx @@ -7,13 +7,17 @@ import React = require("react"); import v5 = require("uuid/v5"); import { DocumentView } from "../../nodes/DocumentView"; import { Docs } from "../../../documents/Documents"; -import { observable } from "mobx"; +import { observable, action } from "mobx"; +import { CollectionDockingView } from "../CollectionDockingView"; +import { dropActionType, DragManager } from "../../../util/DragManager"; +import { emptyFunction } from "../../../../Utils"; +import { DocumentManager } from "../../../util/DocumentManager"; export interface CollectionFreeFormLinkViewProps { sourceView: DocumentView; targetView: DocumentView; proxyDoc: Doc; - addDocTab: (document: Doc, where: string) => void; + // addDocTab: (document: Doc, where: string) => void; } @observer @@ -21,37 +25,104 @@ export class CollectionFreeFormLinkWithProxyView extends React.Component(); + private _downX: number = 0; + private _downY: number = 0; + @observable _x: number = 0; + @observable _y: number = 0; + // @observable private _proxyDoc: Doc = Docs.TextDocument(); // used for positioning + + @action + componentDidMount() { + let a2 = this.props.proxyDoc; + this._x = NumCast(a2.x) + (BoolCast(a2.isMinimized, false) ? 5 : NumCast(a2.width) / NumCast(a2.zoomBasis, 1) / 2); + this._y = NumCast(a2.y) + (BoolCast(a2.isMinimized, false) ? 5 : NumCast(a2.height) / NumCast(a2.zoomBasis, 1) / 2); + } + followButton = (e: React.PointerEvent): void => { - // TODO: would be nicer to open docview in context e.stopPropagation(); - console.log("follow"); - this.props.addDocTab(this.props.targetView.props.Document, "onRight"); + let open = this.props.targetView.props.ContainingCollectionView ? this.props.targetView.props.ContainingCollectionView.props.Document : this.props.targetView.props.Document; + CollectionDockingView.Instance.AddRightSplit(open); + DocumentManager.Instance.jumpToDocument(this.props.targetView.props.Document, e.altKey); + } + + @action + setPosition(x: number, y: number) { + this._x = x; + this._y = y; + } + + startDragging(x: number, y: number) { + if (this._ref.current) { + let dragData = new DragManager.DocumentDragData([this.props.proxyDoc]); + + DragManager.StartLinkProxyDrag(this._ref.current, dragData, x, y, { + handlers: { + dragComplete: action(() => { + let a2 = this.props.proxyDoc; + let offset = NumCast(a2.width) / NumCast(a2.zoomBasis, 1) / 2; + let x = NumCast(a2.x);// + NumCast(a2.width) / NumCast(a2.zoomBasis, 1) / 2; + let y = NumCast(a2.y);// + NumCast(a2.height) / NumCast(a2.zoomBasis, 1) / 2; + this.setPosition(x, y); + + // this is a hack :'( theres prob a better way to make the input doc not render + let views = DocumentManager.Instance.getDocumentViews(this.props.proxyDoc); + views.forEach(dv => { + dv.props.removeDocument && dv.props.removeDocument(dv.props.Document); + }); + }), + }, + hideSource: true //? + }); + } + } + + onPointerDown = (e: React.PointerEvent): void => { + this._downX = e.clientX; + this._downY = e.clientY; + + e.stopPropagation(); + document.removeEventListener("pointermove", this.onPointerMove); + document.addEventListener("pointermove", this.onPointerMove); + document.removeEventListener("pointerup", this.onPointerUp); + document.addEventListener("pointerup", this.onPointerUp); + } + + onPointerMove = (e: PointerEvent): void => { + if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3) { + document.removeEventListener("pointermove", this.onPointerMove); + document.removeEventListener("pointerup", this.onPointerUp); + this.startDragging(this._downX, this._downY); + } + e.stopPropagation(); + e.preventDefault(); + } + onPointerUp = (e: PointerEvent): void => { + document.removeEventListener("pointermove", this.onPointerMove); + document.removeEventListener("pointerup", this.onPointerUp); } render() { let a1 = this.props.sourceView; - let a2 = this.props.proxyDoc; let x1 = NumCast(a1.Document.x) + (BoolCast(a1.Document.isMinimized, false) ? 5 : NumCast(a1.Document.width) / NumCast(a1.Document.zoomBasis, 1) / 2); let y1 = NumCast(a1.Document.y) + (BoolCast(a1.Document.isMinimized, false) ? 5 : NumCast(a1.Document.height) / NumCast(a1.Document.zoomBasis, 1) / 2); - let x2 = NumCast(a2.x) + (BoolCast(a2.isMinimized, false) ? 5 : NumCast(a2.width) / NumCast(a2.zoomBasis, 1) / 2); - let y2 = NumCast(a2.y) + (BoolCast(a2.isMinimized, false) ? 5 : NumCast(a2.height) / NumCast(a2.zoomBasis, 1) / 2); - - // let containing = ""; - // if (this.props.targetView.props.ContainingCollectionView) { - // containing = StrCast(this.props.targetView.props.ContainingCollectionView.props.Document.title); - // } + let context = this.props.targetView.props.ContainingCollectionView ? + (" in the context of " + StrCast(this.props.targetView.props.ContainingCollectionView.props.Document.title)) : ""; + let text = "link to " + StrCast(this.props.targetView.props.Document.title) + context; - // let text = "link to " + StrCast(this.props.targetView.props.Document.title) + (containing === "" ? "" : (" in the context of " + containing)); return ( <> - - {/* - {text} */} + x2={`${this._x}`} y2={`${this._y}`} /> + +
+

{text}

+
+
); } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx index eaef1f32a..bde68001b 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx @@ -1,4 +1,4 @@ -import { computed, IReactionDisposer, reaction } from "mobx"; +import { computed, IReactionDisposer, reaction, action } from "mobx"; import { observer } from "mobx-react"; import { Doc, DocListCast } from "../../../../new_fields/Doc"; import { Id } from "../../../../new_fields/FieldSymbols"; @@ -13,6 +13,8 @@ import { CollectionFreeFormLinkView } from "./CollectionFreeFormLinkView"; import React = require("react"); import { CollectionFreeFormLinkWithProxyView } from "./CollectionFreeFormLinkWithProxyView"; import { Docs } from "../../../documents/Documents"; +import { LinkButtonField } from "../../../../new_fields/LinkButtonField"; +import { LinkManager } from "../../../util/LinkManager"; @observer export class CollectionFreeFormLinksView extends React.Component { @@ -195,18 +197,32 @@ export class CollectionFreeFormLinksView extends React.Component); } else { - let proxyKey = Doc.AreProtosEqual(u.sourceView.Document, Cast(u.linkDoc.anchor1, Doc, new Doc)) ? "proxy1" : "proxy2"; - let proxy = Cast(u.linkDoc[proxyKey], Doc, new Doc); - - let context = u.targetView.props.ContainingCollectionView ? (" in the context of " + StrCast(u.targetView.props.ContainingCollectionView.props.Document.title)) : ""; - let text = proxyKey + " link to " + StrCast(u.targetView.props.Document.title) + context; - - let proxyProto = Doc.GetProto(proxy); - proxyProto.data = text; - - this.props.addDocument(proxy, false); - uniqueList.push(); + let proxy = LinkManager.Instance.findLinkProxy(StrCast(u.sourceView.props.Document[Id]), StrCast(u.targetView.props.Document[Id])); + if (!proxy) { + proxy = Docs.LinkButtonDocument( + { sourceViewId: StrCast(u.sourceView.props.Document[Id]), targetViewId: StrCast(u.targetView.props.Document[Id]) }, + { width: 200, height: 100, borderRounding: 0 }); + let proxy1Proto = Doc.GetProto(proxy); + proxy1Proto.sourceViewId = StrCast(u.sourceView.props.Document[Id]); + proxy1Proto.targetViewId = StrCast(u.targetView.props.Document[Id]); + proxy1Proto.isLinkButton = true; + + // LinkManager.Instance.linkProxies.push(proxy); + LinkManager.Instance.addLinkProxy(proxy); + } + uniqueList.push(); + + // let proxy = LinkManager.Instance.findLinkProxy(StrCast(u.sourceView.props.Document[Id]), StrCast(u.targetView.props.Document[Id])); + // if (proxy) { + // this.props.addDocument(proxy, false); + // uniqueList.push(); + // } + // let proxyKey = Doc.AreProtosEqual(u.sourceView.Document, Cast(u.linkDoc.anchor1, Doc, new Doc)) ? "proxy1" : "proxy2"; + // let proxy = Cast(u.linkDoc[proxyKey], Doc, new Doc); + // this.props.addDocument(proxy, false); + + // uniqueList.push(); } } }); diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index 02396c3af..940b00a90 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -12,6 +12,7 @@ import "./DocumentView.scss"; import { FormattedTextBox } from "./FormattedTextBox"; import { ImageBox } from "./ImageBox"; import { IconBox } from "./IconBox"; +import { LinkButtonBox } from "./LinkButtonBox"; import { KeyValueBox } from "./KeyValueBox"; import { PDFBox } from "./PDFBox"; import { VideoBox } from "./VideoBox"; @@ -103,7 +104,7 @@ export class DocumentContentsView extends React.Component(Docu var scaling = this.props.ContentScaling(); var nativeHeight = this.nativeHeight > 0 ? `${this.nativeHeight}px` : "100%"; var nativeWidth = this.nativeWidth > 0 ? `${this.nativeWidth}px` : "100%"; + + // // for linkbutton docs + // let isLinkButton = BoolCast(this.props.Document.isLinkButton); + // let activeDvs = DocumentManager.Instance.DocumentViews.filter(dv => dv.isSelected() || BoolCast(dv.props.Document.libraryBrush, false)); + // let display = isLinkButton ? activeDvs.reduce((found, dv) => { + // let matchSv = this.props.Document.sourceViewId === StrCast(dv.props.Document[Id]); + // let matchTv = this.props.Document.targetViewId === StrCast(dv.props.Document[Id]); + // let match = matchSv || matchTv; + // return match || found; + // }, false) : true; + return (
(Docu background: this.Document.backgroundColor || "", width: nativeWidth, height: nativeHeight, - transform: `scale(${scaling}, ${scaling})` + transform: `scale(${scaling}, ${scaling})`, + // display: display ? "block" : "none" }} onDrop={this.onDrop} onContextMenu={this.onContextMenu} onPointerDown={this.onPointerDown} onClick={this.onClick} diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx index 5a83de8e3..6fecb34d7 100644 --- a/src/client/views/nodes/FieldView.tsx +++ b/src/client/views/nodes/FieldView.tsx @@ -18,6 +18,8 @@ import { FormattedTextBox } from "./FormattedTextBox"; import { IconBox } from "./IconBox"; import { ImageBox } from "./ImageBox"; import { VideoBox } from "./VideoBox"; +import { LinkButtonBox } from "./LinkButtonBox"; +import { LinkButtonField } from "../../../new_fields/LinkButtonField"; // @@ -49,6 +51,7 @@ export interface FieldViewProps { @observer export class FieldView extends React.Component { public static LayoutString(fieldType: { name: string }, fieldStr: string = "data") { + console.log("LAYOUT STRING", fieldType.name, fieldStr); return `<${fieldType.name} {...props} fieldKey={"${fieldStr}"} />`; } @@ -74,6 +77,9 @@ export class FieldView extends React.Component { else if (field instanceof IconField) { return ; } + else if (field instanceof LinkButtonField) { + return ; + } else if (field instanceof VideoField) { return ; } diff --git a/src/client/views/nodes/LinkBox.scss b/src/client/views/nodes/LinkBox.scss deleted file mode 100644 index 77462f611..000000000 --- a/src/client/views/nodes/LinkBox.scss +++ /dev/null @@ -1,76 +0,0 @@ -@import "../globalCssVariables"; -.linkMenu-item { - border-top: 0.5px solid $main-accent; - padding: 6px; - position: relative; - display: flex; - font-size: 12px; - - .linkMenu-item-content { - width: 100%; - } - - .link-metadata { - margin-top: 6px; - padding: 3px; - border-top: 0.5px solid $light-color-secondary; - .link-metadata-row { - margin-left: 6px; - } - } - - &:last-child { - border-bottom: 0.5px solid $main-accent; - } - &:hover { - .linkMenu-item-buttons { - display: flex; - } - .linkMenu-item-content { - &.expand-two { - width: calc(100% - 46px); - } - &.expand-three { - width: calc(100% - 78px); - } - } - } -} - -.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; - } - } -} \ 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 8d07547ed..000000000 --- a/src/client/views/nodes/LinkBox.tsx +++ /dev/null @@ -1,107 +0,0 @@ -import { library } from '@fortawesome/fontawesome-svg-core'; -import { faEdit, faEye, faTimes, faArrowRight, faChevronDown, faChevronUp } from '@fortawesome/free-solid-svg-icons'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { observer } from "mobx-react"; -import { DocumentManager } from "../../util/DocumentManager"; -import { undoBatch } from "../../util/UndoManager"; -import './LinkBox.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 { DragLinksAsDocuments, DragLinkAsDocument } from '../../util/DragManager'; -import { SelectionManager } from '../../util/SelectionManager'; -library.add(faEye, faEdit, faTimes, faArrowRight, faChevronDown, faChevronUp); - - -interface Props { - groupType: string; - linkDoc: Doc; - sourceDoc: Doc; - destinationDoc: Doc; - showEditor: () => void; -} - -@observer -export class LinkBox extends React.Component { - private _drag = React.createRef(); - @observable private _showMore: boolean = false; - @action toggleShowMore() { this._showMore = !this._showMore; } - - @undoBatch - onFollowLink = async (e: React.PointerEvent): Promise => { - e.stopPropagation(); - DocumentManager.Instance.jumpToDocument(this.props.destinationDoc, e.altKey); - } - - onEdit = (e: React.PointerEvent): void => { - e.stopPropagation(); - this.props.showEditor(); - } - - 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 = []; - if (groupDoc) { - let mdDoc = Cast(groupDoc.metadata, Doc, new Doc); - let keys = LinkManager.Instance.groupMetadataKeys.get(this.props.groupType); - mdRows = keys!.map(key => { - return (
{key}: {StrCast(mdDoc[key])}
); - }); - } - - return (
{mdRows}
); - } - - 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.groupMetadataKeys.get(this.props.groupType); - let canExpand = keys ? keys.length > 0 : false; - - return ( -
-
-
-

{StrCast(this.props.destinationDoc.title)}

-
- {canExpand ?
this.toggleShowMore()}> -
: <>} -
-
-
-
- {this._showMore ? this.renderMetadata() : <>} -
- -
- ); - } -} \ No newline at end of file diff --git a/src/client/views/nodes/LinkButtonBox.scss b/src/client/views/nodes/LinkButtonBox.scss new file mode 100644 index 000000000..24bfd2c9f --- /dev/null +++ b/src/client/views/nodes/LinkButtonBox.scss @@ -0,0 +1,18 @@ +.linkBox-cont { + width: 200px; + height: 100px; + background-color: black; + text-align: center; + color: white; + padding: 10px; + border-radius: 5px; + position: relative; + + .linkBox-cont-wrapper { + width: calc(100% - 20px); + position: absolute; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + } +} \ No newline at end of file diff --git a/src/client/views/nodes/LinkButtonBox.tsx b/src/client/views/nodes/LinkButtonBox.tsx new file mode 100644 index 000000000..8a7c1ed8b --- /dev/null +++ b/src/client/views/nodes/LinkButtonBox.tsx @@ -0,0 +1,63 @@ +import React = require("react"); +import { library } from '@fortawesome/fontawesome-svg-core'; +import { faCaretUp, faFilePdf, faFilm, faImage, faObjectGroup, faStickyNote } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { computed, observable, runInAction } from "mobx"; +import { observer } from "mobx-react"; +import { FieldView, FieldViewProps } from './FieldView'; +import "./LinkButtonBox.scss"; +import { DocumentView } from "./DocumentView"; +import { Doc } from "../../../new_fields/Doc"; +import { LinkButtonField } from "../../../new_fields/LinkButtonField"; +import { Cast, StrCast, BoolCast } from "../../../new_fields/Types"; +import { CollectionDockingView } from "../collections/CollectionDockingView"; +import { DocumentManager } from "../../util/DocumentManager"; +import { Id } from "../../../new_fields/FieldSymbols"; + +library.add(faCaretUp); +library.add(faObjectGroup); +library.add(faStickyNote); +library.add(faFilePdf); +library.add(faFilm); + +@observer +export class LinkButtonBox extends React.Component { + public static LayoutString() { return FieldView.LayoutString(LinkButtonBox); } + + followLink = (): void => { + console.log("follow link???"); + let field = Cast(this.props.Document[this.props.fieldKey], LinkButtonField, new LinkButtonField({ sourceViewId: "-1", targetViewId: "-1" })); + let targetView = DocumentManager.Instance.getDocumentViewById(field.data.targetViewId); + if (targetView && targetView.props.ContainingCollectionView) { + CollectionDockingView.Instance.AddRightSplit(targetView.props.ContainingCollectionView.props.Document); + } + } + + render() { + + let field = Cast(this.props.Document[this.props.fieldKey], LinkButtonField, new LinkButtonField({ sourceViewId: "-1", targetViewId: "-1" })); + let targetView = DocumentManager.Instance.getDocumentViewById(field.data.targetViewId); + + let text = "Could not find link"; + if (targetView) { + let context = targetView.props.ContainingCollectionView ? (" in the context of " + StrCast(targetView.props.ContainingCollectionView.props.Document.title)) : ""; + text = "Link to " + StrCast(targetView.props.Document.title) + context; + } + + let activeDvs = DocumentManager.Instance.DocumentViews.filter(dv => dv.isSelected() || BoolCast(dv.props.Document.libraryBrush, false)); + let display = activeDvs.reduce((found, dv) => { + let matchSv = field.data.sourceViewId === StrCast(dv.props.Document[Id]); + let matchTv = field.data.targetViewId === StrCast(dv.props.Document[Id]); + let match = matchSv || matchTv; + return match || found; + }, false); + + return ( +
+
+

{text}

+
+
+ ); + } +} \ No newline at end of file diff --git a/src/client/views/nodes/LinkMenu.tsx b/src/client/views/nodes/LinkMenu.tsx index aef56df67..7e4c15312 100644 --- a/src/client/views/nodes/LinkMenu.tsx +++ b/src/client/views/nodes/LinkMenu.tsx @@ -1,7 +1,7 @@ import { action, observable } from "mobx"; import { observer } from "mobx-react"; import { DocumentView } from "./DocumentView"; -import { LinkBox } from "./LinkBox"; +import { LinkMenuItem } from "./LinkMenuItem"; import { LinkEditor } from "./LinkEditor"; import './LinkMenu.scss'; import React = require("react"); @@ -23,7 +23,7 @@ export class LinkMenu extends React.Component { let source = this.props.docView.Document; return group.map(linkDoc => { let destination = LinkManager.Instance.findOppositeAnchor(linkDoc, source); - return this._editingLink = linkDoc)} />; + return this._editingLink = linkDoc)} />; }); } diff --git a/src/client/views/nodes/LinkMenuItem.scss b/src/client/views/nodes/LinkMenuItem.scss new file mode 100644 index 000000000..77462f611 --- /dev/null +++ b/src/client/views/nodes/LinkMenuItem.scss @@ -0,0 +1,76 @@ +@import "../globalCssVariables"; +.linkMenu-item { + border-top: 0.5px solid $main-accent; + padding: 6px; + position: relative; + display: flex; + font-size: 12px; + + .linkMenu-item-content { + width: 100%; + } + + .link-metadata { + margin-top: 6px; + padding: 3px; + border-top: 0.5px solid $light-color-secondary; + .link-metadata-row { + margin-left: 6px; + } + } + + &:last-child { + border-bottom: 0.5px solid $main-accent; + } + &:hover { + .linkMenu-item-buttons { + display: flex; + } + .linkMenu-item-content { + &.expand-two { + width: calc(100% - 46px); + } + &.expand-three { + width: calc(100% - 78px); + } + } + } +} + +.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; + } + } +} \ 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..c68365584 --- /dev/null +++ b/src/client/views/nodes/LinkMenuItem.tsx @@ -0,0 +1,107 @@ +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 './LinkMenuItem.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 { DragLinksAsDocuments, DragLinkAsDocument } from '../../util/DragManager'; +import { SelectionManager } from '../../util/SelectionManager'; +library.add(faEye, faEdit, faTimes, faArrowRight, faChevronDown, faChevronUp); + + +interface LinkMenuItemProps { + groupType: string; + linkDoc: Doc; + sourceDoc: Doc; + destinationDoc: Doc; + showEditor: () => void; +} + +@observer +export class LinkMenuItem extends React.Component { + private _drag = React.createRef(); + @observable private _showMore: boolean = false; + @action toggleShowMore() { this._showMore = !this._showMore; } + + @undoBatch + onFollowLink = async (e: React.PointerEvent): Promise => { + e.stopPropagation(); + DocumentManager.Instance.jumpToDocument(this.props.destinationDoc, e.altKey); + } + + onEdit = (e: React.PointerEvent): void => { + e.stopPropagation(); + this.props.showEditor(); + } + + 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 = []; + if (groupDoc) { + let mdDoc = Cast(groupDoc.metadata, Doc, new Doc); + let keys = LinkManager.Instance.groupMetadataKeys.get(this.props.groupType); + mdRows = keys!.map(key => { + return (
{key}: {StrCast(mdDoc[key])}
); + }); + } + + return (
{mdRows}
); + } + + 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.groupMetadataKeys.get(this.props.groupType); + let canExpand = keys ? keys.length > 0 : false; + + return ( +
+
+
+

{StrCast(this.props.destinationDoc.title)}

+
+ {canExpand ?
this.toggleShowMore()}> +
: <>} +
+
+
+
+ {this._showMore ? this.renderMetadata() : <>} +
+ +
+ ); + } +} \ No newline at end of file diff --git a/src/new_fields/LinkButtonField.ts b/src/new_fields/LinkButtonField.ts new file mode 100644 index 000000000..92e1ed922 --- /dev/null +++ b/src/new_fields/LinkButtonField.ts @@ -0,0 +1,35 @@ +import { Deserializable } from "../client/util/SerializationHelper"; +import { serializable, primitive, createSimpleSchema, object } from "serializr"; +import { ObjectField } from "./ObjectField"; +import { Copy, ToScriptString } from "./FieldSymbols"; +import { Doc } from "./Doc"; +import { DocumentView } from "../client/views/nodes/DocumentView"; + +export type LinkButtonData = { + sourceViewId: string, + targetViewId: string +}; + +const LinkButtonSchema = createSimpleSchema({ + sourceViewId: true, + targetViewId: true +}); + +@Deserializable("linkButton") +export class LinkButtonField extends ObjectField { + @serializable(object(LinkButtonSchema)) + readonly data: LinkButtonData; + + constructor(data: LinkButtonData) { + super(); + this.data = data; + } + + [Copy]() { + return new LinkButtonField(this.data); + } + + [ToScriptString]() { + return "invalid"; + } +} -- cgit v1.2.3-70-g09d2