From 0759b23448de29158367f344342e939dfa6eaf48 Mon Sep 17 00:00:00 2001 From: monikahedman Date: Wed, 21 Aug 2019 11:00:08 -0400 Subject: moved links to own folder --- src/client/views/linking/LinkEditor.scss | 145 +++++++++++ src/client/views/linking/LinkEditor.tsx | 400 +++++++++++++++++++++++++++++ src/client/views/linking/LinkFollowBox.tsx | 9 + src/client/views/linking/LinkMenu.scss | 137 ++++++++++ src/client/views/linking/LinkMenu.tsx | 76 ++++++ src/client/views/linking/LinkMenuGroup.tsx | 111 ++++++++ src/client/views/linking/LinkMenuItem.tsx | 282 ++++++++++++++++++++ 7 files changed, 1160 insertions(+) create mode 100644 src/client/views/linking/LinkEditor.scss create mode 100644 src/client/views/linking/LinkEditor.tsx create mode 100644 src/client/views/linking/LinkFollowBox.tsx create mode 100644 src/client/views/linking/LinkMenu.scss create mode 100644 src/client/views/linking/LinkMenu.tsx create mode 100644 src/client/views/linking/LinkMenuGroup.tsx create mode 100644 src/client/views/linking/LinkMenuItem.tsx (limited to 'src/client/views/linking') diff --git a/src/client/views/linking/LinkEditor.scss b/src/client/views/linking/LinkEditor.scss new file mode 100644 index 000000000..fc5f2410c --- /dev/null +++ b/src/client/views/linking/LinkEditor.scss @@ -0,0 +1,145 @@ +@import "../globalCssVariables"; + +.linkEditor { + width: 100%; + height: auto; + font-size: 12px; // TODO +} + +.linkEditor-back { + margin-bottom: 6px; +} + +.linkEditor-info { + border-bottom: 0.5px solid $light-color-secondary; + padding-bottom: 6px; + margin-bottom: 6px; + display: flex; + justify-content: space-between; + + .linkEditor-linkedTo { + width: calc(100% - 26px); + } +} + +.linkEditor-button { + width: 20px; + height: 20px; + margin-left: 6px; + padding: 0; + // font-size: 12px; + border-radius: 10px; + + &:disabled { + background-color: gray; + } +} + +.linkEditor-groupsLabel { + display: flex; + justify-content: space-between; +} + +.linkEditor-group { + background-color: $light-color-secondary; + padding: 6px; + margin: 3px 0; + border-radius: 3px; + + .linkEditor-group-row { + display: flex; + margin-bottom: 3px; + + .linkEditor-group-row-label { + margin-right: 6px; + } + } + + .linkEditor-metadata-row { + display: flex; + justify-content: space-between; + margin-bottom: 6px; + + .linkEditor-error { + border-color: red; + } + + input { + width: calc(50% - 16px); + height: 20px; + } + + button { + width: 20px; + height: 20px; + margin-left: 3px; + padding: 0; + font-size: 10px; + } + } +} + + +.linkEditor-dropdown { + width: 100%; + position: relative; + z-index: 999; + + input { + width: 100%; + } + + .linkEditor-options-wrapper { + width: 100%; + position: absolute; + top: 19px; + left: 0; + display: flex; + flex-direction: column; + } + + .linkEditor-option { + background-color: $light-color-secondary; + border: 1px solid $intermediate-color; + border-top: 0; + padding: 3px; + cursor: pointer; + + &:hover { + background-color: lightgray; + } + + &.onDown { + background-color: gray; + } + } +} + +.linkEditor-typeButton { + background-color: transparent; + color: $dark-color; + width: 100%; + height: 20px; + padding: 0 3px; + padding-bottom: 2px; + text-align: left; + text-transform: none; + letter-spacing: normal; + font-size: 12px; + font-weight: bold; + + &:hover { + background-color: $light-color; + } +} + +.linkEditor-group-buttons { + height: 20px; + display: flex; + justify-content: flex-end; + margin-top: 5px; + + .linkEditor-button { + margin-left: 6px; + } +} \ No newline at end of file diff --git a/src/client/views/linking/LinkEditor.tsx b/src/client/views/linking/LinkEditor.tsx new file mode 100644 index 000000000..ecb3e9db4 --- /dev/null +++ b/src/client/views/linking/LinkEditor.tsx @@ -0,0 +1,400 @@ +import { observable, computed, action, trace } from "mobx"; +import React = require("react"); +import { observer } from "mobx-react"; +import './LinkEditor.scss'; +import { StrCast, Cast, FieldValue } from "../../../new_fields/Types"; +import { Doc } from "../../../new_fields/Doc"; +import { LinkManager } from "../../util/LinkManager"; +import { Docs } from "../../documents/Documents"; +import { Utils } from "../../../Utils"; +import { faArrowLeft, faEllipsisV, faTable, faTrash, faCog, faExchangeAlt, faTimes, faPlus } from '@fortawesome/free-solid-svg-icons'; +import { library } from "@fortawesome/fontawesome-svg-core"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { SetupDrag } from "../../util/DragManager"; +import { SchemaHeaderField, RandomPastel } from "../../../new_fields/SchemaHeaderField"; + +library.add(faArrowLeft, faEllipsisV, faTable, faTrash, faCog, faExchangeAlt, faTimes, faPlus); + + +interface GroupTypesDropdownProps { + groupType: string; + setGroupType: (group: string) => void; +} +// this dropdown could be generalized +@observer +class GroupTypesDropdown extends React.Component { + @observable private _searchTerm: string = this.props.groupType; + @observable private _groupType: string = this.props.groupType; + @observable private _isEditing: boolean = false; + + @action + createGroup = (groupType: string): void => { + this.props.setGroupType(groupType); + LinkManager.Instance.addGroupType(groupType); + } + + @action + onChange = (val: string): void => { + this._searchTerm = val; + this._groupType = val; + this._isEditing = true; + } + + @action + onKeyDown = (e: React.KeyboardEvent): void => { + if (e.key === "Enter") { + let allGroupTypes = Array.from(LinkManager.Instance.getAllGroupTypes()); + let groupOptions = allGroupTypes.filter(groupType => groupType.toUpperCase().indexOf(this._searchTerm.toUpperCase()) > -1); + let exactFound = groupOptions.findIndex(groupType => groupType.toUpperCase() === this._searchTerm.toUpperCase()); + + if (exactFound > -1) { + let groupType = groupOptions[exactFound]; + this.props.setGroupType(groupType); + this._groupType = groupType; + } else { + this.createGroup(this._searchTerm); + this._groupType = this._searchTerm; + } + + this._searchTerm = this._groupType; + this._isEditing = false; + } + } + + @action + onOptionClick = (value: string, createNew: boolean): void => { + if (createNew) { + this.createGroup(this._searchTerm); + this._groupType = this._searchTerm; + + } else { + this.props.setGroupType(value); + this._groupType = value; + + } + this._searchTerm = this._groupType; + this._isEditing = false; + } + + @action + onButtonPointerDown = (): void => { + this._isEditing = true; + } + + renderOptions = (): JSX.Element[] | JSX.Element => { + if (this._searchTerm === "") return <>; + + let allGroupTypes = Array.from(LinkManager.Instance.getAllGroupTypes()); + let groupOptions = allGroupTypes.filter(groupType => groupType.toUpperCase().indexOf(this._searchTerm.toUpperCase()) > -1); + let exactFound = groupOptions.findIndex(groupType => groupType.toUpperCase() === this._searchTerm.toUpperCase()) > -1; + + let options = groupOptions.map(groupType => { + let ref = React.createRef(); + return
this.onOptionClick(groupType, false)}>{groupType}
; + }); + + // if search term does not already exist as a group type, give option to create new group type + if (!exactFound && this._searchTerm !== "") { + let ref = React.createRef(); + options.push(
this.onOptionClick(this._searchTerm, true)}>Define new "{this._searchTerm}" relationship
); + } + + return options; + } + + render() { + if (this._isEditing || this._groupType === "") { + return ( +
+ this.onChange(e.target.value)} onKeyDown={this.onKeyDown} autoFocus> +
+ {this.renderOptions()} +
+
+ ); + } else { + return ; + } + } +} + + +interface LinkMetadataEditorProps { + id: string; + groupType: string; + mdDoc: Doc; + mdKey: string; + mdValue: string; + changeMdIdKey: (id: string, newKey: string) => void; +} +@observer +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 + setMetadataKey = (value: string): void => { + let groupMdKeys = LinkManager.Instance.getMetadataKeysInGroup(this.props.groupType); + + // don't allow user to create existing key + let newIndex = groupMdKeys.findIndex(key => key.toUpperCase() === value.toUpperCase()); + if (newIndex > -1) { + this._keyError = true; + this._key = value; + return; + } else { + this._keyError = false; + } + + // set new value for key + let currIndex = groupMdKeys.findIndex(key => { + return StrCast(key).toUpperCase() === this._key.toUpperCase(); + }); + if (currIndex === -1) console.error("LinkMetadataEditor: key was not found"); + groupMdKeys[currIndex] = value; + + this.props.changeMdIdKey(this.props.id, value); + this._key = value; + LinkManager.Instance.setMetadataKeysForGroup(this.props.groupType, [...groupMdKeys]); + } + + @action + setMetadataValue = (value: string): void => { + if (!this._keyError) { + this._value = value; + this.props.mdDoc[this._key] = value; + } + } + + @action + removeMetadata = (): void => { + let groupMdKeys = LinkManager.Instance.getMetadataKeysInGroup(this.props.groupType); + + let index = groupMdKeys.findIndex(key => key.toUpperCase() === this._key.toUpperCase()); + if (index === -1) console.error("LinkMetadataEditor: key was not found"); + groupMdKeys.splice(index, 1); + + LinkManager.Instance.setMetadataKeysForGroup(this.props.groupType, groupMdKeys); + this._key = ""; + } + + render() { + return ( +
+ this.setMetadataKey(e.target.value)}>: + this.setMetadataValue(e.target.value)}> + +
+ ); + } +} + +interface LinkGroupEditorProps { + sourceDoc: Doc; + linkDoc: Doc; + groupDoc: Doc; +} +@observer +export class LinkGroupEditor extends React.Component { + + private _metadataIds: Map = new Map(); + + constructor(props: LinkGroupEditorProps) { + super(props); + + let groupMdKeys = LinkManager.Instance.getMetadataKeysInGroup(StrCast(props.groupDoc.type)); + groupMdKeys.forEach(key => { + this._metadataIds.set(key, Utils.GenerateGuid()); + }); + } + + @action + setGroupType = (groupType: string): void => { + this.props.groupDoc.type = groupType; + } + + removeGroupFromLink = (groupType: string): void => { + LinkManager.Instance.removeGroupFromAnchor(this.props.linkDoc, this.props.sourceDoc, groupType); + } + + deleteGroup = (groupType: string): void => { + LinkManager.Instance.deleteGroupType(groupType); + } + + copyGroup = async (groupType: string): Promise => { + let sourceGroupDoc = this.props.groupDoc; + const sourceMdDoc = await Cast(sourceGroupDoc.metadata, Doc); + if (!sourceMdDoc) return; + + let destDoc = LinkManager.Instance.getOppositeAnchor(this.props.linkDoc, this.props.sourceDoc); + // let destGroupList = LinkManager.Instance.getAnchorGroups(this.props.linkDoc, destDoc); + let keys = LinkManager.Instance.getMetadataKeysInGroup(groupType); + + // create new metadata doc with copied kvp + let destMdDoc = new Doc(); + destMdDoc.anchor1 = StrCast(sourceMdDoc.anchor2); + destMdDoc.anchor2 = StrCast(sourceMdDoc.anchor1); + keys.forEach(key => { + let val = sourceMdDoc[key] === undefined ? "" : StrCast(sourceMdDoc[key]); + destMdDoc[key] = val; + }); + + // create new group doc with new metadata doc + let destGroupDoc = new Doc(); + destGroupDoc.type = groupType; + destGroupDoc.metadata = destMdDoc; + + if (destDoc) { + LinkManager.Instance.addGroupToAnchor(this.props.linkDoc, destDoc, destGroupDoc, true); + } + } + + @action + addMetadata = (groupType: string): void => { + this._metadataIds.set("new key", Utils.GenerateGuid()); + let mdKeys = LinkManager.Instance.getMetadataKeysInGroup(groupType); + // only add "new key" if there is no other key with value "new key"; prevents spamming + if (mdKeys.indexOf("new key") === -1) mdKeys.push("new key"); + LinkManager.Instance.setMetadataKeysForGroup(groupType, mdKeys); + } + + // for key rendering purposes + changeMdIdKey = (id: string, newKey: string) => { + this._metadataIds.set(newKey, id); + } + + renderMetadata = (): JSX.Element[] => { + let metadata: Array = []; + let groupDoc = this.props.groupDoc; + const mdDoc = FieldValue(Cast(groupDoc.metadata, Doc)); + if (!mdDoc) { + return []; + } + let groupType = StrCast(groupDoc.type); + let groupMdKeys = LinkManager.Instance.getMetadataKeysInGroup(groupType); + + groupMdKeys.forEach((key) => { + let val = StrCast(mdDoc[key]); + metadata.push( + + ); + }); + return metadata; + } + + viewGroupAsTable = (groupType: string): JSX.Element => { + let keys = LinkManager.Instance.getMetadataKeysInGroup(groupType); + let index = keys.indexOf(""); + if (index > -1) keys.splice(index, 1); + let cols = ["anchor1", "anchor2", ...[...keys]].map(c => new SchemaHeaderField(c, "#f1efeb")); + let docs: Doc[] = LinkManager.Instance.getAllMetadataDocsInGroup(groupType); + let createTable = action(() => Docs.Create.SchemaDocument(cols, docs, { width: 500, height: 300, title: groupType + " table" })); + let ref = React.createRef(); + return
; + } + + render() { + let groupType = StrCast(this.props.groupDoc.type); + // if ((groupType && LinkManager.Instance.getMetadataKeysInGroup(groupType).length > 0) || groupType === "") { + let buttons; + if (groupType === "") { + buttons = ( + <> + + + + + + + ); + } else { + buttons = ( + <> + + + + + {this.viewGroupAsTable(groupType)} + + ); + } + return ( +
+
+

type:

+ +
+ {this.renderMetadata().length > 0 ?

metadata:

: <>} + {this.renderMetadata()} +
+ {buttons} +
+
+ ); + } +} + + +interface LinkEditorProps { + sourceDoc: Doc; + linkDoc: Doc; + showLinks: () => void; +} +@observer +export class LinkEditor extends React.Component { + + @action + deleteLink = (): void => { + LinkManager.Instance.deleteLink(this.props.linkDoc); + this.props.showLinks(); + } + + @action + addGroup = (): void => { + // create new metadata document for group + let mdDoc = new Doc(); + mdDoc.anchor1 = this.props.sourceDoc.title; + let opp = LinkManager.Instance.getOppositeAnchor(this.props.linkDoc, this.props.sourceDoc); + if (opp) { + mdDoc.anchor2 = opp.title; + } + + // create new group document + let groupDoc = new Doc(); + groupDoc.type = ""; + groupDoc.metadata = mdDoc; + + LinkManager.Instance.addGroupToAnchor(this.props.linkDoc, this.props.sourceDoc, groupDoc); + } + + render() { + let destination = LinkManager.Instance.getOppositeAnchor(this.props.linkDoc, this.props.sourceDoc); + + let groupList = LinkManager.Instance.getAnchorGroups(this.props.linkDoc, this.props.sourceDoc); + let groups = groupList.map(groupDoc => { + return ; + }); + + if (destination) { + return ( +
+ +
+

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

+ +
+
+ Relationships: + +
+ {groups.length > 0 ? groups :
There are currently no relationships associated with this link.
} +
+ + ); + } + } +} \ No newline at end of file diff --git a/src/client/views/linking/LinkFollowBox.tsx b/src/client/views/linking/LinkFollowBox.tsx new file mode 100644 index 000000000..487281d50 --- /dev/null +++ b/src/client/views/linking/LinkFollowBox.tsx @@ -0,0 +1,9 @@ +import { observable, computed, action, trace } from "mobx"; +import React = require("react"); +import { observer } from "mobx-react"; +import { FieldViewProps } from "../nodes/FieldView"; + +@observer +export class LinkFollowBox extends React.Component { + +} \ No newline at end of file diff --git a/src/client/views/linking/LinkMenu.scss b/src/client/views/linking/LinkMenu.scss new file mode 100644 index 000000000..a4018bd2d --- /dev/null +++ b/src/client/views/linking/LinkMenu.scss @@ -0,0 +1,137 @@ +@import "../globalCssVariables"; + +.linkMenu { + width: 100%; + height: auto; +} + +.linkMenu-list { + max-height: 200px; + overflow-y: scroll; +} + +.linkMenu-group { + border-bottom: 0.5px solid lightgray; + padding: 5px 0; + + + &:last-child { + border-bottom: none; + } + + .linkMenu-group-name { + display: flex; + + &:hover { + p { + background-color: lightgray; + } + p.expand-one { + width: calc(100% - 26px); + } + .linkEditor-tableButton { + display: block; + } + } + + p { + width: 100%; + padding: 4px 6px; + line-height: 12px; + border-radius: 5px; + font-weight: bold; + } + + .linkEditor-tableButton { + display: none; + } + } +} + +.linkMenu-item { + // border-top: 0.5px solid $main-accent; + position: relative; + display: flex; + font-size: 12px; + + + .link-name { + position: relative; + + p { + padding: 4px 6px; + line-height: 12px; + border-radius: 5px; + overflow-wrap: break-word; + } + } + + .linkMenu-item-content { + width: 100%; + } + + .link-metadata { + padding: 0 10px 0 16px; + margin-bottom: 4px; + color: $main-accent; + font-style: italic; + font-size: 10.5px; + } + + &:hover { + .linkMenu-item-buttons { + display: flex; + } + .linkMenu-item-content { + &.expand-two p { + width: calc(100% - 52px); + background-color: lightgray; + } + &.expand-three p { + width: calc(100% - 84px); + background-color: lightgray; + } + } + } +} + +.linkMenu-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/linking/LinkMenu.tsx b/src/client/views/linking/LinkMenu.tsx new file mode 100644 index 000000000..842ce45b1 --- /dev/null +++ b/src/client/views/linking/LinkMenu.tsx @@ -0,0 +1,76 @@ +import { action, observable } from "mobx"; +import { observer } from "mobx-react"; +import { DocumentView } from "../nodes/DocumentView"; +import { LinkEditor } from "./LinkEditor"; +import './LinkMenu.scss'; +import React = require("react"); +import { Doc } from "../../../new_fields/Doc"; +import { LinkManager } from "../../util/LinkManager"; +import { LinkMenuGroup } from "./LinkMenuGroup"; +import { faTrash } from '@fortawesome/free-solid-svg-icons'; +import { library } from "@fortawesome/fontawesome-svg-core"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; + +library.add(faTrash); + +interface Props { + docView: DocumentView; + changeFlyout: () => void; + addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => void; +} + +@observer +export class LinkMenu extends React.Component { + + @observable private _editingLink?: Doc; + + @action + componentWillReceiveProps() { + this._editingLink = undefined; + } + + clearAllLinks = () => { + LinkManager.Instance.deleteAllLinksOnAnchor(this.props.docView.props.Document); + } + + renderAllGroups = (groups: Map>): Array => { + let linkItems: Array = []; + groups.forEach((group, groupType) => { + linkItems.push( + this._editingLink = linkDoc)} + addDocTab={this.props.addDocTab} /> + ); + }); + + // 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; + } + + render() { + let sourceDoc = this.props.docView.props.Document; + let groups: Map = LinkManager.Instance.getRelatedGroupedLinks(sourceDoc); + if (this._editingLink === undefined) { + return ( +
+ + {/* */} +
+ {this.renderAllGroups(groups)} +
+
+ ); + } else { + return ( + this._editingLink = undefined)}> + ); + } + } +} \ No newline at end of file diff --git a/src/client/views/linking/LinkMenuGroup.tsx b/src/client/views/linking/LinkMenuGroup.tsx new file mode 100644 index 000000000..b6a24b0c8 --- /dev/null +++ b/src/client/views/linking/LinkMenuGroup.tsx @@ -0,0 +1,111 @@ +import { action, observable } from "mobx"; +import { observer } from "mobx-react"; +import { DocumentView } from "../nodes/DocumentView"; +import { LinkMenuItem } from "./LinkMenuItem"; +import { LinkEditor } from "./LinkEditor"; +import './LinkMenu.scss'; +import React = require("react"); +import { Doc, DocListCast } from "../../../new_fields/Doc"; +import { Id } from "../../../new_fields/FieldSymbols"; +import { LinkManager } from "../../util/LinkManager"; +import { DragLinksAsDocuments, DragManager, SetupDrag } from "../../util/DragManager"; +import { emptyFunction } from "../../../Utils"; +import { Docs } from "../../documents/Documents"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { UndoManager } from "../../util/UndoManager"; +import { StrCast } from "../../../new_fields/Types"; +import { SchemaHeaderField, RandomPastel } from "../../../new_fields/SchemaHeaderField"; + +interface LinkMenuGroupProps { + sourceDoc: Doc; + group: Doc[]; + groupType: string; + showEditor: (linkDoc: Doc) => void; + addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => void; + docView: DocumentView; + +} + +@observer +export class LinkMenuGroup extends React.Component { + + private _drag = React.createRef(); + private _table = React.createRef(); + + 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) => { + UndoManager.RunInBatch(() => { + if (this._drag.current !== null && (e.movementX > 1 || e.movementY > 1)) { + document.removeEventListener("pointermove", this.onLinkButtonMoved); + document.removeEventListener("pointerup", this.onLinkButtonUp); + + let draggedDocs = this.props.group.map(linkDoc => { + let opp = LinkManager.Instance.getOppositeAnchor(linkDoc, this.props.sourceDoc); + if (opp) return opp; + }) as Doc[]; + let dragData = new DragManager.DocumentDragData(draggedDocs, draggedDocs.map(d => undefined)); + + DragManager.StartLinkedDocumentDrag([this._drag.current], dragData, e.x, e.y, { + handlers: { + dragComplete: action(emptyFunction), + }, + hideSource: false + }); + } + }, "drag links"); + e.stopPropagation(); + } + + viewGroupAsTable = (groupType: string): JSX.Element => { + let keys = LinkManager.Instance.getMetadataKeysInGroup(groupType); + let index = keys.indexOf(""); + if (index > -1) keys.splice(index, 1); + let cols = ["anchor1", "anchor2", ...[...keys]].map(c => new SchemaHeaderField(c, "#f1efeb")); + let docs: Doc[] = LinkManager.Instance.getAllMetadataDocsInGroup(groupType); + let createTable = action(() => Docs.Create.SchemaDocument(cols, docs, { width: 500, height: 300, title: groupType + " table" })); + let ref = React.createRef(); + return
; + } + + render() { + let groupItems = this.props.group.map(linkDoc => { + let destination = LinkManager.Instance.getOppositeAnchor(linkDoc, this.props.sourceDoc); + if (destination && this.props.sourceDoc) { + return ; + } + }); + + return ( +
+
+

{this.props.groupType}:

+ {this.props.groupType === "*" || this.props.groupType === "" ? <> : this.viewGroupAsTable(this.props.groupType)} +
+
+ {groupItems} +
+
+ ); + } +} \ No newline at end of file diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx new file mode 100644 index 000000000..7dc0a9fcd --- /dev/null +++ b/src/client/views/linking/LinkMenuItem.tsx @@ -0,0 +1,282 @@ +import { library } from '@fortawesome/fontawesome-svg-core'; +import { faEdit, faEye, faTimes, faArrowRight, faChevronDown, faChevronUp } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { observer } from "mobx-react"; +import { DocumentManager } from "../../util/DocumentManager"; +import { undoBatch } from "../../util/UndoManager"; +import './LinkMenu.scss'; +import React = require("react"); +import { Doc, DocListCastAsync } from '../../../new_fields/Doc'; +import { StrCast, Cast, FieldValue, NumCast } from '../../../new_fields/Types'; +import { observable, action } from 'mobx'; +import { LinkManager } from '../../util/LinkManager'; +import { DragLinkAsDocument } from '../../util/DragManager'; +import { CollectionDockingView } from '../collections/CollectionDockingView'; +import { SelectionManager } from '../../util/SelectionManager'; +import { CollectionViewType } from '../collections/CollectionBaseView'; +import { DocumentView } from '../nodes/DocumentView'; +library.add(faEye, faEdit, faTimes, faArrowRight, faChevronDown, faChevronUp); + + +interface LinkMenuItemProps { + groupType: string; + linkDoc: Doc; + sourceDoc: Doc; + destinationDoc: Doc; + showEditor: (linkDoc: Doc) => void; + addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => void; +} + +@observer +export class LinkMenuItem extends React.Component { + private _drag = React.createRef(); + @observable private _showMore: boolean = false; + @action toggleShowMore() { this._showMore = !this._showMore; } + + + unhighlight = () => { + Doc.UnhighlightAll(); + document.removeEventListener("pointerdown", this.unhighlight); + } + + @action + highlightDoc = () => { + document.removeEventListener("pointerdown", this.unhighlight); + Doc.HighlightDoc(this.props.destinationDoc); + window.setTimeout(() => { + document.addEventListener("pointerdown", this.unhighlight); + }, 10000); + } + + // NOT TESTED + // col = collection the doc is in + // target = the document to center on + @undoBatch + openLinkColRight = ({ col, target }: { col: Doc, target: Doc }) => { + col = Doc.IsPrototype(col) ? Doc.MakeDelegate(col) : col; + if (NumCast(col.viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) { + const newPanX = NumCast(target.x) + NumCast(target.width) / NumCast(target.zoomBasis, 1) / 2; + const newPanY = NumCast(target.y) + NumCast(target.height) / NumCast(target.zoomBasis, 1) / 2; + col.panX = newPanX; + col.panY = newPanY; + } + CollectionDockingView.Instance.AddRightSplit(col, undefined); + } + + // DONE + // this opens the linked doc in a right split, NOT in its collection + @undoBatch + openLinkRight = () => { + this.highlightDoc(); + let alias = Doc.MakeAlias(this.props.destinationDoc); + CollectionDockingView.Instance.AddRightSplit(alias, undefined); + SelectionManager.DeselectAll(); + } + + // DONE + // this is the standard "follow link" (jump to document) + // taken from follow link + @undoBatch + jumpToLink = async (shouldZoom: boolean) => { + //there is an issue right now so this will be false automatically + shouldZoom = false; + this.highlightDoc(); + let jumpToDoc = this.props.destinationDoc; + let pdfDoc = FieldValue(Cast(this.props.destinationDoc, Doc)); + if (pdfDoc) { + jumpToDoc = pdfDoc; + } + let proto = Doc.GetProto(this.props.linkDoc); + let targetContext = await Cast(proto.targetContext, Doc); + let sourceContext = await Cast(proto.sourceContext, Doc); + let self = this; + + let dockingFunc = (document: Doc) => { this.props.addDocTab(document, undefined, "inTab"); SelectionManager.DeselectAll(); }; + + if (this.props.destinationDoc === self.props.linkDoc.anchor2 && targetContext) { + DocumentManager.Instance.jumpToDocument(jumpToDoc, shouldZoom, false, async document => dockingFunc(document), undefined, targetContext); + } + else if (this.props.destinationDoc === self.props.linkDoc.anchor1 && sourceContext) { + DocumentManager.Instance.jumpToDocument(jumpToDoc, shouldZoom, false, document => dockingFunc(sourceContext!)); + } + else if (DocumentManager.Instance.getDocumentView(jumpToDoc)) { + DocumentManager.Instance.jumpToDocument(jumpToDoc, shouldZoom, undefined, undefined, NumCast((this.props.destinationDoc === self.props.linkDoc.anchor2 ? self.props.linkDoc.anchor2Page : self.props.linkDoc.anchor1Page))); + + } + else { + DocumentManager.Instance.jumpToDocument(jumpToDoc, shouldZoom, false, dockingFunc); + } + } + + // DONE + // opens link in new tab (not in a collection) + // this opens it full screen, do we need a separate full screen option? + @undoBatch + openLinkTab = () => { + this.highlightDoc(); + let fullScreenAlias = Doc.MakeAlias(this.props.destinationDoc); + this.props.addDocTab(fullScreenAlias, undefined, "inTab"); + SelectionManager.DeselectAll(); + } + + // NOT TESTED + // opens link in new tab in collection + // col = collection the doc is in + // target = the document to center on + @undoBatch + openLinkColTab = ({ col, target }: { col: Doc, target: Doc }) => { + this.highlightDoc(); + col = Doc.IsPrototype(col) ? Doc.MakeDelegate(col) : col; + if (NumCast(col.viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) { + const newPanX = NumCast(target.x) + NumCast(target.width) / NumCast(target.zoomBasis, 1) / 2; + const newPanY = NumCast(target.y) + NumCast(target.height) / NumCast(target.zoomBasis, 1) / 2; + col.panX = newPanX; + col.panY = newPanY; + } + // CollectionDockingView.Instance.AddRightSplit(col, undefined); + this.props.addDocTab(col, undefined, "inTab"); + SelectionManager.DeselectAll(); + } + + // this will open a link next to the source doc + @undoBatch + openLinkInPlace = () => { + this.highlightDoc(); + + let alias = Doc.MakeAlias(this.props.destinationDoc); + let y = this.props.sourceDoc.y; + let x = this.props.sourceDoc.x; + let parentView: any = undefined; + let parentDoc: Doc = this.props.sourceDoc; + + SelectionManager.SelectedDocuments().map(dv => { + if (dv.props.Document === this.props.sourceDoc) { + parentView = dv.props.ContainingCollectionView; + } + }); + + if (parentView) { + // console.log(parentDoc) + console.log(parentView.props.addDocument); + } + } + + //set this to be the default link behavior, can be any of the above + private defaultLinkBehavior: any = this.openLinkInPlace; + + onEdit = (e: React.PointerEvent): void => { + e.stopPropagation(); + this.props.showEditor(this.props.linkDoc); + SelectionManager.DeselectAll(); + } + + 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, null); + if (mdDoc) { + let keys = LinkManager.Instance.getMetadataKeysInGroup(this.props.groupType);//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.getMetadataKeysInGroup(this.props.groupType);//groupMetadataKeys.get(this.props.groupType); + let canExpand = keys ? keys.length > 0 : false; + + return ( +
+
+
+

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

+
+ {canExpand ?
this.toggleShowMore()}> +
: <>} +
+ {/* Original */} + {/*
*/} + {/* New */} +
+
+
+ {this._showMore ? this.renderMetadata() : <>} +
+ +
+ ); + } +} + + // @undoBatch + // onFollowLink = async (e: React.PointerEvent): Promise => { + // e.stopPropagation(); + // e.persist(); + // let jumpToDoc = this.props.destinationDoc; + // let pdfDoc = FieldValue(Cast(this.props.destinationDoc, Doc)); + // if (pdfDoc) { + // jumpToDoc = pdfDoc; + // } + // let proto = Doc.GetProto(this.props.linkDoc); + // let targetContext = await Cast(proto.targetContext, Doc); + // let sourceContext = await Cast(proto.sourceContext, Doc); + // let self = this; + + + // let dockingFunc = (document: Doc) => { this.props.addDocTab(document, undefined, "inTab"); SelectionManager.DeselectAll(); }; + // if (e.ctrlKey) { + // dockingFunc = (document: Doc) => CollectionDockingView.Instance.AddRightSplit(document, undefined); + // } + + // if (this.props.destinationDoc === self.props.linkDoc.anchor2 && targetContext) { + // DocumentManager.Instance.jumpToDocument(jumpToDoc, e.altKey, false, async document => dockingFunc(document), undefined, targetContext!); + // console.log("1") + // } + // else if (this.props.destinationDoc === self.props.linkDoc.anchor1 && sourceContext) { + // DocumentManager.Instance.jumpToDocument(jumpToDoc, e.altKey, false, document => dockingFunc(sourceContext!)); + // console.log("2") + // } + // else if (DocumentManager.Instance.getDocumentView(jumpToDoc)) { + // DocumentManager.Instance.jumpToDocument(jumpToDoc, e.altKey, undefined, undefined, NumCast((this.props.destinationDoc === self.props.linkDoc.anchor2 ? self.props.linkDoc.anchor2Page : self.props.linkDoc.anchor1Page))); + // console.log("3") + + // } + // else { + // DocumentManager.Instance.jumpToDocument(jumpToDoc, e.altKey, false, dockingFunc); + // console.log("4") + + // } + // } \ No newline at end of file -- cgit v1.2.3-70-g09d2 From 11d2743b553da47da88e77de1ac758e16e09b3e0 Mon Sep 17 00:00:00 2001 From: monikahedman Date: Wed, 21 Aug 2019 13:55:46 -0400 Subject: adding docs works --- src/client/views/linking/LinkFollowBox.tsx | 3 ++- src/client/views/linking/LinkMenuItem.tsx | 26 ++++++++++++++------------ src/client/views/nodes/FormattedTextBox.tsx | 2 +- 3 files changed, 17 insertions(+), 14 deletions(-) (limited to 'src/client/views/linking') diff --git a/src/client/views/linking/LinkFollowBox.tsx b/src/client/views/linking/LinkFollowBox.tsx index 487281d50..061a4fa93 100644 --- a/src/client/views/linking/LinkFollowBox.tsx +++ b/src/client/views/linking/LinkFollowBox.tsx @@ -1,9 +1,10 @@ import { observable, computed, action, trace } from "mobx"; import React = require("react"); import { observer } from "mobx-react"; -import { FieldViewProps } from "../nodes/FieldView"; +import { FieldViewProps, FieldView } from "../nodes/FieldView"; @observer export class LinkFollowBox extends React.Component { + public static LayoutString() { return FieldView.LayoutString(LinkFollowBox); } } \ No newline at end of file diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx index 7dc0a9fcd..65516b374 100644 --- a/src/client/views/linking/LinkMenuItem.tsx +++ b/src/client/views/linking/LinkMenuItem.tsx @@ -1,12 +1,12 @@ import { library } from '@fortawesome/fontawesome-svg-core'; -import { faEdit, faEye, faTimes, faArrowRight, faChevronDown, faChevronUp } from '@fortawesome/free-solid-svg-icons'; +import { faEdit, faEye, faTimes, faArrowRight, faChevronDown, faChevronUp, faGlobeAsia } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { observer } from "mobx-react"; import { DocumentManager } from "../../util/DocumentManager"; import { undoBatch } from "../../util/UndoManager"; import './LinkMenu.scss'; import React = require("react"); -import { Doc, DocListCastAsync } from '../../../new_fields/Doc'; +import { Doc, DocListCastAsync, WidthSym } from '../../../new_fields/Doc'; import { StrCast, Cast, FieldValue, NumCast } from '../../../new_fields/Types'; import { observable, action } from 'mobx'; import { LinkManager } from '../../util/LinkManager'; @@ -138,27 +138,29 @@ export class LinkMenuItem extends React.Component { SelectionManager.DeselectAll(); } + // DONE // this will open a link next to the source doc @undoBatch openLinkInPlace = () => { this.highlightDoc(); let alias = Doc.MakeAlias(this.props.destinationDoc); - let y = this.props.sourceDoc.y; - let x = this.props.sourceDoc.x; - let parentView: any = undefined; - let parentDoc: Doc = this.props.sourceDoc; + let y = NumCast(this.props.sourceDoc.y); + let x = NumCast(this.props.sourceDoc.x); + + let width = NumCast(this.props.sourceDoc.width); + let height = NumCast(this.props.sourceDoc.height); + + alias.x = x + width + 30; + alias.y = y; + alias.width = width; + alias.height = height; SelectionManager.SelectedDocuments().map(dv => { if (dv.props.Document === this.props.sourceDoc) { - parentView = dv.props.ContainingCollectionView; + dv.props.addDocument && dv.props.addDocument(alias, false); } }); - - if (parentView) { - // console.log(parentDoc) - console.log(parentView.props.addDocument); - } } //set this to be the default link behavior, can be any of the above diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 9ae092bad..9652a3a78 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -739,7 +739,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe @action tryUpdateHeight() { if (this.props.Document.autoHeight && this._ref.current!.scrollHeight !== 0) { - console.log("DT = " + this.props.Document.title + " " + this._ref.current!.clientHeight + " " + this._ref.current!.scrollHeight + " " + this._ref.current!.textContent); + // console.log("DT = " + this.props.Document.title + " " + this._ref.current!.clientHeight + " " + this._ref.current!.scrollHeight + " " + this._ref.current!.textContent); let xf = this._ref.current!.getBoundingClientRect(); let scrBounds = this.props.ScreenToLocalTransform().transformBounds(0, 0, xf.width, this._ref.current!.textContent === "" ? 35 : this._ref.current!.scrollHeight); let nh = this.props.Document.isTemplate ? 0 : NumCast(this.dataDoc.nativeHeight, 0); -- cgit v1.2.3-70-g09d2 From 5c9f40006aa157c58ec40828ebd4845c16daa8af Mon Sep 17 00:00:00 2001 From: monikahedman Date: Wed, 21 Aug 2019 15:08:39 -0400 Subject: start of making link follow --- src/client/documents/DocumentTypes.ts | 1 + src/client/documents/Documents.ts | 4 + src/client/views/MainView.tsx | 11 ++ src/client/views/linking/LinkFollowBox.scss | 6 + src/client/views/linking/LinkFollowBox.tsx | 148 ++++++++++++++++++++++++ src/client/views/linking/LinkMenuItem.tsx | 12 +- src/client/views/nodes/DocumentContentsView.tsx | 4 +- 7 files changed, 179 insertions(+), 7 deletions(-) create mode 100644 src/client/views/linking/LinkFollowBox.scss (limited to 'src/client/views/linking') diff --git a/src/client/documents/DocumentTypes.ts b/src/client/documents/DocumentTypes.ts index 1578e49fe..381981e1b 100644 --- a/src/client/documents/DocumentTypes.ts +++ b/src/client/documents/DocumentTypes.ts @@ -19,4 +19,5 @@ export enum DocumentType { YOUTUBE = "youtube", DRAGBOX = "dragbox", PRES = "presentation", + LINKFOLLOW = "linkfollow", } \ No newline at end of file diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 47df17329..cd612aaa9 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -45,6 +45,7 @@ import { PresBox } from "../views/nodes/PresBox"; import { ComputedField } from "../../new_fields/ScriptField"; import { ProxyField } from "../../new_fields/Proxy"; import { DocumentType } from "./DocumentTypes"; +import { LinkFollowBox } from "../views/linking/LinkFollowBox"; //import { PresBox } from "../views/nodes/PresBox"; //import { PresField } from "../../new_fields/PresField"; var requestImageSize = require('../util/request-image-size'); @@ -169,6 +170,9 @@ export namespace Docs { [DocumentType.DRAGBOX, { layout: { view: DragBox }, options: { width: 40, height: 40 }, + }], + [DocumentType.LINKFOLLOW, { + layout: { view: LinkFollowBox } }] ]); diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index f28844009..f3c8a176c 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -40,6 +40,7 @@ import { PreviewCursor } from './PreviewCursor'; import { FilterBox } from './search/FilterBox'; import PresModeMenu from './presentationview/PresentationModeMenu'; import { PresBox } from './nodes/PresBox'; +import { LinkFollowBox } from './linking/LinkFollowBox'; @observer export class MainView extends React.Component { @@ -55,6 +56,8 @@ export class MainView extends React.Component { public overlayTimeout: NodeJS.Timeout | undefined; + @observable private _linkFollowBox = false; + public initiateDictationFade = () => { let duration = DictationManager.Commands.dictationFadeDuration; this.overlayTimeout = setTimeout(() => { @@ -498,6 +501,12 @@ export class MainView extends React.Component { this._colorPickerDisplay = close ? false : !this._colorPickerDisplay; } + @action + toggleLinkFollowBox = () => { + console.log("toggling link editor") + this._linkFollowBox = !this._linkFollowBox; + } + /* @TODO this should really be moved into a moveable toolbar component, but for now let's put it here to meet the deadline */ @computed get miscButtons() { @@ -506,6 +515,7 @@ export class MainView extends React.Component { return [ this.isSearchVisible ?
: null,
+
]; @@ -564,6 +574,7 @@ export class MainView extends React.Component { + {/* */} ); } diff --git a/src/client/views/linking/LinkFollowBox.scss b/src/client/views/linking/LinkFollowBox.scss new file mode 100644 index 000000000..c764b002f --- /dev/null +++ b/src/client/views/linking/LinkFollowBox.scss @@ -0,0 +1,6 @@ +@import "../globalCssVariables"; + +.linkFollowBox-main { + position: absolute; + background: $main-accent; +} \ No newline at end of file diff --git a/src/client/views/linking/LinkFollowBox.tsx b/src/client/views/linking/LinkFollowBox.tsx index 061a4fa93..7fc4449d3 100644 --- a/src/client/views/linking/LinkFollowBox.tsx +++ b/src/client/views/linking/LinkFollowBox.tsx @@ -2,9 +2,157 @@ import { observable, computed, action, trace } from "mobx"; import React = require("react"); import { observer } from "mobx-react"; import { FieldViewProps, FieldView } from "../nodes/FieldView"; +import { Doc } from "../../../new_fields/Doc"; +import { undoBatch } from "../../util/UndoManager"; +import { NumCast, FieldValue, Cast } from "../../../new_fields/Types"; +import { CollectionViewType } from "../collections/CollectionBaseView"; +import { CollectionDockingView } from "../collections/CollectionDockingView"; +import { SelectionManager } from "../../util/SelectionManager"; +import { DocumentManager } from "../../util/DocumentManager"; @observer export class LinkFollowBox extends React.Component { public static LayoutString() { return FieldView.LayoutString(LinkFollowBox); } + public static Instance: LinkFollowBox; + //set this to be the default link behavior, can be any of the above + + unhighlight = () => { + Doc.UnhighlightAll(); + document.removeEventListener("pointerdown", this.unhighlight); + } + + @action + highlightDoc = (destinationDoc: Doc) => { + document.removeEventListener("pointerdown", this.unhighlight); + Doc.HighlightDoc(destinationDoc); + window.setTimeout(() => { + document.addEventListener("pointerdown", this.unhighlight); + }, 10000); + } + + // NOT TESTED + // col = collection the doc is in + // target = the document to center on + @undoBatch + openLinkColRight = (destinationDoc: Doc, col: Doc) => { + col = Doc.IsPrototype(col) ? Doc.MakeDelegate(col) : col; + if (NumCast(col.viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) { + const newPanX = NumCast(destinationDoc.x) + NumCast(destinationDoc.width) / NumCast(destinationDoc.zoomBasis, 1) / 2; + const newPanY = NumCast(destinationDoc.y) + NumCast(destinationDoc.height) / NumCast(destinationDoc.zoomBasis, 1) / 2; + col.panX = newPanX; + col.panY = newPanY; + } + CollectionDockingView.Instance.AddRightSplit(col, undefined); + } + + // DONE + // this opens the linked doc in a right split, NOT in its collection + @undoBatch + openLinkRight = (destinationDoc: Doc) => { + this.highlightDoc(destinationDoc); + let alias = Doc.MakeAlias(destinationDoc); + CollectionDockingView.Instance.AddRightSplit(alias, undefined); + SelectionManager.DeselectAll(); + } + + // DONE + // this is the standard "follow link" (jump to document) + // taken from follow link + @undoBatch + jumpToLink = async (destinationDoc: Doc, shouldZoom: boolean, linkDoc: Doc) => { + //there is an issue right now so this will be false automatically + shouldZoom = false; + this.highlightDoc(destinationDoc); + let jumpToDoc = destinationDoc; + let pdfDoc = FieldValue(Cast(destinationDoc, Doc)); + if (pdfDoc) { + jumpToDoc = pdfDoc; + } + let proto = Doc.GetProto(linkDoc); + let targetContext = await Cast(proto.targetContext, Doc); + let sourceContext = await Cast(proto.sourceContext, Doc); + + let dockingFunc = (document: Doc) => { this.props.addDocTab(document, undefined, "inTab"); SelectionManager.DeselectAll(); }; + + if (destinationDoc === linkDoc.anchor2 && targetContext) { + DocumentManager.Instance.jumpToDocument(jumpToDoc, shouldZoom, false, async document => dockingFunc(document), undefined, targetContext); + } + else if (destinationDoc === linkDoc.anchor1 && sourceContext) { + DocumentManager.Instance.jumpToDocument(jumpToDoc, shouldZoom, false, document => dockingFunc(sourceContext!)); + } + else if (DocumentManager.Instance.getDocumentView(jumpToDoc)) { + DocumentManager.Instance.jumpToDocument(jumpToDoc, shouldZoom, undefined, undefined, NumCast((destinationDoc === linkDoc.anchor2 ? linkDoc.anchor2Page : linkDoc.anchor1Page))); + + } + else { + DocumentManager.Instance.jumpToDocument(jumpToDoc, shouldZoom, false, dockingFunc); + } + } + + // DONE + // opens link in new tab (not in a collection) + // this opens it full screen, do we need a separate full screen option? + @undoBatch + openLinkTab = (destinationDoc: Doc) => { + this.highlightDoc(destinationDoc); + let fullScreenAlias = Doc.MakeAlias(destinationDoc); + this.props.addDocTab(fullScreenAlias, undefined, "inTab"); + SelectionManager.DeselectAll(); + } + + // NOT TESTED + // opens link in new tab in collection + // col = collection the doc is in + // target = the document to center on + @undoBatch + openLinkColTab = (destinationDoc: Doc, col: Doc) => { + this.highlightDoc(destinationDoc); + col = Doc.IsPrototype(col) ? Doc.MakeDelegate(col) : col; + if (NumCast(col.viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) { + const newPanX = NumCast(destinationDoc.x) + NumCast(destinationDoc.width) / NumCast(destinationDoc.zoomBasis, 1) / 2; + const newPanY = NumCast(destinationDoc.y) + NumCast(destinationDoc.height) / NumCast(destinationDoc.zoomBasis, 1) / 2; + col.panX = newPanX; + col.panY = newPanY; + } + // CollectionDockingView.Instance.AddRightSplit(col, undefined); + this.props.addDocTab(col, undefined, "inTab"); + SelectionManager.DeselectAll(); + } + + // DONE + // this will open a link next to the source doc + @undoBatch + openLinkInPlace = (destinationDoc: Doc, sourceDoc: Doc) => { + this.highlightDoc(destinationDoc); + + let alias = Doc.MakeAlias(destinationDoc); + let y = NumCast(sourceDoc.y); + let x = NumCast(sourceDoc.x); + + let width = NumCast(sourceDoc.width); + let height = NumCast(sourceDoc.height); + + alias.x = x + width + 30; + alias.y = y; + alias.width = width; + alias.height = height; + + SelectionManager.SelectedDocuments().map(dv => { + if (dv.props.Document === sourceDoc) { + dv.props.addDocument && dv.props.addDocument(alias, false); + } + }); + } + + private defaultLinkBehavior: any = this.openLinkInPlace; + private currentLinkBehavior: any = this.defaultLinkBehavior; + + render() { + return ( +
+ +
+ ); + } } \ No newline at end of file diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx index 65516b374..41723030d 100644 --- a/src/client/views/linking/LinkMenuItem.tsx +++ b/src/client/views/linking/LinkMenuItem.tsx @@ -52,11 +52,11 @@ export class LinkMenuItem extends React.Component { // col = collection the doc is in // target = the document to center on @undoBatch - openLinkColRight = ({ col, target }: { col: Doc, target: Doc }) => { + openLinkColRight = (col: Doc) => { col = Doc.IsPrototype(col) ? Doc.MakeDelegate(col) : col; if (NumCast(col.viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) { - const newPanX = NumCast(target.x) + NumCast(target.width) / NumCast(target.zoomBasis, 1) / 2; - const newPanY = NumCast(target.y) + NumCast(target.height) / NumCast(target.zoomBasis, 1) / 2; + const newPanX = NumCast(this.props.destinationDoc.x) + NumCast(this.props.destinationDoc.width) / NumCast(this.props.destinationDoc.zoomBasis, 1) / 2; + const newPanY = NumCast(this.props.destinationDoc.y) + NumCast(this.props.destinationDoc.height) / NumCast(this.props.destinationDoc.zoomBasis, 1) / 2; col.panX = newPanX; col.panY = newPanY; } @@ -124,12 +124,12 @@ export class LinkMenuItem extends React.Component { // col = collection the doc is in // target = the document to center on @undoBatch - openLinkColTab = ({ col, target }: { col: Doc, target: Doc }) => { + openLinkColTab = (col: Doc) => { this.highlightDoc(); col = Doc.IsPrototype(col) ? Doc.MakeDelegate(col) : col; if (NumCast(col.viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) { - const newPanX = NumCast(target.x) + NumCast(target.width) / NumCast(target.zoomBasis, 1) / 2; - const newPanY = NumCast(target.y) + NumCast(target.height) / NumCast(target.zoomBasis, 1) / 2; + const newPanX = NumCast(this.props.destinationDoc.x) + NumCast(this.props.destinationDoc.width) / NumCast(this.props.destinationDoc.zoomBasis, 1) / 2; + const newPanY = NumCast(this.props.destinationDoc.y) + NumCast(this.props.destinationDoc.height) / NumCast(this.props.destinationDoc.zoomBasis, 1) / 2; col.panX = newPanX; col.panY = newPanY; } diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index d77662355..d0e117fe4 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -14,6 +14,7 @@ import { ImageBox } from "./ImageBox"; import { DragBox } from "./DragBox"; import { ButtonBox } from "./ButtonBox"; import { PresBox } from "./PresBox"; +import { LinkFollowBox } from "../linking/LinkFollowBox"; import { IconBox } from "./IconBox"; import { KeyValueBox } from "./KeyValueBox"; import { PDFBox } from "./PDFBox"; @@ -30,6 +31,7 @@ import { List } from "../../../new_fields/List"; import { Doc } from "../../../new_fields/Doc"; import DirectoryImportBox from "../../util/Import & Export/DirectoryImportBox"; import { ScriptField } from "../../../new_fields/ScriptField"; +import { fromPromise } from "mobx-utils"; const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this? type BindingProps = Without; @@ -108,7 +110,7 @@ export class DocumentContentsView extends React.Component Date: Thu, 22 Aug 2019 19:42:54 -0400 Subject: making the box come up --- src/client/documents/Documents.ts | 4 ++ src/client/views/MainView.tsx | 2 + src/client/views/linking/LinkFollowBox.scss | 2 +- src/client/views/linking/LinkFollowBox.tsx | 87 ++++++++++++++++++----------- src/client/views/linking/LinkMenuItem.tsx | 12 +++- 5 files changed, 73 insertions(+), 34 deletions(-) (limited to 'src/client/views/linking') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index cd612aaa9..b7c8f4c12 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -446,6 +446,10 @@ export namespace Docs { return InstanceFromProto(Prototypes.get(DocumentType.DRAGBOX), undefined, { ...(options || {}) }); } + export function LinkFollowBoxDocument(options?: DocumentOptions) { + return InstanceFromProto(Prototypes.get(DocumentType.LINKFOLLOW), undefined, { ...(options || {}) }); + } + export function DockDocument(documents: Array, config: string, options: DocumentOptions, id?: string) { return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { ...options, viewType: CollectionViewType.Docking, dockingConfig: config }, id); } diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index f3c8a176c..79c44ae72 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -443,6 +443,7 @@ export class MainView extends React.Component { let imgurl = "https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg"; let addColNode = action(() => Docs.Create.FreeformDocument([], { width: this.pwidth * .7, height: this.pheight, title: "a freeform collection" })); + let addLinkFollowBox = action(() => Docs.Create.LinkFollowBoxDocument({ width: 200, height: 500, title: "Link Follow Document" })); let addPresNode = action(() => Doc.UserDoc().curPresentation = Docs.Create.PresDocument(new List(), { width: 200, height: 500, title: "a presentation trail" })); let addWebNode = action(() => Docs.Create.WebDocument("https://en.wikipedia.org/wiki/Hedgehog", { width: 300, height: 300, title: "New Webpage" })); let addDragboxNode = action(() => Docs.Create.DragboxDocument({ width: 40, height: 40, title: "drag collection" })); @@ -458,6 +459,7 @@ export class MainView extends React.Component { [React.createRef(), "globe-asia", "Add Website", addWebNode], [React.createRef(), "bolt", "Add Button", addButtonDocument], [React.createRef(), "file", "Add Document Dragger", addDragboxNode], + [React.createRef(), "caret-up", "Open Link Follow Box", addLinkFollowBox], [React.createRef(), "cloud-upload-alt", "Import Directory", addImportCollectionNode], //remove at some point in favor of addImportCollectionNode //[React.createRef(), "play", "Add Youtube Searcher", addYoutubeSearcher], ]; diff --git a/src/client/views/linking/LinkFollowBox.scss b/src/client/views/linking/LinkFollowBox.scss index c764b002f..522191792 100644 --- a/src/client/views/linking/LinkFollowBox.scss +++ b/src/client/views/linking/LinkFollowBox.scss @@ -2,5 +2,5 @@ .linkFollowBox-main { position: absolute; - background: $main-accent; + background: red; } \ No newline at end of file diff --git a/src/client/views/linking/LinkFollowBox.tsx b/src/client/views/linking/LinkFollowBox.tsx index 7fc4449d3..247b67776 100644 --- a/src/client/views/linking/LinkFollowBox.tsx +++ b/src/client/views/linking/LinkFollowBox.tsx @@ -9,13 +9,22 @@ import { CollectionViewType } from "../collections/CollectionBaseView"; import { CollectionDockingView } from "../collections/CollectionDockingView"; import { SelectionManager } from "../../util/SelectionManager"; import { DocumentManager } from "../../util/DocumentManager"; +import { DocumentView } from "../nodes/DocumentView"; +import "./LinkFollowBox.scss"; + +export type LinkParamOptions = { + container: Doc; + context: Doc; + sourceDoc: Doc; + shoudldZoom: boolean; + linkDoc: Doc; +}; @observer export class LinkFollowBox extends React.Component { public static LayoutString() { return FieldView.LayoutString(LinkFollowBox); } public static Instance: LinkFollowBox; - //set this to be the default link behavior, can be any of the above unhighlight = () => { Doc.UnhighlightAll(); @@ -31,19 +40,32 @@ export class LinkFollowBox extends React.Component { }, 10000); } + // DONE + @undoBatch + openFullScreen = (destinationDoc: Doc) => { + let view: DocumentView | null = DocumentManager.Instance.getDocumentView(destinationDoc) + view && CollectionDockingView.Instance && CollectionDockingView.Instance.OpenFullScreen(view); + } + + // should container be a doc or documentview or what? This one needs work and is more long term + @undoBatch + openInContainer = (destinationDoc: Doc, options: { container: Doc }) => { + + } + // NOT TESTED // col = collection the doc is in // target = the document to center on @undoBatch - openLinkColRight = (destinationDoc: Doc, col: Doc) => { - col = Doc.IsPrototype(col) ? Doc.MakeDelegate(col) : col; - if (NumCast(col.viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) { + openLinkColRight = (destinationDoc: Doc, options: { context: Doc }) => { + options.context = Doc.IsPrototype(options.context) ? Doc.MakeDelegate(options.context) : options.context; + if (NumCast(options.context.viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) { const newPanX = NumCast(destinationDoc.x) + NumCast(destinationDoc.width) / NumCast(destinationDoc.zoomBasis, 1) / 2; const newPanY = NumCast(destinationDoc.y) + NumCast(destinationDoc.height) / NumCast(destinationDoc.zoomBasis, 1) / 2; - col.panX = newPanX; - col.panY = newPanY; + options.context.panX = newPanX; + options.context.panY = newPanY; } - CollectionDockingView.Instance.AddRightSplit(col, undefined); + CollectionDockingView.Instance.AddRightSplit(options.context, undefined); } // DONE @@ -60,39 +82,39 @@ export class LinkFollowBox extends React.Component { // this is the standard "follow link" (jump to document) // taken from follow link @undoBatch - jumpToLink = async (destinationDoc: Doc, shouldZoom: boolean, linkDoc: Doc) => { + jumpToLink = async (destinationDoc: Doc, options: { shouldZoom: boolean, linkDoc: Doc }) => { //there is an issue right now so this will be false automatically - shouldZoom = false; + options.shouldZoom = false; this.highlightDoc(destinationDoc); let jumpToDoc = destinationDoc; let pdfDoc = FieldValue(Cast(destinationDoc, Doc)); if (pdfDoc) { jumpToDoc = pdfDoc; } - let proto = Doc.GetProto(linkDoc); + let proto = Doc.GetProto(options.linkDoc); let targetContext = await Cast(proto.targetContext, Doc); let sourceContext = await Cast(proto.sourceContext, Doc); let dockingFunc = (document: Doc) => { this.props.addDocTab(document, undefined, "inTab"); SelectionManager.DeselectAll(); }; - if (destinationDoc === linkDoc.anchor2 && targetContext) { - DocumentManager.Instance.jumpToDocument(jumpToDoc, shouldZoom, false, async document => dockingFunc(document), undefined, targetContext); + if (destinationDoc === options.linkDoc.anchor2 && targetContext) { + DocumentManager.Instance.jumpToDocument(jumpToDoc, options.shouldZoom, false, async document => dockingFunc(document), undefined, targetContext); } - else if (destinationDoc === linkDoc.anchor1 && sourceContext) { - DocumentManager.Instance.jumpToDocument(jumpToDoc, shouldZoom, false, document => dockingFunc(sourceContext!)); + else if (destinationDoc === options.linkDoc.anchor1 && sourceContext) { + DocumentManager.Instance.jumpToDocument(jumpToDoc, options.shouldZoom, false, document => dockingFunc(sourceContext!)); } else if (DocumentManager.Instance.getDocumentView(jumpToDoc)) { - DocumentManager.Instance.jumpToDocument(jumpToDoc, shouldZoom, undefined, undefined, NumCast((destinationDoc === linkDoc.anchor2 ? linkDoc.anchor2Page : linkDoc.anchor1Page))); + DocumentManager.Instance.jumpToDocument(jumpToDoc, options.shouldZoom, undefined, undefined, NumCast((destinationDoc === options.linkDoc.anchor2 ? options.linkDoc.anchor2Page : options.linkDoc.anchor1Page))); } else { - DocumentManager.Instance.jumpToDocument(jumpToDoc, shouldZoom, false, dockingFunc); + DocumentManager.Instance.jumpToDocument(jumpToDoc, options.shouldZoom, false, dockingFunc); } } // DONE // opens link in new tab (not in a collection) - // this opens it full screen, do we need a separate full screen option? + // this opens it full screen in new tab @undoBatch openLinkTab = (destinationDoc: Doc) => { this.highlightDoc(destinationDoc); @@ -106,32 +128,32 @@ export class LinkFollowBox extends React.Component { // col = collection the doc is in // target = the document to center on @undoBatch - openLinkColTab = (destinationDoc: Doc, col: Doc) => { + openLinkColTab = (destinationDoc: Doc, options: { context: Doc }) => { this.highlightDoc(destinationDoc); - col = Doc.IsPrototype(col) ? Doc.MakeDelegate(col) : col; - if (NumCast(col.viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) { + options.context = Doc.IsPrototype(options.context) ? Doc.MakeDelegate(options.context) : options.context; + if (NumCast(options.context.viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) { const newPanX = NumCast(destinationDoc.x) + NumCast(destinationDoc.width) / NumCast(destinationDoc.zoomBasis, 1) / 2; const newPanY = NumCast(destinationDoc.y) + NumCast(destinationDoc.height) / NumCast(destinationDoc.zoomBasis, 1) / 2; - col.panX = newPanX; - col.panY = newPanY; + options.context.panX = newPanX; + options.context.panY = newPanY; } // CollectionDockingView.Instance.AddRightSplit(col, undefined); - this.props.addDocTab(col, undefined, "inTab"); + this.props.addDocTab(options.context, undefined, "inTab"); SelectionManager.DeselectAll(); } // DONE // this will open a link next to the source doc @undoBatch - openLinkInPlace = (destinationDoc: Doc, sourceDoc: Doc) => { + openLinkInPlace = (destinationDoc: Doc, options: { sourceDoc: Doc }) => { this.highlightDoc(destinationDoc); let alias = Doc.MakeAlias(destinationDoc); - let y = NumCast(sourceDoc.y); - let x = NumCast(sourceDoc.x); + let y = NumCast(options.sourceDoc.y); + let x = NumCast(options.sourceDoc.x); - let width = NumCast(sourceDoc.width); - let height = NumCast(sourceDoc.height); + let width = NumCast(options.sourceDoc.width); + let height = NumCast(options.sourceDoc.height); alias.x = x + width + 30; alias.y = y; @@ -139,18 +161,19 @@ export class LinkFollowBox extends React.Component { alias.height = height; SelectionManager.SelectedDocuments().map(dv => { - if (dv.props.Document === sourceDoc) { + if (dv.props.Document === options.sourceDoc) { dv.props.addDocument && dv.props.addDocument(alias, false); } }); } - private defaultLinkBehavior: any = this.openLinkInPlace; - private currentLinkBehavior: any = this.defaultLinkBehavior; + //set this to be the default link behavior, can be any of the above + private defaultLinkBehavior: (destinationDoc: Doc, options?: any) => void = this.openLinkInPlace; + private currentLinkBehavior: (destinationDoc: Doc, options?: any) => void = this.defaultLinkBehavior; render() { return ( -
+
); diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx index 41723030d..60f27c664 100644 --- a/src/client/views/linking/LinkMenuItem.tsx +++ b/src/client/views/linking/LinkMenuItem.tsx @@ -15,6 +15,7 @@ import { CollectionDockingView } from '../collections/CollectionDockingView'; import { SelectionManager } from '../../util/SelectionManager'; import { CollectionViewType } from '../collections/CollectionBaseView'; import { DocumentView } from '../nodes/DocumentView'; +import { SearchUtil } from '../../util/SearchUtil'; library.add(faEye, faEdit, faTimes, faArrowRight, faChevronDown, faChevronUp); @@ -63,6 +64,13 @@ export class LinkMenuItem extends React.Component { CollectionDockingView.Instance.AddRightSplit(col, undefined); } + // DONE + @undoBatch + openFullScreen = () => { + let view: DocumentView | null = DocumentManager.Instance.getDocumentView(this.props.destinationDoc); + view && CollectionDockingView.Instance && CollectionDockingView.Instance.OpenFullScreen(view); + } + // DONE // this opens the linked doc in a right split, NOT in its collection @undoBatch @@ -161,10 +169,12 @@ export class LinkMenuItem extends React.Component { dv.props.addDocument && dv.props.addDocument(alias, false); } }); + + this.jumpToLink(false); } //set this to be the default link behavior, can be any of the above - private defaultLinkBehavior: any = this.openLinkInPlace; + private defaultLinkBehavior: any = this.openLinkTab; onEdit = (e: React.PointerEvent): void => { e.stopPropagation(); -- cgit v1.2.3-70-g09d2 From ed7f9a9cd3255f7b774268cfda35685ddacfe2e9 Mon Sep 17 00:00:00 2001 From: monikahedman Date: Fri, 23 Aug 2019 16:38:27 -0400 Subject: basic menu and setting docs working --- src/client/documents/Documents.ts | 1 + src/client/views/MainView.tsx | 6 +- src/client/views/linking/LinkFollowBox.scss | 77 +++++++++- src/client/views/linking/LinkFollowBox.tsx | 213 +++++++++++++++++++++++----- src/client/views/linking/LinkMenuItem.tsx | 5 +- 5 files changed, 260 insertions(+), 42 deletions(-) (limited to 'src/client/views/linking') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index b7c8f4c12..e903d1e06 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -54,6 +54,7 @@ var path = require('path'); export interface DocumentOptions { x?: number; y?: number; + z?: number; type?: string; width?: number; height?: number; diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 79c44ae72..82e3c706a 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -1,5 +1,5 @@ import { IconName, library } from '@fortawesome/fontawesome-svg-core'; -import { faArrowDown, faArrowUp, faBolt, faCaretUp, faCat, faCheck, faClone, faCloudUploadAlt, faCommentAlt, faCut, faExclamation, faFilePdf, faFilm, faFont, faGlobeAsia, faLongArrowAltRight, faMusic, faObjectGroup, faPause, faPenNib, faPlay, faPortrait, faRedoAlt, faThumbtack, faTree, faUndoAlt, faTv } from '@fortawesome/free-solid-svg-icons'; +import { faLink, faArrowDown, faArrowUp, faBolt, faCaretUp, faCat, faCheck, faClone, faCloudUploadAlt, faCommentAlt, faCut, faExclamation, faFilePdf, faFilm, faFont, faGlobeAsia, faLongArrowAltRight, faMusic, faObjectGroup, faPause, faPenNib, faPlay, faPortrait, faRedoAlt, faThumbtack, faTree, faUndoAlt, faTv } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, computed, configure, observable, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; @@ -443,7 +443,7 @@ export class MainView extends React.Component { let imgurl = "https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg"; let addColNode = action(() => Docs.Create.FreeformDocument([], { width: this.pwidth * .7, height: this.pheight, title: "a freeform collection" })); - let addLinkFollowBox = action(() => Docs.Create.LinkFollowBoxDocument({ width: 200, height: 500, title: "Link Follow Document" })); + let addLinkFollowBox = action(() => Docs.Create.LinkFollowBoxDocument({ width: 500, height: 350, title: "Link Follower" })); let addPresNode = action(() => Doc.UserDoc().curPresentation = Docs.Create.PresDocument(new List(), { width: 200, height: 500, title: "a presentation trail" })); let addWebNode = action(() => Docs.Create.WebDocument("https://en.wikipedia.org/wiki/Hedgehog", { width: 300, height: 300, title: "New Webpage" })); let addDragboxNode = action(() => Docs.Create.DragboxDocument({ width: 40, height: 40, title: "drag collection" })); @@ -459,7 +459,7 @@ export class MainView extends React.Component { [React.createRef(), "globe-asia", "Add Website", addWebNode], [React.createRef(), "bolt", "Add Button", addButtonDocument], [React.createRef(), "file", "Add Document Dragger", addDragboxNode], - [React.createRef(), "caret-up", "Open Link Follow Box", addLinkFollowBox], + [React.createRef(), "link", "Open Link Follow Box", addLinkFollowBox], [React.createRef(), "cloud-upload-alt", "Import Directory", addImportCollectionNode], //remove at some point in favor of addImportCollectionNode //[React.createRef(), "play", "Add Youtube Searcher", addYoutubeSearcher], ]; diff --git a/src/client/views/linking/LinkFollowBox.scss b/src/client/views/linking/LinkFollowBox.scss index 522191792..aedbfdea4 100644 --- a/src/client/views/linking/LinkFollowBox.scss +++ b/src/client/views/linking/LinkFollowBox.scss @@ -2,5 +2,80 @@ .linkFollowBox-main { position: absolute; - background: red; + background: whitesmoke; + color: grey; + border-radius: 15px; + box-shadow: $intermediate-color 0.2vw 0.2vw 0.4vw; + border: solid #BBBBBBBB 5px; + pointer-events: all; + // overflow: hidden; + + .linkFollowBox-header { + height: 30px; + text-align: center; + text-transform: uppercase; + line-height: 30px; + letter-spacing: 2px; + font-size: 16px; + } + + .linkFollowBox-footer { + height: 50px; + text-align: center; + display: flex; + justify-content: center; + align-items: center; + + button { + background-color: $darker-alt-accent; + // height: 30px; + width: 30%; + // font-size: 18px; + } + } + + .linkFollowBox-content { + display: grid; + grid-template-columns: 1fr 1fr 1fr; + grid-column-gap: 5px; + margin-left: 5px; + margin-right: 5px; + + .linkFollowBox-item { + background-color: $light-color; + width: 100%; + height: 100%; + + .linkFollowBox-itemContent { + padding: 5px; + font-size: 12px; + // line-height: 40px; + overflow: scroll; + + + input[type=radio] { + border: 0px; + // width: 100%; + // height: 20px; + // width: 20px; + margin-right: 5px; + } + } + + .title { + display: flex; + justify-content: center; + align-items: center; + text-transform: uppercase; + color: $light-color; + background-color: $lighter-alt-accent; + width: 100%; + height: 30px; + border-bottom: solid $darker-alt-accent 5px; + font-size: 12px; + text-align: center; + } + } + } + } \ No newline at end of file diff --git a/src/client/views/linking/LinkFollowBox.tsx b/src/client/views/linking/LinkFollowBox.tsx index 247b67776..8cd26bdec 100644 --- a/src/client/views/linking/LinkFollowBox.tsx +++ b/src/client/views/linking/LinkFollowBox.tsx @@ -1,10 +1,10 @@ -import { observable, computed, action, trace } from "mobx"; +import { observable, computed, action, trace, ObservableMap } from "mobx"; import React = require("react"); import { observer } from "mobx-react"; import { FieldViewProps, FieldView } from "../nodes/FieldView"; import { Doc } from "../../../new_fields/Doc"; import { undoBatch } from "../../util/UndoManager"; -import { NumCast, FieldValue, Cast } from "../../../new_fields/Types"; +import { NumCast, FieldValue, Cast, StrCast } from "../../../new_fields/Types"; import { CollectionViewType } from "../collections/CollectionBaseView"; import { CollectionDockingView } from "../collections/CollectionDockingView"; import { SelectionManager } from "../../util/SelectionManager"; @@ -12,19 +12,41 @@ import { DocumentManager } from "../../util/DocumentManager"; import { DocumentView } from "../nodes/DocumentView"; import "./LinkFollowBox.scss"; -export type LinkParamOptions = { - container: Doc; - context: Doc; - sourceDoc: Doc; - shoudldZoom: boolean; - linkDoc: Doc; -}; +enum FollowModes { + OPENTAB = "Open in Tab", + OPENRIGHT = "Open in Right Split", + OPENFULL = "Open Full Screen", + PAN = "Pan to Document", + INPLACE = "Open In Place" +} + +enum FollowOptions { + ZOOM = "zoom", + NOZOOM = "no zoom", +} @observer export class LinkFollowBox extends React.Component { public static LayoutString() { return FieldView.LayoutString(LinkFollowBox); } public static Instance: LinkFollowBox; + @observable static linkDoc: Doc | undefined = undefined; + @observable static destinationDoc: Doc | undefined = undefined; + @observable static sourceDoc: Doc | undefined = undefined; + @observable selectedMode: string = ""; + @observable selectedOption: string = ""; + + constructor(props: FieldViewProps) { + super(props); + LinkFollowBox.Instance = this; + } + + @action + setLinkDocs = (linkDoc: Doc, source: Doc, dest: Doc) => { + LinkFollowBox.linkDoc = linkDoc; + LinkFollowBox.sourceDoc = source; + LinkFollowBox.destinationDoc = dest; + } unhighlight = () => { Doc.UnhighlightAll(); @@ -40,11 +62,11 @@ export class LinkFollowBox extends React.Component { }, 10000); } - // DONE @undoBatch openFullScreen = (destinationDoc: Doc) => { - let view: DocumentView | null = DocumentManager.Instance.getDocumentView(destinationDoc) + let view: DocumentView | null = DocumentManager.Instance.getDocumentView(destinationDoc); view && CollectionDockingView.Instance && CollectionDockingView.Instance.OpenFullScreen(view); + SelectionManager.DeselectAll(); } // should container be a doc or documentview or what? This one needs work and is more long term @@ -54,8 +76,6 @@ export class LinkFollowBox extends React.Component { } // NOT TESTED - // col = collection the doc is in - // target = the document to center on @undoBatch openLinkColRight = (destinationDoc: Doc, options: { context: Doc }) => { options.context = Doc.IsPrototype(options.context) ? Doc.MakeDelegate(options.context) : options.context; @@ -66,26 +86,22 @@ export class LinkFollowBox extends React.Component { options.context.panY = newPanY; } CollectionDockingView.Instance.AddRightSplit(options.context, undefined); + + this.highlightDoc(destinationDoc); + SelectionManager.DeselectAll(); } - // DONE - // this opens the linked doc in a right split, NOT in its collection @undoBatch openLinkRight = (destinationDoc: Doc) => { - this.highlightDoc(destinationDoc); let alias = Doc.MakeAlias(destinationDoc); CollectionDockingView.Instance.AddRightSplit(alias, undefined); + this.highlightDoc(destinationDoc); SelectionManager.DeselectAll(); + } - // DONE - // this is the standard "follow link" (jump to document) - // taken from follow link @undoBatch jumpToLink = async (destinationDoc: Doc, options: { shouldZoom: boolean, linkDoc: Doc }) => { - //there is an issue right now so this will be false automatically - options.shouldZoom = false; - this.highlightDoc(destinationDoc); let jumpToDoc = destinationDoc; let pdfDoc = FieldValue(Cast(destinationDoc, Doc)); if (pdfDoc) { @@ -110,26 +126,23 @@ export class LinkFollowBox extends React.Component { else { DocumentManager.Instance.jumpToDocument(jumpToDoc, options.shouldZoom, false, dockingFunc); } + + this.highlightDoc(destinationDoc); + SelectionManager.DeselectAll(); } - // DONE - // opens link in new tab (not in a collection) - // this opens it full screen in new tab @undoBatch openLinkTab = (destinationDoc: Doc) => { - this.highlightDoc(destinationDoc); let fullScreenAlias = Doc.MakeAlias(destinationDoc); this.props.addDocTab(fullScreenAlias, undefined, "inTab"); + + this.highlightDoc(destinationDoc); SelectionManager.DeselectAll(); } // NOT TESTED - // opens link in new tab in collection - // col = collection the doc is in - // target = the document to center on @undoBatch openLinkColTab = (destinationDoc: Doc, options: { context: Doc }) => { - this.highlightDoc(destinationDoc); options.context = Doc.IsPrototype(options.context) ? Doc.MakeDelegate(options.context) : options.context; if (NumCast(options.context.viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) { const newPanX = NumCast(destinationDoc.x) + NumCast(destinationDoc.width) / NumCast(destinationDoc.zoomBasis, 1) / 2; @@ -137,16 +150,14 @@ export class LinkFollowBox extends React.Component { options.context.panX = newPanX; options.context.panY = newPanY; } - // CollectionDockingView.Instance.AddRightSplit(col, undefined); this.props.addDocTab(options.context, undefined, "inTab"); + + this.highlightDoc(destinationDoc); SelectionManager.DeselectAll(); } - // DONE - // this will open a link next to the source doc @undoBatch - openLinkInPlace = (destinationDoc: Doc, options: { sourceDoc: Doc }) => { - this.highlightDoc(destinationDoc); + openLinkInPlace = (destinationDoc: Doc, options: { sourceDoc: Doc, linkDoc: Doc }) => { let alias = Doc.MakeAlias(destinationDoc); let y = NumCast(options.sourceDoc.y); @@ -165,16 +176,146 @@ export class LinkFollowBox extends React.Component { dv.props.addDocument && dv.props.addDocument(alias, false); } }); + + this.jumpToLink(destinationDoc, { shouldZoom: false, linkDoc: options.linkDoc }); + + this.highlightDoc(destinationDoc); + SelectionManager.DeselectAll(); } //set this to be the default link behavior, can be any of the above private defaultLinkBehavior: (destinationDoc: Doc, options?: any) => void = this.openLinkInPlace; - private currentLinkBehavior: (destinationDoc: Doc, options?: any) => void = this.defaultLinkBehavior; + // private currentLinkBehavior: (destinationDoc: Doc, options?: any) => void = this.defaultLinkBehavior; + + @computed + get LinkFollowTitle(): string { + if (LinkFollowBox.linkDoc) { + return StrCast(LinkFollowBox.linkDoc.title); + } + return "No Link Selected"; + } + + @action + currentLinkBehavior = () => { + if (this.selectedMode === FollowModes.INPLACE) { + + } + else if (this.selectedMode === FollowModes.OPENFULL) { + + } + else if (this.selectedMode === FollowModes.OPENRIGHT) { + + } + else if (this.selectedMode === FollowModes.OPENTAB) { + + } + else if (this.selectedMode === FollowModes.INPLACE) { + + } + else if (this.selectedMode === FollowModes.PAN) { + + } + else return; + } + + @action + handleModeChange = (e: React.ChangeEvent) => { + let target = e.target as HTMLInputElement; + this.selectedMode = target.value; + } + + @action + handleOptionChange = (e: React.ChangeEvent) => { + let target = e.target as HTMLInputElement; + this.selectedOption = target.value; + } + + @computed + get availableModes() { + return ( +
+
+
+
+
+
+
+ ); + } + + @computed + get contexts() { + return ( +
+ +
+ ); + } render() { return (
+
{this.LinkFollowTitle}
+
+
+
Mode
+
{this.availableModes}
+
+
+
Context
+
+ +
+
+
+
Options
+
+
+
+
+
+ +
); } diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx index 60f27c664..406429ebf 100644 --- a/src/client/views/linking/LinkMenuItem.tsx +++ b/src/client/views/linking/LinkMenuItem.tsx @@ -16,6 +16,7 @@ import { SelectionManager } from '../../util/SelectionManager'; import { CollectionViewType } from '../collections/CollectionBaseView'; import { DocumentView } from '../nodes/DocumentView'; import { SearchUtil } from '../../util/SearchUtil'; +import { LinkFollowBox } from './LinkFollowBox'; library.add(faEye, faEdit, faTimes, faArrowRight, faChevronDown, faChevronUp); @@ -174,7 +175,7 @@ export class LinkMenuItem extends React.Component { } //set this to be the default link behavior, can be any of the above - private defaultLinkBehavior: any = this.openLinkTab; + // private defaultLinkBehavior: any = LinkFollowBox.computeLinkDocs(this.props.linkDoc); onEdit = (e: React.PointerEvent): void => { e.stopPropagation(); @@ -242,7 +243,7 @@ export class LinkMenuItem extends React.Component { {/* Original */} {/*
*/} {/* New */} -
+
LinkFollowBox.Instance.setLinkDocs(this.props.linkDoc, this.props.sourceDoc, this.props.destinationDoc)}>
{this._showMore ? this.renderMetadata() : <>} -- cgit v1.2.3-70-g09d2 From c752994552be6d8a7e91093bf9d64896fd51138f Mon Sep 17 00:00:00 2001 From: monikahedman Date: Fri, 23 Aug 2019 18:36:56 -0400 Subject: finding all collections --- src/client/views/linking/LinkFollowBox.tsx | 126 ++++++++++++++++++++++++++++- src/client/views/nodes/WebBox.tsx | 1 - src/client/views/search/SearchItem.tsx | 1 + 3 files changed, 123 insertions(+), 5 deletions(-) (limited to 'src/client/views/linking') diff --git a/src/client/views/linking/LinkFollowBox.tsx b/src/client/views/linking/LinkFollowBox.tsx index 8cd26bdec..6d4f3633e 100644 --- a/src/client/views/linking/LinkFollowBox.tsx +++ b/src/client/views/linking/LinkFollowBox.tsx @@ -1,4 +1,4 @@ -import { observable, computed, action, trace, ObservableMap } from "mobx"; +import { observable, computed, action, trace, ObservableMap, runInAction } from "mobx"; import React = require("react"); import { observer } from "mobx-react"; import { FieldViewProps, FieldView } from "../nodes/FieldView"; @@ -11,6 +11,8 @@ import { SelectionManager } from "../../util/SelectionManager"; import { DocumentManager } from "../../util/DocumentManager"; import { DocumentView } from "../nodes/DocumentView"; import "./LinkFollowBox.scss"; +import { SearchUtil } from "../../util/SearchUtil"; +import { Id } from "../../../new_fields/FieldSymbols"; enum FollowModes { OPENTAB = "Open in Tab", @@ -25,6 +27,60 @@ enum FollowOptions { NOZOOM = "no zoom", } +// @observer +// export class SelectorContextMenu extends React.Component { +// @observable private _docs: { col: Doc, target: Doc }[] = []; +// @observable private _otherDocs: { col: Doc, target: Doc }[] = []; + +// constructor(props: any) { +// super(props); +// this.fetchDocuments(); +// } + +// async fetchDocuments() { +// let aliases = (await SearchUtil.GetViewsOfDocument(this.props.doc)).filter(doc => doc !== this.props.doc); +// const { docs } = await SearchUtil.Search("", true, { fq: `data_l:"${this.props.doc[Id]}"` }); +// const map: Map = new Map; +// const allDocs = await Promise.all(aliases.map(doc => SearchUtil.Search("", true, { fq: `data_l:"${doc[Id]}"` }).then(result => result.docs))); +// allDocs.forEach((docs, index) => docs.forEach(doc => map.set(doc, aliases[index]))); +// docs.forEach(doc => map.delete(doc)); +// runInAction(() => { +// this._docs = docs.filter(doc => !Doc.AreProtosEqual(doc, CollectionDockingView.Instance.props.Document)).map(doc => ({ col: doc, target: this.props.doc })); +// this._otherDocs = Array.from(map.entries()).filter(entry => !Doc.AreProtosEqual(entry[0], CollectionDockingView.Instance.props.Document)).map(([col, target]) => ({ col, target })); +// }); +// } + +// getOnClick({ col, target }: { col: Doc, target: Doc }) { +// return () => { +// col = Doc.IsPrototype(col) ? Doc.MakeDelegate(col) : col; +// if (NumCast(col.viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) { +// const newPanX = NumCast(target.x) + NumCast(target.width) / NumCast(target.zoomBasis, 1) / 2; +// const newPanY = NumCast(target.y) + NumCast(target.height) / NumCast(target.zoomBasis, 1) / 2; +// col.panX = newPanX; +// col.panY = newPanY; +// } +// CollectionDockingView.Instance.AddRightSplit(col, undefined); +// }; +// } +// render() { +// return ( +//
+//

Contexts:

+// {[...this._docs, ...this._otherDocs].map(doc => { +// let item = React.createRef(); +// return
+//
doc.col, undefined, undefined, undefined, undefined, () => SearchBox.Instance.closeSearch())}> +// +//
+// {doc.col.title} +//
; +// })} +//
+// ); +// } +// } + @observer export class LinkFollowBox extends React.Component { @@ -34,18 +90,45 @@ export class LinkFollowBox extends React.Component { @observable static destinationDoc: Doc | undefined = undefined; @observable static sourceDoc: Doc | undefined = undefined; @observable selectedMode: string = ""; + @observable selectedContext: any = undefined; @observable selectedOption: string = ""; + @observable selectedContextString: string = ""; + + @observable private _docs: { col: Doc, target: Doc }[] = []; + @observable private _otherDocs: { col: Doc, target: Doc }[] = []; constructor(props: FieldViewProps) { super(props); LinkFollowBox.Instance = this; } + async fetchDocuments() { + if (LinkFollowBox.destinationDoc) { + let dest: Doc = LinkFollowBox.destinationDoc; + let aliases = await SearchUtil.GetViewsOfDocument(Doc.GetProto(dest)); + const { docs } = await SearchUtil.Search("", true, { fq: `data_l:"${dest[Id]}"` }); + const map: Map = new Map; + const allDocs = await Promise.all(aliases.map(doc => SearchUtil.Search("", true, { fq: `data_l:"${doc[Id]}"` }).then(result => result.docs))); + allDocs.forEach((docs, index) => docs.forEach(doc => map.set(doc, aliases[index]))); + docs.forEach(doc => map.delete(doc)); + runInAction(() => { + this._docs = docs.filter(doc => !Doc.AreProtosEqual(doc, CollectionDockingView.Instance.props.Document)).map(doc => ({ col: doc, target: dest })); + this._otherDocs = Array.from(map.entries()).filter(entry => !Doc.AreProtosEqual(entry[0], CollectionDockingView.Instance.props.Document)).map(([col, target]) => ({ col, target })); + }); + } + } + @action setLinkDocs = (linkDoc: Doc, source: Doc, dest: Doc) => { + this.selectedContext = undefined; + this.selectedContextString = ""; + this.selectedMode = ""; + this.selectedOption = ""; + LinkFollowBox.linkDoc = linkDoc; LinkFollowBox.sourceDoc = source; LinkFollowBox.destinationDoc = dest; + this.fetchDocuments(); } unhighlight = () => { @@ -230,6 +313,12 @@ export class LinkFollowBox extends React.Component { this.selectedOption = target.value; } + @action + handleContextChange = (e: React.ChangeEvent) => { + let target = e.target as HTMLInputElement; + this.selectedContextString = target.value; + } + @computed get availableModes() { return ( @@ -279,7 +368,36 @@ export class LinkFollowBox extends React.Component { } @computed - get contexts() { + get availableContexts() { + return ( +
+
+ {[...this._docs, ...this._otherDocs].map(doc => { + if (doc && doc.target) { + return

; + } + })} +
+ ); + } + + @computed + get availableOptions() { return (
@@ -299,13 +417,13 @@ export class LinkFollowBox extends React.Component {
Context
- + {this.selectedMode !== "" ? this.availableContexts : "Please select a mode to view contexts"}
Options
- + {this.selectedContextString !== "" ? this.availableOptions : "Please select a context to view options"}
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index 095b0094e..29eef27a0 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -24,7 +24,6 @@ import { SelectionManager } from "../../util/SelectionManager"; import { Docs } from "../../documents/Documents"; library.add(faStickyNote); -library.add(faStickyNote) @observer export class WebBox extends React.Component { diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx index 41fc49c2e..567bc6c97 100644 --- a/src/client/views/search/SearchItem.tsx +++ b/src/client/views/search/SearchItem.tsx @@ -63,6 +63,7 @@ export class SelectorContextMenu extends React.Component { runInAction(() => { this._docs = docs.filter(doc => !Doc.AreProtosEqual(doc, CollectionDockingView.Instance.props.Document)).map(doc => ({ col: doc, target: this.props.doc })); this._otherDocs = Array.from(map.entries()).filter(entry => !Doc.AreProtosEqual(entry[0], CollectionDockingView.Instance.props.Document)).map(([col, target]) => ({ col, target })); + }); } -- cgit v1.2.3-70-g09d2 From 34699f81960504143057451a1959c80f268c07d4 Mon Sep 17 00:00:00 2001 From: monikahedman Date: Fri, 23 Aug 2019 22:18:42 -0400 Subject: pan works --- src/client/views/linking/LinkFollowBox.tsx | 358 ++++++++++++++++------------- 1 file changed, 196 insertions(+), 162 deletions(-) (limited to 'src/client/views/linking') diff --git a/src/client/views/linking/LinkFollowBox.tsx b/src/client/views/linking/LinkFollowBox.tsx index 6d4f3633e..f58e0c8b0 100644 --- a/src/client/views/linking/LinkFollowBox.tsx +++ b/src/client/views/linking/LinkFollowBox.tsx @@ -1,4 +1,4 @@ -import { observable, computed, action, trace, ObservableMap, runInAction } from "mobx"; +import { observable, computed, action, trace, ObservableMap, runInAction, reaction } from "mobx"; import React = require("react"); import { observer } from "mobx-react"; import { FieldViewProps, FieldView } from "../nodes/FieldView"; @@ -13,6 +13,7 @@ import { DocumentView } from "../nodes/DocumentView"; import "./LinkFollowBox.scss"; import { SearchUtil } from "../../util/SearchUtil"; import { Id } from "../../../new_fields/FieldSymbols"; +import { listSpec } from "../../../new_fields/Schema"; enum FollowModes { OPENTAB = "Open in Tab", @@ -27,60 +28,6 @@ enum FollowOptions { NOZOOM = "no zoom", } -// @observer -// export class SelectorContextMenu extends React.Component { -// @observable private _docs: { col: Doc, target: Doc }[] = []; -// @observable private _otherDocs: { col: Doc, target: Doc }[] = []; - -// constructor(props: any) { -// super(props); -// this.fetchDocuments(); -// } - -// async fetchDocuments() { -// let aliases = (await SearchUtil.GetViewsOfDocument(this.props.doc)).filter(doc => doc !== this.props.doc); -// const { docs } = await SearchUtil.Search("", true, { fq: `data_l:"${this.props.doc[Id]}"` }); -// const map: Map = new Map; -// const allDocs = await Promise.all(aliases.map(doc => SearchUtil.Search("", true, { fq: `data_l:"${doc[Id]}"` }).then(result => result.docs))); -// allDocs.forEach((docs, index) => docs.forEach(doc => map.set(doc, aliases[index]))); -// docs.forEach(doc => map.delete(doc)); -// runInAction(() => { -// this._docs = docs.filter(doc => !Doc.AreProtosEqual(doc, CollectionDockingView.Instance.props.Document)).map(doc => ({ col: doc, target: this.props.doc })); -// this._otherDocs = Array.from(map.entries()).filter(entry => !Doc.AreProtosEqual(entry[0], CollectionDockingView.Instance.props.Document)).map(([col, target]) => ({ col, target })); -// }); -// } - -// getOnClick({ col, target }: { col: Doc, target: Doc }) { -// return () => { -// col = Doc.IsPrototype(col) ? Doc.MakeDelegate(col) : col; -// if (NumCast(col.viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) { -// const newPanX = NumCast(target.x) + NumCast(target.width) / NumCast(target.zoomBasis, 1) / 2; -// const newPanY = NumCast(target.y) + NumCast(target.height) / NumCast(target.zoomBasis, 1) / 2; -// col.panX = newPanX; -// col.panY = newPanY; -// } -// CollectionDockingView.Instance.AddRightSplit(col, undefined); -// }; -// } -// render() { -// return ( -//
-//

Contexts:

-// {[...this._docs, ...this._otherDocs].map(doc => { -// let item = React.createRef(); -// return
-//
doc.col, undefined, undefined, undefined, undefined, () => SearchBox.Instance.closeSearch())}> -// -//
-// {doc.col.title} -//
; -// })} -//
-// ); -// } -// } - @observer export class LinkFollowBox extends React.Component { @@ -93,6 +40,8 @@ export class LinkFollowBox extends React.Component { @observable selectedContext: any = undefined; @observable selectedOption: string = ""; @observable selectedContextString: string = ""; + @observable sourceView: DocumentView | undefined = undefined; + @observable canPan: boolean = false; @observable private _docs: { col: Doc, target: Doc }[] = []; @observable private _otherDocs: { col: Doc, target: Doc }[] = []; @@ -102,6 +51,53 @@ export class LinkFollowBox extends React.Component { LinkFollowBox.Instance = this; } + componentDidMount = () => { + this.resetVars(); + + reaction( + () => LinkFollowBox.destinationDoc, + async newLinkDestination => { + if (LinkFollowBox.destinationDoc && this.sourceView && this.sourceView.props.ContainingCollectionView) { + let colView = this.sourceView.props.ContainingCollectionView; + let colDoc = colView.props.Document; + let shouldReturn = true; + if (colDoc.viewType && colDoc.viewType === 1) { + //this means its in a freeform collection + let docs = Cast(colDoc.data, listSpec(Doc), []); + let aliases = await SearchUtil.GetViewsOfDocument(Doc.GetProto(LinkFollowBox.destinationDoc)); + + aliases.forEach(alias => { + let docs2 = docs; + let res = docs2.filter(doc => doc === alias); + if (res.length > 0) { + runInAction(() => { + this.canPan = true; + shouldReturn = false; + }); + } + }); + + if (shouldReturn) { (() => { this.canPan = false; }); return; } + } + else { runInAction(() => { this.canPan = false; }); return; } + } + else { runInAction(() => { this.canPan = false; }); return; } + } + ); + } + + @action + resetVars = () => { + this.selectedContext = undefined; + this.selectedContextString = ""; + this.selectedMode = ""; + this.selectedOption = ""; + LinkFollowBox.linkDoc = undefined; + LinkFollowBox.sourceDoc = undefined; + LinkFollowBox.destinationDoc = undefined; + this.sourceView = undefined; + } + async fetchDocuments() { if (LinkFollowBox.destinationDoc) { let dest: Doc = LinkFollowBox.destinationDoc; @@ -120,15 +116,18 @@ export class LinkFollowBox extends React.Component { @action setLinkDocs = (linkDoc: Doc, source: Doc, dest: Doc) => { - this.selectedContext = undefined; - this.selectedContextString = ""; - this.selectedMode = ""; - this.selectedOption = ""; + this.resetVars(); LinkFollowBox.linkDoc = linkDoc; LinkFollowBox.sourceDoc = source; LinkFollowBox.destinationDoc = dest; this.fetchDocuments(); + + SelectionManager.SelectedDocuments().forEach(dv => { + if (dv.props.Document === LinkFollowBox.sourceDoc) { + this.sourceView = dv; + } + }); } unhighlight = () => { @@ -137,147 +136,156 @@ export class LinkFollowBox extends React.Component { } @action - highlightDoc = (destinationDoc: Doc) => { - document.removeEventListener("pointerdown", this.unhighlight); - Doc.HighlightDoc(destinationDoc); - window.setTimeout(() => { - document.addEventListener("pointerdown", this.unhighlight); - }, 10000); + highlightDoc = () => { + if (LinkFollowBox.destinationDoc) { + document.removeEventListener("pointerdown", this.unhighlight); + Doc.HighlightDoc(LinkFollowBox.destinationDoc); + window.setTimeout(() => { + document.addEventListener("pointerdown", this.unhighlight); + }, 10000); + } } @undoBatch - openFullScreen = (destinationDoc: Doc) => { - let view: DocumentView | null = DocumentManager.Instance.getDocumentView(destinationDoc); - view && CollectionDockingView.Instance && CollectionDockingView.Instance.OpenFullScreen(view); - SelectionManager.DeselectAll(); + openFullScreen = () => { + if (LinkFollowBox.destinationDoc) { + let view: DocumentView | null = DocumentManager.Instance.getDocumentView(LinkFollowBox.destinationDoc); + view && CollectionDockingView.Instance && CollectionDockingView.Instance.OpenFullScreen(view); + SelectionManager.DeselectAll(); + } } // should container be a doc or documentview or what? This one needs work and is more long term @undoBatch - openInContainer = (destinationDoc: Doc, options: { container: Doc }) => { + openInContainer = (options: { container: Doc }) => { } // NOT TESTED @undoBatch - openLinkColRight = (destinationDoc: Doc, options: { context: Doc }) => { - options.context = Doc.IsPrototype(options.context) ? Doc.MakeDelegate(options.context) : options.context; - if (NumCast(options.context.viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) { - const newPanX = NumCast(destinationDoc.x) + NumCast(destinationDoc.width) / NumCast(destinationDoc.zoomBasis, 1) / 2; - const newPanY = NumCast(destinationDoc.y) + NumCast(destinationDoc.height) / NumCast(destinationDoc.zoomBasis, 1) / 2; - options.context.panX = newPanX; - options.context.panY = newPanY; - } - CollectionDockingView.Instance.AddRightSplit(options.context, undefined); + openLinkColRight = (options: { context: Doc }) => { + if (LinkFollowBox.destinationDoc) { + options.context = Doc.IsPrototype(options.context) ? Doc.MakeDelegate(options.context) : options.context; + if (NumCast(options.context.viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) { + const newPanX = NumCast(LinkFollowBox.destinationDoc.x) + NumCast(LinkFollowBox.destinationDoc.width) / NumCast(LinkFollowBox.destinationDoc.zoomBasis, 1) / 2; + const newPanY = NumCast(LinkFollowBox.destinationDoc.y) + NumCast(LinkFollowBox.destinationDoc.height) / NumCast(LinkFollowBox.destinationDoc.zoomBasis, 1) / 2; + options.context.panX = newPanX; + options.context.panY = newPanY; + } + CollectionDockingView.Instance.AddRightSplit(options.context, undefined); - this.highlightDoc(destinationDoc); - SelectionManager.DeselectAll(); + this.highlightDoc(); + SelectionManager.DeselectAll(); + } } @undoBatch - openLinkRight = (destinationDoc: Doc) => { - let alias = Doc.MakeAlias(destinationDoc); - CollectionDockingView.Instance.AddRightSplit(alias, undefined); - this.highlightDoc(destinationDoc); - SelectionManager.DeselectAll(); + openLinkRight = () => { + if (LinkFollowBox.destinationDoc) { + let alias = Doc.MakeAlias(LinkFollowBox.destinationDoc); + CollectionDockingView.Instance.AddRightSplit(alias, undefined); + this.highlightDoc(); + SelectionManager.DeselectAll(); + } } @undoBatch - jumpToLink = async (destinationDoc: Doc, options: { shouldZoom: boolean, linkDoc: Doc }) => { - let jumpToDoc = destinationDoc; - let pdfDoc = FieldValue(Cast(destinationDoc, Doc)); - if (pdfDoc) { - jumpToDoc = pdfDoc; - } - let proto = Doc.GetProto(options.linkDoc); - let targetContext = await Cast(proto.targetContext, Doc); - let sourceContext = await Cast(proto.sourceContext, Doc); + jumpToLink = async (options: { shouldZoom: boolean }) => { + if (LinkFollowBox.destinationDoc && LinkFollowBox.linkDoc) { + let jumpToDoc: Doc = LinkFollowBox.destinationDoc; + let pdfDoc = FieldValue(Cast(LinkFollowBox.destinationDoc, Doc)); + if (pdfDoc) { + jumpToDoc = pdfDoc; + } + let proto = Doc.GetProto(LinkFollowBox.linkDoc); + let targetContext = await Cast(proto.targetContext, Doc); + let sourceContext = await Cast(proto.sourceContext, Doc); - let dockingFunc = (document: Doc) => { this.props.addDocTab(document, undefined, "inTab"); SelectionManager.DeselectAll(); }; + let dockingFunc = (document: Doc) => { this.props.addDocTab(document, undefined, "inTab"); SelectionManager.DeselectAll(); }; - if (destinationDoc === options.linkDoc.anchor2 && targetContext) { - DocumentManager.Instance.jumpToDocument(jumpToDoc, options.shouldZoom, false, async document => dockingFunc(document), undefined, targetContext); - } - else if (destinationDoc === options.linkDoc.anchor1 && sourceContext) { - DocumentManager.Instance.jumpToDocument(jumpToDoc, options.shouldZoom, false, document => dockingFunc(sourceContext!)); - } - else if (DocumentManager.Instance.getDocumentView(jumpToDoc)) { - DocumentManager.Instance.jumpToDocument(jumpToDoc, options.shouldZoom, undefined, undefined, NumCast((destinationDoc === options.linkDoc.anchor2 ? options.linkDoc.anchor2Page : options.linkDoc.anchor1Page))); + if (LinkFollowBox.destinationDoc === LinkFollowBox.linkDoc.anchor2 && targetContext) { + DocumentManager.Instance.jumpToDocument(jumpToDoc, options.shouldZoom, false, async document => dockingFunc(document), undefined, targetContext); + } + else if (LinkFollowBox.destinationDoc === LinkFollowBox.linkDoc.anchor1 && sourceContext) { + DocumentManager.Instance.jumpToDocument(jumpToDoc, options.shouldZoom, false, document => dockingFunc(sourceContext!)); + } + else if (DocumentManager.Instance.getDocumentView(jumpToDoc)) { + DocumentManager.Instance.jumpToDocument(jumpToDoc, options.shouldZoom, undefined, undefined, + NumCast((LinkFollowBox.destinationDoc === LinkFollowBox.linkDoc.anchor2 ? LinkFollowBox.linkDoc.anchor2Page : LinkFollowBox.linkDoc.anchor1Page))); - } - else { - DocumentManager.Instance.jumpToDocument(jumpToDoc, options.shouldZoom, false, dockingFunc); - } + } + else { + DocumentManager.Instance.jumpToDocument(jumpToDoc, options.shouldZoom, false, dockingFunc); + } - this.highlightDoc(destinationDoc); - SelectionManager.DeselectAll(); + this.highlightDoc(); + SelectionManager.DeselectAll(); + } } @undoBatch - openLinkTab = (destinationDoc: Doc) => { - let fullScreenAlias = Doc.MakeAlias(destinationDoc); - this.props.addDocTab(fullScreenAlias, undefined, "inTab"); + openLinkTab = () => { + if (LinkFollowBox.destinationDoc) { + let fullScreenAlias = Doc.MakeAlias(LinkFollowBox.destinationDoc); + this.props.addDocTab(fullScreenAlias, undefined, "inTab"); - this.highlightDoc(destinationDoc); - SelectionManager.DeselectAll(); + this.highlightDoc(); + SelectionManager.DeselectAll(); + } } // NOT TESTED @undoBatch - openLinkColTab = (destinationDoc: Doc, options: { context: Doc }) => { - options.context = Doc.IsPrototype(options.context) ? Doc.MakeDelegate(options.context) : options.context; - if (NumCast(options.context.viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) { - const newPanX = NumCast(destinationDoc.x) + NumCast(destinationDoc.width) / NumCast(destinationDoc.zoomBasis, 1) / 2; - const newPanY = NumCast(destinationDoc.y) + NumCast(destinationDoc.height) / NumCast(destinationDoc.zoomBasis, 1) / 2; - options.context.panX = newPanX; - options.context.panY = newPanY; - } - this.props.addDocTab(options.context, undefined, "inTab"); + openLinkColTab = (options: { context: Doc }) => { + if (LinkFollowBox.destinationDoc) { + options.context = Doc.IsPrototype(options.context) ? Doc.MakeDelegate(options.context) : options.context; + if (NumCast(options.context.viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) { + const newPanX = NumCast(LinkFollowBox.destinationDoc.x) + NumCast(LinkFollowBox.destinationDoc.width) / NumCast(LinkFollowBox.destinationDoc.zoomBasis, 1) / 2; + const newPanY = NumCast(LinkFollowBox.destinationDoc.y) + NumCast(LinkFollowBox.destinationDoc.height) / NumCast(LinkFollowBox.destinationDoc.zoomBasis, 1) / 2; + options.context.panX = newPanX; + options.context.panY = newPanY; + } + this.props.addDocTab(options.context, undefined, "inTab"); - this.highlightDoc(destinationDoc); - SelectionManager.DeselectAll(); + this.highlightDoc(); + SelectionManager.DeselectAll(); + } } @undoBatch - openLinkInPlace = (destinationDoc: Doc, options: { sourceDoc: Doc, linkDoc: Doc }) => { + openLinkInPlace = (options: { shouldZoom: boolean }) => { - let alias = Doc.MakeAlias(destinationDoc); - let y = NumCast(options.sourceDoc.y); - let x = NumCast(options.sourceDoc.x); + if (LinkFollowBox.destinationDoc && LinkFollowBox.sourceDoc) { + let alias = Doc.MakeAlias(LinkFollowBox.destinationDoc); + let y = NumCast(LinkFollowBox.sourceDoc.y); + let x = NumCast(LinkFollowBox.sourceDoc.x); - let width = NumCast(options.sourceDoc.width); - let height = NumCast(options.sourceDoc.height); + let width = NumCast(LinkFollowBox.sourceDoc.width); + let height = NumCast(LinkFollowBox.sourceDoc.height); - alias.x = x + width + 30; - alias.y = y; - alias.width = width; - alias.height = height; + alias.x = x + width + 30; + alias.y = y; + alias.width = width; + alias.height = height; - SelectionManager.SelectedDocuments().map(dv => { - if (dv.props.Document === options.sourceDoc) { - dv.props.addDocument && dv.props.addDocument(alias, false); - } - }); + SelectionManager.SelectedDocuments().map(dv => { + if (dv.props.Document === LinkFollowBox.sourceDoc) { + dv.props.addDocument && dv.props.addDocument(alias, false); + } + }); - this.jumpToLink(destinationDoc, { shouldZoom: false, linkDoc: options.linkDoc }); + this.jumpToLink({ shouldZoom: false }); - this.highlightDoc(destinationDoc); - SelectionManager.DeselectAll(); + this.highlightDoc(); + SelectionManager.DeselectAll(); + } } //set this to be the default link behavior, can be any of the above - private defaultLinkBehavior: (destinationDoc: Doc, options?: any) => void = this.openLinkInPlace; + private defaultLinkBehavior: (options?: any) => void = this.openLinkInPlace; // private currentLinkBehavior: (destinationDoc: Doc, options?: any) => void = this.defaultLinkBehavior; - @computed - get LinkFollowTitle(): string { - if (LinkFollowBox.linkDoc) { - return StrCast(LinkFollowBox.linkDoc.title); - } - return "No Link Selected"; - } - @action currentLinkBehavior = () => { if (this.selectedMode === FollowModes.INPLACE) { @@ -319,6 +327,23 @@ export class LinkFollowBox extends React.Component { this.selectedContextString = target.value; } + // async canPan() { + + // } + + @computed + get canOpenInPlace() { + if (this.sourceView && this.sourceView.props.ContainingCollectionView) { + let colView = this.sourceView.props.ContainingCollectionView; + let colDoc = colView.props.Document; + if (colDoc.viewType && colDoc.viewType === 1) { + //this means its in a freeform collection + return true; + } + } + return false; + } + @computed get availableModes() { return ( @@ -328,7 +353,8 @@ export class LinkFollowBox extends React.Component { name="mode" value={FollowModes.OPENRIGHT} checked={this.selectedMode === FollowModes.OPENRIGHT} - onChange={this.handleModeChange} /> + onChange={this.handleModeChange} + disabled={false} /> {FollowModes.OPENRIGHT}




@@ -372,7 +402,7 @@ export class LinkFollowBox extends React.Component { return (