diff options
-rw-r--r-- | src/client/util/LinkManager.ts | 2 | ||||
-rw-r--r-- | src/client/views/nodes/LinkEditor.scss | 26 | ||||
-rw-r--r-- | src/client/views/nodes/LinkEditor.tsx | 370 | ||||
-rw-r--r-- | src/client/views/nodes/LinkMenu.tsx | 43 |
4 files changed, 252 insertions, 189 deletions
diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts index 5e8e0475b..99fb4c6c0 100644 --- a/src/client/util/LinkManager.ts +++ b/src/client/util/LinkManager.ts @@ -57,7 +57,7 @@ export class LinkManager { } @observable public allLinks: Array<Doc> = []; - @observable public allGroups: Map<string, Doc> = new Map(); + @observable public allGroups: Map<string, Array<string>> = new Map(); public findAllRelatedLinks(anchor: Doc): Array<Doc> { return LinkManager.Instance.allLinks.filter( diff --git a/src/client/views/nodes/LinkEditor.scss b/src/client/views/nodes/LinkEditor.scss index 52ed26442..d3ac8cf19 100644 --- a/src/client/views/nodes/LinkEditor.scss +++ b/src/client/views/nodes/LinkEditor.scss @@ -80,5 +80,31 @@ } .linkEditor-metadata-row { display: flex; + justify-content: space-between; + input { + width: calc(50% - 2px); + } + } +} + +.linkEditor-dropdown { + width: 100%; + position: relative; + .linkEditor-options-wrapper { + width: 100%; + position: absolute; + top: 19px; + left: 0; + display: flex; + flex-direction: column; + } + .linkEditor-option { + background-color: red; + border: 1px solid $intermediate-color; + border-top: 0; + cursor: pointer; + &:hover { + background-color: $light-color-secondary; + } } }
\ No newline at end of file diff --git a/src/client/views/nodes/LinkEditor.tsx b/src/client/views/nodes/LinkEditor.tsx index 14fdd26df..481d3bf37 100644 --- a/src/client/views/nodes/LinkEditor.tsx +++ b/src/client/views/nodes/LinkEditor.tsx @@ -21,235 +21,270 @@ import { string } from "prop-types"; library.add(faArrowLeft); library.add(faEllipsisV); +// this dropdown could be generalizeds +@observer +class LinkGroupsDropdown extends React.Component<{ groupId: string, groupType: string, setGroup: (groupId: string, group: string) => void }> { + @observable private _searchTerm: string = ""; + @observable private _groupType: string = this.props.groupType; + + @action + setSearchTerm(value: string) { + this._searchTerm = value; + } + + @action + setGroupType(value: string) { + this._groupType = value; + } + + createGroup(value: string) { + LinkManager.Instance.allGroups.set(value, []); + this.props.setGroup(this.props.groupId, value); + } + + renderOptions = (): JSX.Element[] => { + let allGroups: string[], searchTerm: string, results: string[], exactFound: boolean; + if (this._searchTerm !== "") { + allGroups = Array.from(LinkManager.Instance.allGroups.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 options = []; + results.forEach(result => { + options.push(<div key={result} className="linkEditor-option" + onClick={() => { this.props.setGroup(this.props.groupId, result); this.setGroupType(result); this.setSearchTerm("") }}>{result}</div>) + }); + + if (!exactFound && this._searchTerm !== "") { + options.push(<div key={""} className="linkEditor-option" + onClick={() => { this.createGroup(this._searchTerm); this.setGroupType(this._searchTerm); this.setSearchTerm("") }}>Create new "{this._searchTerm}" group</div>) + } + + return options; + } + + render() { + return ( + <div className="linkEditor-dropdown"> + <input type="text" value={this._groupType} placeholder="Search for a group or create a new group" + onChange={e => { this.setSearchTerm(e.target.value); this.setGroupType(e.target.value) }}></input> + <div className="linkEditor-options-wrapper"> + {this.renderOptions()} + </div> + </div> + ) + } +} + + interface LinkEditorProps { sourceDoc: Doc; linkDoc: Doc; - groups: Map<string, Doc>; - metadata: Map<string, Map<string, Doc>>; + // groups: Map<string, Doc>; + // metadata: Map<string, Map<string, Doc>>; showLinks: () => void; } @observer 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); - // @observable private _tags: Array<string> = Cast(this.props.linkDoc.linkTags, List); - - // @action - // onTitleChanged = (e: React.ChangeEvent<HTMLInputElement>) => { - // this._title = e.target.value; - // } + @observable private _groups: Map<string, Doc> = new Map(); + @observable private _metadata: Map<string, Map<string, Doc>> = new Map(); + + 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.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<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); + } else { + // promise doc + } + }) + this._groups = groups; + this._metadata = metadata; + } // @action - // onDescriptionChanged = (e: React.ChangeEvent<HTMLTextAreaElement>) => { - // this._description = e.target.value; - // } - - // renderTags() { - // return this._tags.map(tag => { - // if (tag === "") { - // return <input type="text" placeholder="Tag"></input>; - // } else { - // return <input type="text" value={tag}></input>; - // } - // }) - // } - - // addTag = (): void => { - // this._tags.push(""); + // 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 - 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; - 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); + 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())); + } + @action + setGroup = (groupId: string, group: string): void => { 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())); + let groupDoc = this._groups.get(groupId); + if (groupDoc) { + groupDoc.proto!.type = group; + LinkUtils.setAnchorGroups(linkDoc, this.props.sourceDoc, [groupDoc]); + } } renderGroup(groupId: string, groupDoc: Doc) { - // let metadata = this.props.metadata.get(groupId); - // if (!metadata) { - // metadata = new Map<string, Doc>(); - // } + console.log("testing", groupDoc["strawberry"], groupDoc["type"]); return ( - // <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 key={groupId} className="linkEditor-group"> + <div className="linkEditor-group-row"> + <p className="linkEditor-group-row-label">type:</p> + <LinkGroupsDropdown groupId={groupId} groupType={StrCast(groupDoc.proto!.type)} setGroup={this.setGroup} /> + {/* <input key={groupId + "-type"} type="text" value={StrCast(groupDoc.proto!.type)} onChange={e => this.editGroup(groupId, e.target.value)}></input> */} + {/* <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 onClick={() => this.addMetadata(groupId)}>+</button> </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); + addMetadata = (groupType: string): void => { + let mdKeys = LinkManager.Instance.allGroups.get(groupType); + if (mdKeys) { + if (mdKeys.indexOf("new key") > -1) { + mdKeys.push("new key"); + } } else { - mdMap = new Map(); - mdMap.set(Utils.GenerateGuid(), mdDoc); + mdKeys = ["new key"]; } + LinkManager.Instance.allGroups.set(groupType, mdKeys); + console.log("md added", groupType, LinkManager.Instance.allGroups.get(groupType), LinkManager.Instance.allGroups); + + // // 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.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 internal representation of metadata + // this._metadata.set(groupId, mdMap); - // 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())); + // // add to internatal representation of group + // let groupDoc = this._groups.get(groupId); + // if (groupDoc) { + // groupDoc.proto!.metadata = new List<Doc>(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 - // 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); + // 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 group = this.props.metadata.get(groupId); - // if (group) { - // let mdDoc = group.get(mdId); + // 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 - 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> - ) - }) + let groupDoc = this._groups.get(groupId); + if (groupDoc) { + let mdDoc = Cast(groupDoc.proto!.metadata, Doc, new Doc); + let groupType = StrCast(groupDoc.proto!.type); + let groupMdKeys = LinkManager.Instance.allGroups.get(groupType); + console.log("rendering md", groupType, groupMdKeys, LinkManager.Instance.allGroups); + if (groupMdKeys) { + groupMdKeys.forEach(key => { + metadata.push( + <div key={key} className="linkEditor-metadata-row"> + <input type="text" value={key} placeholder="key"></input> + : + <input type="text" value={(mdDoc[key] === undefined) ? "" : StrCast(mdDoc[key])} placeholder="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> - // ) - // }) - } + // let metadataMap = this._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)} placeholder="key" onChange={e => this.editMetadataTitle(groupId, mdId, e.target.value)}></input> + // : + // <input type="text" value={StrCast(mdDoc.proto!.value)} placeholder="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(this.renderGroup(groupId, groupDoc)) - }); - return groups; + return metadata; } render() { let destination = LinkUtils.findOppositeAnchor(this.props.linkDoc, this.props.sourceDoc); + let groups: Array<JSX.Element> = []; + this._groups.forEach((groupDoc, groupId) => { + groups.push(this.renderGroup(groupId, groupDoc)) + }); + return ( <div className="linkEditor"> <button className="linkEditor-back" onPointerDown={() => this.props.showLinks()}><FontAwesomeIcon icon="arrow-left" size="sm" /></button> @@ -258,8 +293,7 @@ export class LinkEditor extends React.Component<LinkEditorProps> { <b>Groups:</b> <button onClick={this.addGroup} title="Add Group">+</button> </div> - {this.renderGroups()} - + {groups} </div> ); diff --git a/src/client/views/nodes/LinkMenu.tsx b/src/client/views/nodes/LinkMenu.tsx index ab478feae..e378ee1cb 100644 --- a/src/client/views/nodes/LinkMenu.tsx +++ b/src/client/views/nodes/LinkMenu.tsx @@ -48,7 +48,6 @@ export class LinkMenu extends React.Component<Props> { let linkItems: Array<JSX.Element> = []; links.forEach((links, group) => { - console.log("category is ", group); linkItems.push( <div key={group} className="link-menu-group"> <p className="link-menu-group-name">{group}:</p> @@ -59,6 +58,10 @@ export class LinkMenu extends React.Component<Props> { ) }); + if (linkItems.length === 0) { + linkItems.push(<p key="">no links have been created yet</p>); + } + return linkItems; } @@ -79,28 +82,28 @@ export class LinkMenu extends React.Component<Props> { </div> ); } else { - 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(groupDoc => { - if (groupDoc instanceof Doc) { - let id = Utils.GenerateGuid(); - groups.set(id, groupDoc); + // 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(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); - } - }) + // 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} metadata={metadata} sourceDoc={this.props.docView.props.Document} linkDoc={this._editingLink} showLinks={action(() => this._editingLink = undefined)}></LinkEditor> + <LinkEditor sourceDoc={this.props.docView.props.Document} linkDoc={this._editingLink} showLinks={action(() => this._editingLink = undefined)}></LinkEditor> ); } |