diff options
author | Fawn <fangrui_tong@brown.edu> | 2019-06-10 20:02:32 -0400 |
---|---|---|
committer | Fawn <fangrui_tong@brown.edu> | 2019-06-10 20:02:32 -0400 |
commit | d429898b5337331450e46c223380e5d00967b2d6 (patch) | |
tree | a2ce1aacdb258a152c55a8800fa1e884e6ba8b94 /src | |
parent | 70996f3f19d408e819e081ed03bd7ccf0de44503 (diff) |
added set up for metadata on links
Diffstat (limited to 'src')
-rw-r--r-- | src/client/documents/Documents.ts | 1 | ||||
-rw-r--r-- | src/client/util/DocumentManager.ts | 5 | ||||
-rw-r--r-- | src/client/util/DragManager.ts | 4 | ||||
-rw-r--r-- | src/client/util/LinkManager.ts | 81 | ||||
-rw-r--r-- | src/client/views/nodes/LinkEditor.scss | 42 | ||||
-rw-r--r-- | src/client/views/nodes/LinkEditor.tsx | 229 | ||||
-rw-r--r-- | src/client/views/nodes/LinkMenu.tsx | 34 |
7 files changed, 313 insertions, 83 deletions
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 5ec19f987..731490d97 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -91,6 +91,7 @@ export namespace DocUtils { UndoManager.RunInBatch(() => { let groupDoc = Docs.TextDocument(); groupDoc.proto!.type = "*"; + groupDoc.proto!.metadata = new List<Doc>([]); let linkDoc = Docs.TextDocument({ width: 100, height: 30, borderRounding: -1 }); //let linkDoc = new Doc; diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 52f0fe3f6..2acbb3ad4 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -9,7 +9,7 @@ import { CollectionView } from '../views/collections/CollectionView'; import { CollectionPDFView } from '../views/collections/CollectionPDFView'; import { CollectionVideoView } from '../views/collections/CollectionVideoView'; import { Id } from '../../new_fields/FieldSymbols'; -import { LinkManager } from './LinkManager'; +import { LinkManager, LinkUtils } from './LinkManager'; export class DocumentManager { @@ -92,7 +92,8 @@ export class DocumentManager { pairs.push(...linksList.reduce((pairs, link) => { if (link) { // let destination = (link["linkedTo"] === dv.props.Document) ? link["linkedFrom"] : link["linkedTo"]; - let destination = LinkManager.Instance.findOppositeAnchor(link, dv.props.Document); + + let destination = LinkUtils.findOppositeAnchor(link, dv.props.Document); let linkToDoc = FieldValue(Cast(destination, Doc)); // let linkToDoc = FieldValue(Cast(link.linkedTo, Doc)); if (linkToDoc) { diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 809368aff..9ac421fbf 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -4,7 +4,7 @@ import { Cast } from "../../new_fields/Types"; import { emptyFunction } from "../../Utils"; import { CollectionDockingView } from "../views/collections/CollectionDockingView"; import * as globalCssVariables from "../views/globalCssVariables.scss"; -import { LinkManager } from "./LinkManager"; +import { LinkManager, LinkUtils } from "./LinkManager"; export type dropActionType = "alias" | "copy" | undefined; export function SetupDrag(_reference: React.RefObject<HTMLElement>, docFunc: () => Doc | Promise<Doc>, moveFunc?: DragManager.MoveFunction, dropAction?: dropActionType) { @@ -48,7 +48,7 @@ export async function DragLinksAsDocuments(dragEle: HTMLElement, x: number, y: n // let linkFromDocs = await DocListCastAsync(srcTarg.linkedFromDocs); let linkDocs = LinkManager.Instance.findAllRelatedLinks(srcTarg); if (linkDocs) draggedDocs = linkDocs.map(link => { - return LinkManager.Instance.findOppositeAnchor(link, sourceDoc); + return LinkUtils.findOppositeAnchor(link, sourceDoc); }); diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts index a6d649395..5e8e0475b 100644 --- a/src/client/util/LinkManager.ts +++ b/src/client/util/LinkManager.ts @@ -10,6 +10,43 @@ import { Doc } from "../../new_fields/Doc"; import { listSpec } from "../../new_fields/Schema"; import { List } from "../../new_fields/List"; +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 getAnchorGroups(link: Doc, anchor: Doc): Doc[] { + // 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), []); + // } + + // if (groups instanceof Doc[]) { + // return groups; + // } else { + // return []; + // } + // // if (Doc.AreProtosEqual(anchor, Cast(link.anchor1, Doc, new Doc))) { + // // returnCast(link.anchor1Groups, listSpec(Doc), []); + // // } else { + // // return Cast(link.anchor2Groups, listSpec(Doc), []); + // // } + // } + + export function setAnchorGroups(link: Doc, anchor: Doc, groups: Doc[]) { + if (Doc.AreProtosEqual(anchor, Cast(link.anchor1, Doc, new Doc))) { + link.anchor1Groups = new List<Doc>(groups); + } else { + link.anchor2Groups = new List<Doc>(groups); + } + } +} export class LinkManager { private static _instance: LinkManager; @@ -19,31 +56,30 @@ export class LinkManager { private constructor() { } - @observable - public allLinks: Array<Doc> = []; + @observable public allLinks: Array<Doc> = []; + @observable public allGroups: Map<string, Doc> = new Map(); - public findAllRelatedLinks(source: Doc): Array<Doc> { - let related = LinkManager.Instance.allLinks.filter( - link => Doc.AreProtosEqual(source, Cast(link.anchor1, Doc, new Doc)) || Doc.AreProtosEqual(source, Cast(link.anchor2, Doc, new Doc))); - return related; + public findAllRelatedLinks(anchor: Doc): Array<Doc> { + return LinkManager.Instance.allLinks.filter( + link => Doc.AreProtosEqual(anchor, Cast(link.anchor1, Doc, new Doc)) || Doc.AreProtosEqual(anchor, Cast(link.anchor2, Doc, new Doc))); } - public findRelatedGroupedLinks(source: Doc): Map<string, Array<Doc>> { - let related = this.findAllRelatedLinks(source); + public findRelatedGroupedLinks(anchor: Doc): Map<string, Array<Doc>> { + let related = this.findAllRelatedLinks(anchor); - let categories = new Map(); + let anchorGroups = new Map(); related.forEach(link => { // get groups of given doc - let groups = (Doc.AreProtosEqual(source, Cast(link.anchor1, Doc, new Doc))) ? Cast(link.anchor1Groups, listSpec(Doc), []) : Cast(link.anchor2Groups, listSpec(Doc), []); - if (groups) { - if (groups.length > 0) { - groups.forEach(groupDoc => { + 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 = categories.get(groupType); // TODO: clean this up lol + let group = anchorGroups.get(groupType); // TODO: clean this up lol if (group) group.push(link); else group = [link]; - categories.set(groupType, group); + anchorGroups.set(groupType, group); } else { // promise doc } @@ -52,10 +88,10 @@ export class LinkManager { } else { // if link is in no groups then put it in default group - let group = categories.get("*"); + let group = anchorGroups.get("*"); if (group) group.push(link); else group = [link]; - categories.set("*", group); + anchorGroups.set("*", group); } } @@ -66,16 +102,11 @@ export class LinkManager { // else group = [link]; // categories.set(link.linkTags, group); }) - return categories; + return anchorGroups; } - public findOppositeAnchor(link: Doc, source: Doc): Doc { - if (Doc.AreProtosEqual(source, Cast(link.anchor1, Doc, new Doc))) { - return Cast(link.anchor2, Doc, new Doc); - } else { - return Cast(link.anchor1, Doc, new Doc); - } - } + + // public findAnchorTags(link: Doc, source: Doc): Doc[] { // if (source) diff --git a/src/client/views/nodes/LinkEditor.scss b/src/client/views/nodes/LinkEditor.scss index 9629585d7..52ed26442 100644 --- a/src/client/views/nodes/LinkEditor.scss +++ b/src/client/views/nodes/LinkEditor.scss @@ -39,4 +39,46 @@ .save-button:hover { background: $main-accent; cursor: pointer; +} + +.linkEditor { + font-size: 12px; // TODO + + .linkEditor-back { + // background-color: $dark-color; + // color: $light-color; + margin-bottom: 6px; + } + + .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; + .linkEditor-group-row-label { + margin-right: 6px; + } + } + .linkEditor-metadata-row { + display: flex; + } }
\ No newline at end of file diff --git a/src/client/views/nodes/LinkEditor.tsx b/src/client/views/nodes/LinkEditor.tsx index 2ab8c3460..14fdd26df 100644 --- a/src/client/views/nodes/LinkEditor.tsx +++ b/src/client/views/nodes/LinkEditor.tsx @@ -10,17 +10,57 @@ 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 } from "../../util/LinkManager"; +import { LinkManager, LinkUtils } from "../../util/LinkManager"; +import { Docs } from "../../documents/Documents"; +import { Utils } from "../../../Utils"; +import { faArrowLeft, faEllipsisV } from '@fortawesome/free-solid-svg-icons'; +import { library } from "@fortawesome/fontawesome-svg-core"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { string } from "prop-types"; -interface Props { +library.add(faArrowLeft); +library.add(faEllipsisV); + +interface LinkEditorProps { sourceDoc: Doc; linkDoc: Doc; - groups: Map<number, Doc>; + groups: Map<string, Doc>; + metadata: Map<string, Map<string, Doc>>; showLinks: () => void; } @observer -export class LinkEditor extends React.Component<Props> { +export class LinkEditor extends React.Component<LinkEditorProps> { + + // @observable private _groups: Map<string, Doc> = new Map(); + // @observable private _metadata: Map<string, Map<string, Doc>> = new Map(); + + // // componentDidMount() { + + // // } + // constructor(props: LinkEditorProps) { + // super(props); + + // let groups = new Map<string, Doc>(); + // let metadata: Map<string, Map<string, Doc>> = new Map(); + // let groupList = (Doc.AreProtosEqual(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<string, Doc>(); + // 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); + // } + // }) + // } // @observable private _title: string = StrCast(this.props.linkDoc.title); // @observable private _description: string = StrCast(this.props.linkDoc.linkDescription); @@ -51,68 +91,175 @@ export class LinkEditor extends React.Component<Props> { // } @action - editGroup(groupId: number, value: string) { + editGroup(groupId: string, value: string) { let linkDoc = this.props.linkDoc.proto ? this.props.linkDoc.proto : this.props.linkDoc; let groupDoc = this.props.groups.get(groupId); if (groupDoc) { groupDoc.proto!.type = value; - if (Doc.AreProtosEqual(this.props.sourceDoc, Cast(linkDoc.anchor1, Doc, new Doc))) { - // let groups = Cast(linkDoc.anchor1Groups, listSpec(Doc), []); - // groups.push(groupDoc); - linkDoc.anchor1Groups = new List<Doc>([groupDoc]); - - } else { - linkDoc.anchor2Groups = new List<Doc>([groupDoc]); - } + 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<Doc>([]); + this.props.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.props.groups.values())); } - renderGroup(groupId: number, groupDoc: Doc) { + renderGroup(groupId: string, groupDoc: Doc) { + // let metadata = this.props.metadata.get(groupId); + // if (!metadata) { + // metadata = new Map<string, Doc>(); + // } return ( - <div> - <p>type:</p> - <input type="text" value={StrCast(groupDoc.proto!.type)} onChange={e => this.editGroup(groupId, e.target.value)}></input> + // <div key={groupId} className="linkEditor-group"> + <div key={groupId} className="linkEditor-group-row"> + <p className="linkEditor-group-row-label">type:</p> + <input key={groupId + "-type"} type="text" value={StrCast(groupDoc.proto!.type)} onChange={e => this.editGroup(groupId, e.target.value)}></input> </div> + // {/* {this.renderMetadata(groupId)} */ } + // {/* <button onPointerDown={() => this.addMetadata(groupId)}>+</button> */ } + // // </div> ) } + @action + addMetadata = (groupId: string): void => { + // create new metadata doc + let mdDoc = Docs.TextDocument(); + mdDoc.proto!.title = ""; + mdDoc.proto!.value = ""; + + // append to map + let mdMap = this.props.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.props.metadata.set(groupId, mdMap); + + // add to internatal representation of group + let groupDoc = this.props.groups.get(groupId); + if (groupDoc) { + groupDoc.proto!.metadata = new List<Doc>(Array.from(mdMap.values())); + this.props.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.props.groups.values())); + + } + + // @action + // addMetadata = (groupId: string): void => { + // let groupDoc = this.props.groups.get(groupId); + // if (groupDoc) { + // // create new document for metadata row + // let metadata = Cast(groupDoc.metadata, listSpec(Doc), []); + // let metadataDoc = Docs.TextDocument(); + // metadataDoc.proto!.title = ""; + // metadataDoc.proto!.value = ""; + // let metadataMap = new Map<string, + + // this.props.metadata.set(groupId, new Map) + + // groupDoc.proto!.metadata = new List<Doc>([metadataDoc]); // TODO: append to metadata + // } + // } + + // @action + // editMetadataTitle = (groupId: string, mdId: string, value: string) => { + // let group = this.props.metadata.get(groupId); + // if (group) { + // let mdDoc = group.get(mdId); + // if (mdDoc) { + // mdDoc.proto!.title = value; + // } + // } + // } + + // @action + // editMetadataValue = (groupId: string, mdId: string, value: string) => { + // let group = this.props.metadata.get(groupId); + // if (group) { + // let mdDoc = group.get(mdId); + // if (mdDoc) { + // mdDoc.proto!.value = value; + // } + // } + // } + + @action + editMetadataTitle(groupId: string, mdId: string, value: string) { + + } + + @action + editMetadataValue(groupId: string, mdId: string, value: string) { + + } + + renderMetadata(groupId: string) { + let metadata: Array<JSX.Element> = []; + let metadataMap = this.props.metadata.get(groupId); + if (metadataMap) { + metadataMap.forEach((mdDoc, mdId) => { + metadata.push( + <div key={mdId} className="linkEditor-metadata-row"> + <input type="text" value={StrCast(mdDoc.proto!.title)} onChange={e => this.editMetadataTitle(groupId, mdId, e.target.value)}></input> + : + <input type="text" value={StrCast(mdDoc.proto!.value)} onChange={e => this.editMetadataValue(groupId, mdId, e.target.value)}></input> + </div> + ) + }) + } + + return metadata; + + // let metadataList: Array<JSX.Element> = []; + // metadata.forEach((mdDoc, mdId) => { + // metadataList.push( + // <div key={mdId} className="linkEditor-metadata-row"> + // <input type="text" value={StrCast(mdDoc.proto!.title)} onChange={e => this.editMetadataTitle(groupId, mdId, e.target.value)}></input>: + // <input type="text" value={StrCast(mdDoc.proto!.value)} onChange={e => this.editMetadataValue(groupId, mdId, e.target.value)}></input> + // </div> + // ) + // }) + } + renderGroups() { let groups: Array<JSX.Element> = []; this.props.groups.forEach((groupDoc, groupId) => { - groups.push( - <div> - {this.renderGroup(groupId, groupDoc)} - </div> - ) + groups.push(this.renderGroup(groupId, groupDoc)) }); return groups; } - onSaveButtonPressed = (e: React.PointerEvent): void => { - e.stopPropagation(); - - // let linkDoc = this.props.linkDoc.proto ? this.props.linkDoc.proto : this.props.linkDoc; - // // linkDoc.title = this._title; - // // linkDoc.linkDescription = this._description; - - this.props.showLinks(); - } - render() { - let destination = LinkManager.Instance.findOppositeAnchor(this.props.linkDoc, this.props.sourceDoc); + let destination = LinkUtils.findOppositeAnchor(this.props.linkDoc, this.props.sourceDoc); return ( - <div className="edit-container"> - <p>linked to: {destination.proto!.title}</p> - <b>Groups:</b> + <div className="linkEditor"> + <button className="linkEditor-back" onPointerDown={() => this.props.showLinks()}><FontAwesomeIcon icon="arrow-left" size="sm" /></button> + <p className="linkEditor-linkedTo">editing link to: <b>{destination.proto!.title}</b></p> + <div className="linkEditor-groupsLabel"> + <b>Groups:</b> + <button onClick={this.addGroup} title="Add Group">+</button> + </div> {this.renderGroups()} - {/* <input onChange={this.onTitleChanged} className="name-input" type="text" value={this._title} placeholder="Name . . ."></input> - <textarea onChange={this.onDescriptionChanged} className="description-input" value={this._description} placeholder="Description . . ."></textarea> */} - {/* {this.renderTags()} - <button onClick={this.addTag}>+</button> */} - <div className="save-button" onPointerDown={this.onSaveButtonPressed}>SAVE</div> </div> ); diff --git a/src/client/views/nodes/LinkMenu.tsx b/src/client/views/nodes/LinkMenu.tsx index affe35e2a..ab478feae 100644 --- a/src/client/views/nodes/LinkMenu.tsx +++ b/src/client/views/nodes/LinkMenu.tsx @@ -8,9 +8,10 @@ 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 } from "../../util/LinkManager"; -import { number } from "prop-types"; +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; @@ -34,12 +35,11 @@ export class LinkMenu extends React.Component<Props> { renderLinkGroupItems(links: Doc[]) { let source = this.props.docView.Document; return links.map(link => { - // let destination = (link["linkedTo"] === source) ? link["linkedFrom"] : link["linkedTo"]; - let destination = LinkManager.Instance.findOppositeAnchor(link, source); + let destination = LinkUtils.findOppositeAnchor(link, source); let doc = FieldValue(Cast(destination, Doc)); if (doc) { console.log(doc[Id] + source[Id], "source is", source[Id]); - return <LinkBox key={doc[Id] + source[Id]} linkDoc={link} linkName={"link"} pairedDoc={doc} showEditor={action(() => this._editingLink = link)} type={""} />; + return <LinkBox key={doc[Id] + source[Id]} linkDoc={link} linkName={StrCast(destination.title)} pairedDoc={doc} showEditor={action(() => this._editingLink = link)} type={""} />; } }); } @@ -79,20 +79,28 @@ export class LinkMenu extends React.Component<Props> { </div> ); } else { - let counter = 0; - let groups = new Map<number, Doc>(); + let groups = new Map<string, Doc>(); + let metadata: Map<string, Map<string, Doc>> = 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(group => { - if (group instanceof Doc) { - console.log(counter); - groups.set(counter, group); - counter++; + groupList.forEach(groupDoc => { + if (groupDoc instanceof Doc) { + let id = Utils.GenerateGuid(); + groups.set(id, groupDoc); + + let metadataMap = new Map<string, Doc>(); + 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 ( - <LinkEditor groups={groups} sourceDoc={this.props.docView.props.Document} linkDoc={this._editingLink} showLinks={action(() => this._editingLink = undefined)}></LinkEditor> + <LinkEditor groups={groups} metadata={metadata} sourceDoc={this.props.docView.props.Document} linkDoc={this._editingLink} showLinks={action(() => this._editingLink = undefined)}></LinkEditor> ); } |