diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/client/documents/Documents.ts | 9 | ||||
-rw-r--r-- | src/client/views/DocumentButtonBar.tsx | 2 | ||||
-rw-r--r-- | src/client/views/GlobalKeyHandler.ts | 8 | ||||
-rw-r--r-- | src/client/views/MainView.tsx | 21 | ||||
-rw-r--r-- | src/client/views/collections/CollectionLinearView.scss | 40 | ||||
-rw-r--r-- | src/client/views/collections/CollectionLinearView.tsx | 36 | ||||
-rw-r--r-- | src/client/views/linking/LinkMenu.tsx | 7 | ||||
-rw-r--r-- | src/client/views/linking/LinkMenuGroup.tsx | 4 | ||||
-rw-r--r-- | src/client/views/linking/LinkMenuItem.scss | 1 | ||||
-rw-r--r-- | src/client/views/linking/LinkMenuItem.tsx | 33 | ||||
-rw-r--r-- | src/client/views/nodes/DocumentLinksButton.tsx | 59 | ||||
-rw-r--r-- | src/client/views/nodes/DocumentView.tsx | 6 | ||||
-rw-r--r-- | src/client/views/nodes/LinkDocPreview.tsx | 54 | ||||
-rw-r--r-- | src/client/views/nodes/formattedText/FormattedTextBox.tsx | 38 | ||||
-rw-r--r-- | src/client/views/nodes/formattedText/FormattedTextBoxComment.scss | 57 | ||||
-rw-r--r-- | src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx | 118 | ||||
-rw-r--r-- | src/fields/Doc.ts | 2 |
17 files changed, 415 insertions, 80 deletions
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 85da621e0..2e0323ede 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -1,4 +1,4 @@ -import { runInAction } from "mobx"; +import { runInAction, action } from "mobx"; import { extname, basename } from "path"; import { DateField } from "../../fields/DateField"; import { Doc, DocListCast, DocListCastAsync, Field, HeightSym, Opt, WidthSym } from "../../fields/Doc"; @@ -50,6 +50,7 @@ import { DashWebRTCVideo } from "../views/webcam/DashWebRTCVideo"; import { DocumentType } from "./DocumentTypes"; import { Networking } from "../Network"; import { Upload } from "../../server/SharedMediaTypes"; +import { MainView } from "../views/MainView"; const path = require('path'); export interface DocumentOptions { @@ -866,6 +867,7 @@ export namespace DocUtils { export function MakeLinkToActiveAudio(doc: Doc) { DocUtils.ActiveRecordings.map(d => DocUtils.MakeLink({ doc: doc }, { doc: d }, "audio link", "audio timeline")); } + export function MakeLink(source: { doc: Doc }, target: { doc: Doc }, linkRelationship: string = "", id?: string) { const sv = DocumentManager.Instance.getDocumentView(source.doc); if (sv && sv.props.ContainingCollectionDoc === target.doc) return; @@ -877,6 +879,11 @@ export namespace DocUtils { Doc.GetProto(source.doc).links = ComputedField.MakeFunction("links(self)"); Doc.GetProto(target.doc).links = ComputedField.MakeFunction("links(self)"); + + runInAction(() => { MainView.linkCreated = true; }); + console.log("link created"); + runInAction(() => { setTimeout("MainView.changeLinkCreated()", 2000); }); + return linkDoc; } diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index c05ca33fb..d544e13eb 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -271,7 +271,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV const considerPush = isText && this.considerGoogleDocsPush; return <div className="documentButtonBar"> <div className="documentButtonBar-button"> - <DocumentLinksButton View={this.view0} AlwaysOn={true} /> + <DocumentLinksButton View={this.view0} AlwaysOn={true} InMenu={true} /> </div> <div className="documentButtonBar-button"> {this.templateButton} diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts index c85849adb..369d6f4ae 100644 --- a/src/client/views/GlobalKeyHandler.ts +++ b/src/client/views/GlobalKeyHandler.ts @@ -79,7 +79,15 @@ export default class KeyManager { // MarqueeView.DragMarquee = !MarqueeView.DragMarquee; // bcz: this needs a better disclosure UI break; case "escape": + // if (DocumentLinksButton.StartLink) { + // if (DocumentLinksButton.StartLink.Document) { + // action((e: React.PointerEvent<HTMLDivElement>) => { + // Doc.UnBrushDoc(DocumentLinksButton.StartLink?.Document as Doc); + // }); + // } + // } DocumentLinksButton.StartLink = undefined; + const main = MainView.Instance; Doc.SetSelectedTool(InkTool.None); var doDeselect = true; diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index f6db1af66..4898e114a 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -4,7 +4,7 @@ import { faTerminal, faToggleOn, faFile as fileSolid, faExternalLinkAlt, faLocationArrow, faSearch, faFileDownload, faStop, faCalculator, faWindowMaximize, faAddressCard, faQuestionCircle, faArrowLeft, faArrowRight, faArrowDown, faArrowUp, faBolt, faBullseye, faCaretUp, faCat, faCheck, faChevronRight, faClipboard, faClone, faCloudUploadAlt, faCommentAlt, faCompressArrowsAlt, faCut, faEllipsisV, faEraser, faExclamation, faFileAlt, faFileAudio, faFilePdf, faFilm, faFilter, faFont, faGlobeAsia, faHighlighter, - faLongArrowAltRight, faMicrophone, faMousePointer, faMusic, faObjectGroup, faPause, faPen, faPenNib, faPhone, faPlay, faPortrait, faRedoAlt, faStamp, faStickyNote, + faLongArrowAltRight, faMicrophone, faMousePointer, faMusic, faObjectGroup, faPause, faPen, faPenNib, faPhone, faPlay, faPortrait, faRedoAlt, faStamp, faStickyNote, faTimesCircle, faThumbtack, faTree, faTv, faUndoAlt, faVideo, faAsterisk, faBrain, faImage, faPaintBrush, faTimes, faEye, faArrowsAlt, faQuoteLeft, faSortAmountDown, faAlignLeft, faAlignCenter, faAlignRight } from '@fortawesome/free-solid-svg-icons'; import { ANTIMODEMENU_HEIGHT } from './globalCssVariables.scss'; @@ -59,6 +59,7 @@ import { DocumentManager } from '../util/DocumentManager'; import { DocumentLinksButton } from './nodes/DocumentLinksButton'; import { LinkMenu } from './linking/LinkMenu'; import { LinkDocPreview } from './nodes/LinkDocPreview'; +import { Fade } from '@material-ui/core'; @observer export class MainView extends React.Component { @@ -82,6 +83,8 @@ export class MainView extends React.Component { public isPointerDown = false; + @observable public static linkCreated: boolean = false; + componentDidMount() { DocServer.setPlaygroundFields(["dataTransition", "_viewTransition", "_panX", "_panY", "_viewScale", "_viewType"]); // can play with these fields on someone else's @@ -138,7 +141,7 @@ export class MainView extends React.Component { } library.add(faTasks, faEdit, faTrashAlt, faPalette, faAngleRight, faBell, faTrash, faCamera, faExpand, faCaretDown, faCaretLeft, faCaretRight, faCaretSquareDown, faCaretSquareRight, faArrowsAltH, faPlus, faMinus, - faTerminal, faToggleOn, faExternalLinkAlt, faLocationArrow, faSearch, faFileDownload, faStop, faCalculator, faWindowMaximize, faAddressCard, fileSolid, + faTerminal, faToggleOn, faExternalLinkAlt, faLocationArrow, faSearch, faFileDownload, faStop, faCalculator, faTimesCircle, faWindowMaximize, faAddressCard, fileSolid, faQuestionCircle, faArrowLeft, faArrowRight, faArrowDown, faArrowUp, faBolt, faBullseye, faCaretUp, faCat, faCheck, faChevronRight, faClipboard, faClone, faCloudUploadAlt, faCommentAlt, faCompressArrowsAlt, faCut, faEllipsisV, faEraser, faExclamation, faFileAlt, faFileAudio, faFilePdf, faFilm, faFilter, faFont, faGlobeAsia, faHighlighter, faLongArrowAltRight, faMicrophone, faMousePointer, faMusic, faObjectGroup, faPause, faPen, faPenNib, faPhone, faPlay, faPortrait, faRedoAlt, faStamp, faStickyNote, faTrashAlt, faAngleRight, faBell, @@ -363,6 +366,11 @@ export class MainView extends React.Component { } @action + public static changeLinkCreated = () => { + MainView.linkCreated = !MainView.linkCreated; + } + + @action onPointerMove = (e: PointerEvent) => { this.flyoutWidth = Math.max(e.clientX, 0); Math.abs(this.flyoutWidth - this._flyoutSizeOnDown) > 6 && (this._canClick = false); @@ -606,6 +614,15 @@ export class MainView extends React.Component { {this.mainContent} </GestureOverlay> <PreviewCursor /> + {MainView.linkCreated ? <div className="mainView-linkCreated" style={{ + border: "2px solid red", top: screenTop / 2, + left: screenLeft / 2, width: "50px", height: "20px", zIndex: 10000, + fontSize: "12px" + }}> + {/* <Fade in={MainView.linkCreated}> */} + link created! + {/* </Fade> */} + </div> : null} {DocumentLinksButton.EditLink ? <LinkMenu location={DocumentLinksButton.EditLinkLoc} docView={DocumentLinksButton.EditLink} addDocTab={DocumentLinksButton.EditLink.props.addDocTab} changeFlyout={emptyFunction} /> : (null)} {LinkDocPreview.LinkInfo ? <LinkDocPreview location={LinkDocPreview.LinkInfo.Location} backgroundColor={this.defaultBackgroundColors} linkDoc={LinkDocPreview.LinkInfo.linkDoc} linkSrc={LinkDocPreview.LinkInfo.linkSrc} href={LinkDocPreview.LinkInfo.href} diff --git a/src/client/views/collections/CollectionLinearView.scss b/src/client/views/collections/CollectionLinearView.scss index 123a27deb..134d62d4f 100644 --- a/src/client/views/collections/CollectionLinearView.scss +++ b/src/client/views/collections/CollectionLinearView.scss @@ -1,12 +1,27 @@ @import "../globalCssVariables"; @import "../_nodeModuleOverrides"; -.collectionLinearView-outer{ +.collectionLinearView-outer { overflow: hidden; - height:100%; + height: 100%; + .collectionLinearView { - display:flex; + display: flex; height: 100%; + + >span { + + margin-top: 8px; + margin-left: 4px; + //margin-bottom: 2px; + background: $dark-color; + color: $light-color; + display: inline-block; + border-radius: 18px; + margin-right: 6px; + cursor: pointer; + } + >label { margin-top: "auto"; margin-bottom: "auto"; @@ -17,15 +32,15 @@ font-size: 12.5px; width: 18px; height: 18px; - margin-top:auto; - margin-bottom:auto; + margin-top: auto; + margin-bottom: auto; margin-right: 3px; cursor: pointer; transition: transform 0.2s; } label p { - padding-left:5px; + padding-left: 5px; } label:hover { @@ -36,6 +51,7 @@ >input { display: none; } + >input:not(:checked)~.collectionLinearView-content { display: none; } @@ -52,12 +68,14 @@ position: relative; margin-top: auto; - .collectionLinearView-docBtn, .collectionLinearView-docBtn-scalable { - position:relative; - margin:auto; + .collectionLinearView-docBtn, + .collectionLinearView-docBtn-scalable { + position: relative; + margin: auto; margin-left: 3px; transform-origin: center 80%; } + .collectionLinearView-docBtn-scalable:hover { transform: scale(1.15); } @@ -68,10 +86,10 @@ border-radius: 18px; font-size: 15px; } - + .collectionLinearView-round-button:hover { transform: scale(1.15); } } } -} +}
\ No newline at end of file diff --git a/src/client/views/collections/CollectionLinearView.tsx b/src/client/views/collections/CollectionLinearView.tsx index f38eeaf41..a180991d0 100644 --- a/src/client/views/collections/CollectionLinearView.tsx +++ b/src/client/views/collections/CollectionLinearView.tsx @@ -13,6 +13,8 @@ import { CollectionSubView } from './CollectionSubView'; import { DocumentView } from '../nodes/DocumentView'; import { documentSchema } from '../../../fields/documentSchemas'; import { Id } from '../../../fields/FieldSymbols'; +import { DocumentLinksButton } from '../nodes/DocumentLinksButton'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; type LinearDocument = makeInterface<[typeof documentSchema,]>; @@ -75,6 +77,18 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) { return new Transform(-translateX, -translateY, 1); } + @action + exitLongLinks = () => { + if (DocumentLinksButton.StartLink) { + if (DocumentLinksButton.StartLink.Document) { + action((e: React.PointerEvent<HTMLDivElement>) => { + Doc.UnBrushDoc(DocumentLinksButton.StartLink?.Document as Doc); + }); + } + } + DocumentLinksButton.StartLink = undefined; + } + render() { const guid = Utils.GenerateGuid(); const flexDir: any = StrCast(this.Document.flexDirection); @@ -82,7 +96,12 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) { const color = StrCast(this.props.Document.color, "white"); return <div className="collectionLinearView-outer"> <div className="collectionLinearView" ref={this.createDashEventsTarget} > - <label htmlFor={`${guid}`} title="Close Menu" style={{ background: backgroundColor === color ? "black" : backgroundColor }} + <label htmlFor={`${guid}`} title="Close Menu" style={{ + background: backgroundColor === color ? "black" : backgroundColor, + // width: "18px", height: "18px", fontSize: "12.5px", + // transition: this.props.Document.linearViewIsExpanded ? "transform 0.2s" : "transform 0.5s", + // transform: this.props.Document.linearViewIsExpanded ? "" : "rotate(45deg)" + }} onPointerDown={e => e.stopPropagation()} > <p>+</p> </label> @@ -130,6 +149,21 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) { </div>; })} </div> + {DocumentLinksButton.StartLink ? <span style={{ + background: backgroundColor === color ? "black" : backgroundColor, + }} + onPointerDown={e => e.stopPropagation()} > + <span style={{ + fontSize: "12.5px", display: "inline", whiteSpace: "nowrap", + paddingLeft: "8px", paddingRight: "8px", paddingTop: "4px", + paddingBottom: "10px" + }}> + Creating link from: {DocumentLinksButton.StartLink.title} </span> + + <FontAwesomeIcon icon="times-circle" size="lg" style={{ color: "red" }} + onClick={this.exitLongLinks} /> + + </span> : null} </div> </div>; } diff --git a/src/client/views/linking/LinkMenu.tsx b/src/client/views/linking/LinkMenu.tsx index de1d60a09..c36e7b67c 100644 --- a/src/client/views/linking/LinkMenu.tsx +++ b/src/client/views/linking/LinkMenu.tsx @@ -31,6 +31,10 @@ export class LinkMenu extends React.Component<Props> { if (this._linkMenuRef?.contains(e.target as any)) { DocumentLinksButton.EditLink = undefined; } + + if (this._linkMenuRef && !this._linkMenuRef.contains(e.target as any)) { + DocumentLinksButton.EditLink = undefined; + } } @action componentDidMount() { @@ -70,7 +74,8 @@ export class LinkMenu extends React.Component<Props> { render() { const sourceDoc = this.props.docView.props.Document; const groups: Map<string, Doc[]> = LinkManager.Instance.getRelatedGroupedLinks(sourceDoc); - return <div className="linkMenu-list" ref={(r) => this._linkMenuRef = r} style={{ left: this.props.location[0], top: this.props.location[1] }}> + return <div className="linkMenu-list" + ref={(r) => this._linkMenuRef = r} style={{ left: this.props.location[0], top: this.props.location[1] }}> {!this._editingLink ? this.renderAllGroups(groups) : <LinkEditor sourceDoc={this.props.docView.props.Document} linkDoc={this._editingLink} showLinks={action(() => this._editingLink = undefined)} /> diff --git a/src/client/views/linking/LinkMenuGroup.tsx b/src/client/views/linking/LinkMenuGroup.tsx index 89deb3a55..7892d381b 100644 --- a/src/client/views/linking/LinkMenuGroup.tsx +++ b/src/client/views/linking/LinkMenuGroup.tsx @@ -80,11 +80,11 @@ export class LinkMenuGroup extends React.Component<LinkMenuGroupProps> { return ( <div className="linkMenu-group"> - <div className="linkMenu-group-name"> + {/* <div className="linkMenu-group-name"> <p ref={this._drag} onPointerDown={this.onLinkButtonDown} className={this.props.groupType === "*" || this.props.groupType === "" ? "" : "expand-one"} > {this.props.groupType}:</p> {this.props.groupType === "*" || this.props.groupType === "" ? <></> : this.viewGroupAsTable(this.props.groupType)} - </div> + </div> */} <div className="linkMenu-group-wrapper"> {groupItems} </div> diff --git a/src/client/views/linking/LinkMenuItem.scss b/src/client/views/linking/LinkMenuItem.scss index fd0954f65..1669fc39d 100644 --- a/src/client/views/linking/LinkMenuItem.scss +++ b/src/client/views/linking/LinkMenuItem.scss @@ -61,7 +61,6 @@ margin: 0; margin-right: 6px; border-radius: 50%; - cursor: pointer; pointer-events: auto; background-color: $dark-color; color: $light-color; diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx index edc18b6a9..54dca261d 100644 --- a/src/client/views/linking/LinkMenuItem.tsx +++ b/src/client/views/linking/LinkMenuItem.tsx @@ -13,6 +13,8 @@ import React = require("react"); import { DocumentManager } from '../../util/DocumentManager'; import { setupMoveUpEvents, emptyFunction } from '../../../Utils'; import { DocumentView } from '../nodes/DocumentView'; +import { DocumentLinksButton } from '../nodes/DocumentLinksButton'; +import { LinkDocPreview } from '../nodes/LinkDocPreview'; library.add(faEye, faEdit, faTimes, faArrowRight, faChevronDown, faChevronUp); @@ -124,7 +126,10 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> { e.stopPropagation(); } + @action onContextMenu = (e: React.MouseEvent) => { + DocumentLinksButton.EditLink = undefined; + LinkDocPreview.LinkInfo = undefined; e.preventDefault(); ContextMenu.Instance.addItem({ description: "Follow Default Link", event: () => this.followDefault(), icon: "arrow-right" }); ContextMenu.Instance.displayMenu(e.clientX, e.clientY); @@ -132,9 +137,20 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> { @action.bound async followDefault() { + console.log("FOLLOWWW"); + DocumentLinksButton.EditLink = undefined; + LinkDocPreview.LinkInfo = undefined; DocumentManager.Instance.FollowLink(this.props.linkDoc, this.props.sourceDoc, doc => this.props.addDocTab(doc, "onRight"), false); } + @action + deleteLink = (): void => { + LinkManager.Instance.deleteLink(this.props.linkDoc); + //this.props.showLinks(); + LinkDocPreview.LinkInfo = undefined; + DocumentLinksButton.EditLink = undefined; + } + render() { const keys = LinkManager.Instance.getMetadataKeysInGroup(this.props.groupType);//groupMetadataKeys.get(this.props.groupType); const canExpand = keys ? keys.length > 0 : false; @@ -142,13 +158,24 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> { return ( <div className="linkMenu-item"> <div className={canExpand ? "linkMenu-item-content expand-three" : "linkMenu-item-content expand-two"}> - <div ref={this._drag} className="linkMenu-name" title="drag to view target. click to customize." onPointerDown={this.onLinkButtonDown}> + <div ref={this._drag} className="linkMenu-name" title="drag to view target. click to customize." + onPointerLeave={action(() => LinkDocPreview.LinkInfo = undefined)} + onPointerEnter={action(e => this.props.linkDoc && (LinkDocPreview.LinkInfo = { + addDocTab: this.props.addDocTab, + linkSrc: this.props.sourceDoc, + linkDoc: this.props.linkDoc, + Location: [e.clientX, e.clientY + 20] + }))} + onPointerDown={this.onLinkButtonDown}> <p >{StrCast(this.props.destinationDoc.title)}</p> <div className="linkMenu-item-buttons"> {canExpand ? <div title="Show more" className="button" onPointerDown={e => this.toggleShowMore(e)}> <FontAwesomeIcon className="fa-icon" icon={this._showMore ? "chevron-up" : "chevron-down"} size="sm" /></div> : <></>} - <div title="Edit link" className="button" ref={this._editRef} onPointerDown={this.onEdit}><FontAwesomeIcon className="fa-icon" icon="edit" size="sm" /></div> - <div title="Follow link" className="button" onClick={this.followDefault} onContextMenu={this.onContextMenu}> + + {/* <div title="Edit link" className="button" ref={this._editRef} onPointerDown={this.onEdit}><FontAwesomeIcon className="fa-icon" icon="edit" size="sm" /></div> */} + <div title="Delete link" className="button" ref={this._editRef} onPointerDown={this.deleteLink}> + <FontAwesomeIcon className="fa-icon" icon="trash" size="sm" /></div> + <div title="Follow link" className="button" onClick={e => this.followDefault()} onContextMenu={this.onContextMenu}> <FontAwesomeIcon className="fa-icon" icon="arrow-right" size="sm" /> </div> </div> diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx index 4f4f12521..27fbbc7fc 100644 --- a/src/client/views/nodes/DocumentLinksButton.tsx +++ b/src/client/views/nodes/DocumentLinksButton.tsx @@ -10,6 +10,7 @@ import React = require("react"); import { DocUtils } from "../../documents/Documents"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { LinkDocPreview } from "./LinkDocPreview"; +import { MainView } from "../MainView"; const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; @@ -18,11 +19,14 @@ interface DocumentLinksButtonProps { View: DocumentView; Offset?: number[]; AlwaysOn?: boolean; + InMenu?: boolean; } @observer export class DocumentLinksButton extends React.Component<DocumentLinksButtonProps, {}> { private _linkButton = React.createRef<HTMLDivElement>(); + @observable public static StartLink: DocumentView | undefined; + @action onLinkButtonMoved = (e: PointerEvent) => { if (this._linkButton.current !== null) { @@ -50,22 +54,37 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp return false; } - @observable static StartLink: DocumentView | undefined; onLinkButtonDown = (e: React.PointerEvent): void => { setupMoveUpEvents(this, e, this.onLinkButtonMoved, emptyFunction, action((e, doubleTap) => { - if (doubleTap) { + if (doubleTap && this.props.InMenu) { + //action(() => Doc.BrushDoc(this.props.View.Document)); DocumentLinksButton.StartLink = this.props.View; - } else { + } else if (!!!this.props.InMenu) { DocumentLinksButton.EditLink = this.props.View; DocumentLinksButton.EditLinkLoc = [e.clientX + 10, e.clientY]; } })); } + + @action + onLinkClick = (e: React.MouseEvent): void => { + if (this.props.InMenu) { + DocumentLinksButton.StartLink = this.props.View; + //action(() => Doc.BrushDoc(this.props.View.Document)); + } else if (!!!this.props.InMenu) { + DocumentLinksButton.EditLink = this.props.View; + DocumentLinksButton.EditLinkLoc = [e.clientX + 10, e.clientY]; + } + } + completeLink = (e: React.PointerEvent): void => { setupMoveUpEvents(this, e, returnFalse, emptyFunction, action((e, doubleTap) => { if (doubleTap) { if (DocumentLinksButton.StartLink === this.props.View) { DocumentLinksButton.StartLink = undefined; + // action((e: React.PointerEvent<HTMLDivElement>) => { + // Doc.UnBrushDoc(this.props.View.Document); + // }); } else { DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== this.props.View && DocUtils.MakeLink({ doc: DocumentLinksButton.StartLink.props.Document }, { doc: this.props.View.props.Document }, "long drag"); @@ -74,6 +93,18 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp })); } + finishLinkClick = () => { + if (DocumentLinksButton.StartLink === this.props.View) { + DocumentLinksButton.StartLink = undefined; + // action((e: React.PointerEvent<HTMLDivElement>) => { + // Doc.UnBrushDoc(this.props.View.Document); + // }); + } else { + DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== this.props.View && + DocUtils.MakeLink({ doc: DocumentLinksButton.StartLink.props.Document }, { doc: this.props.View.props.Document }, "long drag"); + } + } + @observable public static EditLink: DocumentView | undefined; public static EditLinkLoc: number[] = [0, 0]; @@ -84,17 +115,19 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp return (!links.length || links[0].hidden) && !this.props.AlwaysOn ? (null) : <div title="Drag(create link) Tap(view links)" ref={this._linkButton} style={{ minWidth: 20, minHeight: 20, position: "absolute", left: this.props.Offset?.[0] }}> <div className={"documentLinksButton"} style={{ backgroundColor: DocumentLinksButton.StartLink ? "transparent" : "" }} - onPointerDown={this.onLinkButtonDown} - onPointerLeave={action(() => LinkDocPreview.LinkInfo = undefined)} - onPointerEnter={action(e => links.length && (LinkDocPreview.LinkInfo = { - addDocTab: this.props.View.props.addDocTab, - linkSrc: this.props.View.props.Document, - linkDoc: links[0], - Location: [e.clientX, e.clientY + 20] - }))} > - {links.length ? links.length : <FontAwesomeIcon className="documentdecorations-icon" icon="link" size="sm" />} + onPointerDown={this.onLinkButtonDown} onClick={this.onLinkClick} + // onPointerLeave={action(() => LinkDocPreview.LinkInfo = undefined)} + // onPointerEnter={action(e => links.length && (LinkDocPreview.LinkInfo = { + // addDocTab: this.props.View.props.addDocTab, + // linkSrc: this.props.View.props.Document, + // linkDoc: links[0], + // Location: [e.clientX, e.clientY + 20] + // }))} + > + {links.length && !!!this.props.InMenu ? links.length : <FontAwesomeIcon className="documentdecorations-icon" icon="link" size="sm" />} </div> - {DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== this.props.View ? <div className={"documentLinksButton-endLink"} onPointerDown={this.completeLink} /> : (null)} + {DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== this.props.View ? <div className={"documentLinksButton-endLink"} + onPointerDown={this.completeLink} onClick={this.finishLinkClick} /> : (null)} {DocumentLinksButton.StartLink === this.props.View ? <div className={"documentLinksButton-startLink"} /> : (null)} </div>; } diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 09eeaee36..0d7029576 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -116,6 +116,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer; private holdDisposer?: InteractionUtils.MultiTouchEventDisposer; + public get title() { return this.props.Document.title; } public get displayName() { return "DocumentView(" + this.props.Document.title + ")"; } // this makes mobx trace() statements more descriptive public get ContentDiv() { return this._mainCont.current; } get active() { return SelectionManager.IsSelected(this, true) || this.props.parentActive(true); } @@ -1190,7 +1191,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu id={this.props.Document[Id]} ref={this._mainCont} onKeyDown={this.onKeyDown} onContextMenu={this.onContextMenu} onPointerDown={this.onPointerDown} onClick={this.onClick} - onPointerEnter={action(() => Doc.BrushDoc(this.props.Document))} + onPointerEnter={action(() => { Doc.BrushDoc(this.props.Document); })} onPointerLeave={action((e: React.PointerEvent<HTMLDivElement>) => { let entered = false; const target = document.elementFromPoint(e.nativeEvent.x, e.nativeEvent.y); @@ -1199,7 +1200,10 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu entered = true; } } + // if (this.props.Document !== DocumentLinksButton.StartLink?.Document) { !entered && Doc.UnBrushDoc(this.props.Document); + //} + })} style={{ transformOrigin: this._animateScalingTo ? "center center" : undefined, diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx index 92b443d3b..d02896436 100644 --- a/src/client/views/nodes/LinkDocPreview.tsx +++ b/src/client/views/nodes/LinkDocPreview.tsx @@ -10,6 +10,11 @@ import { Transform } from "../../util/Transform"; import { ContentFittingDocumentView } from "./ContentFittingDocumentView"; import React = require("react"); import { DocumentView } from './DocumentView'; +import { sortAndDeduplicateDiagnostics } from 'typescript'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { LinkManager } from '../../util/LinkManager'; +import { DocumentLinksButton } from './DocumentLinksButton'; +import { ContextMenu } from '../ContextMenu'; interface Props { linkDoc?: Doc; @@ -24,6 +29,31 @@ export class LinkDocPreview extends React.Component<Props> { @observable public static LinkInfo: Opt<{ linkDoc?: Doc; addDocTab: (document: Doc, where: string) => boolean, linkSrc: Doc; href?: string; Location: number[] }>; @observable _targetDoc: Opt<Doc>; @observable _toolTipText = ""; + _editRef = React.createRef<HTMLDivElement>(); + + @action + deleteLink = (): void => { + this.props.linkDoc ? LinkManager.Instance.deleteLink(this.props.linkDoc) : null; + //this.props.showLinks(); + LinkDocPreview.LinkInfo = undefined; + DocumentLinksButton.EditLink = undefined; + } + + @action + onContextMenu = (e: React.MouseEvent) => { + DocumentLinksButton.EditLink = undefined; + LinkDocPreview.LinkInfo = undefined; + e.preventDefault(); + ContextMenu.Instance.addItem({ description: "Follow Default Link", event: () => this.followDefault(), icon: "arrow-right" }); + ContextMenu.Instance.displayMenu(e.clientX, e.clientY); + } + + @action.bound + async followDefault() { + DocumentLinksButton.EditLink = undefined; + LinkDocPreview.LinkInfo = undefined; + this._targetDoc ? DocumentManager.Instance.FollowLink(this.props.linkDoc, this._targetDoc, doc => this.props.addDocTab(doc, "onRight"), false) : null; + } componentDidUpdate() { this.updatePreview(); } componentDidMount() { this.updatePreview(); } @@ -56,15 +86,30 @@ export class LinkDocPreview extends React.Component<Props> { this.props.addDocTab(Docs.Create.WebDocument(this.props.href, { title: this.props.href, _width: 200, _height: 400, UseCors: true }), "onRight"); } } - width = () => Math.min(350, NumCast(this._targetDoc?.[WidthSym](), 350)); - height = () => Math.min(350, NumCast(this._targetDoc?.[HeightSym](), 350)); + width = () => Math.min(225, NumCast(this._targetDoc?.[WidthSym](), 225)); + height = () => Math.min(225, NumCast(this._targetDoc?.[HeightSym](), 225)); @computed get targetDocView() { return !this._targetDoc ? - <div style={{ pointerEvents: "all", maxWidth: 350, maxHeight: 250, width: "100%", height: "100%", overflow: "hidden" }}> + <div style={{ + pointerEvents: "all", maxWidth: 225, maxHeight: 225, width: "100%", height: "100%", + overflow: "hidden" + }}> <div style={{ width: "100%", height: "100%", textOverflow: "ellipsis", }} onPointerDown={this.pointerDown}> {this._toolTipText} </div> </div> : + // <div style={{ + // border: "6px solid white", + // }}> + // <div style={{ backgroundColor: "white" }}> {this._targetDoc.title} + // <div className="wrapper" style={{ float: "right" }}> + // <div title="Delete link" className="button" style={{ display: "inline" }} ref={this._editRef} onPointerDown={this.deleteLink}> + // <FontAwesomeIcon className="fa-icon" icon="trash" size="sm" /></div> + // <div title="Follow link" className="button" style={{ display: "inline" }} onClick={this.followDefault} onContextMenu={this.onContextMenu}> + // <FontAwesomeIcon className="fa-icon" icon="arrow-right" size="sm" /> + // </div> + // </div> + // </div> <ContentFittingDocumentView Document={this._targetDoc} LibraryPath={emptyPath} @@ -92,6 +137,7 @@ export class LinkDocPreview extends React.Component<Props> { NativeWidth={returnZero} NativeHeight={returnZero} />; + //</div>; } render() { @@ -99,7 +145,7 @@ export class LinkDocPreview extends React.Component<Props> { style={{ position: "absolute", left: this.props.location[0], top: this.props.location[1], width: this.width(), height: this.height(), - boxShadow: "black 2px 2px 1em" + boxShadow: "black 2px 2px 1em", zIndex: 1000 }}> {this.targetDocView} </div>; diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 9c7c46ae9..1ffa16e43 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -59,6 +59,7 @@ import { FieldView, FieldViewProps } from "../FieldView"; import "./FormattedTextBox.scss"; import { FormattedTextBoxComment, formattedTextBoxCommentPlugin, findLinkMark } from './FormattedTextBoxComment'; import React = require("react"); +import { DocumentManager } from '../../../util/DocumentManager'; library.add(faEdit); library.add(faSmile, faTextHeight, faUpload); @@ -1078,7 +1079,42 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } @action + onDoubleClick = (e: React.MouseEvent): void => { + + this.doLinkOnDeselect(); + FormattedTextBox._downEvent = true; + FormattedTextBoxComment.textBox = this; + if (this.props.onClick && e.button === 0 && !this.props.isSelected(false)) { + e.preventDefault(); + } + if (e.button === 0 && this.props.isSelected(true) && !e.altKey && !e.ctrlKey && !e.metaKey) { + if (e.clientX < this.ProseRef!.getBoundingClientRect().right) { // stop propagation if not in sidebar + e.stopPropagation(); // if the text box is selected, then it consumes all down events + } + } + if (e.button === 2 || (e.button === 0 && e.ctrlKey)) { + e.preventDefault(); + } + FormattedTextBoxComment.Hide(); + if (FormattedTextBoxComment.linkDoc) { + if (FormattedTextBoxComment.linkDoc.type !== DocumentType.LINK) { + this.props.addDocTab(FormattedTextBoxComment.linkDoc, e.ctrlKey ? "inTab" : "onRight"); + } else { + DocumentManager.Instance.FollowLink(FormattedTextBoxComment.linkDoc, this.props.Document, + (doc: Doc, followLinkLocation: string) => this.props.addDocTab(doc, e.ctrlKey ? "inTab" : followLinkLocation)); + } + } + + (e.nativeEvent as any).formattedHandled = true; + + if (e.buttons === 1 && this.props.isSelected(true) && !e.altKey) { + e.stopPropagation(); + } + } + + @action onFocused = (e: React.FocusEvent): void => { + console.log("FOUCSS"); FormattedTextBox.FocusedBox = this; this.tryUpdateHeight(); @@ -1210,6 +1246,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } public static HadSelection: boolean = false; onBlur = (e: any) => { + console.log("BLURRR"); FormattedTextBox.HadSelection = window.getSelection()?.toString() !== ""; //DictationManager.Controls.stop(false); this.endUndoTypingBatch(); @@ -1346,6 +1383,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } } })} + onDoubleClick={this.onDoubleClick} > <div className={`formattedTextBox-outer`} ref={this._scrollRef} style={{ width: `calc(100% - ${this.sidebarWidthPercent})`, pointerEvents: !this.props.isSelected() ? "none" : undefined }} diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.scss b/src/client/views/nodes/formattedText/FormattedTextBoxComment.scss index 2dd63ec21..adef7f81f 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.scss +++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.scss @@ -8,10 +8,12 @@ margin-bottom: 7px; -webkit-transform: translateX(-50%); transform: translateX(-50%); - } - .FormattedTextBox-tooltip:before { +} + +.FormattedTextBox-tooltip:before { content: ""; - height: 0; width: 0; + height: 0; + width: 0; position: absolute; left: 50%; margin-left: -5px; @@ -19,10 +21,12 @@ border: 5px solid transparent; border-bottom-width: 0; border-top-color: silver; - } - .FormattedTextBox-tooltip:after { +} + +.FormattedTextBox-tooltip:after { content: ""; - height: 0; width: 0; + height: 0; + width: 0; position: absolute; left: 50%; margin-left: -5px; @@ -30,4 +34,43 @@ border: 5px solid transparent; border-bottom-width: 0; border-top-color: white; - }
\ No newline at end of file +} + +.FormattedTextBox-buttons { + display: none; + position: absolute; + top: 50%; + right: 0; + transform: translateY(-50%); + + .FormattedTextBox-button { + width: 20px; + height: 20px; + margin: 0; + margin-right: 6px; + border-radius: 50%; + pointer-events: auto; + background-color: rgb(53, 146, 199); + color: rgb(150, 211, 248); + font-size: 65%; + transition: transform 0.2s; + text-align: center; + position: relative; + + .FormattedTextBox-fa-icon { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + } + + &:last-child { + margin-right: 0; + } + + &:hover { + background: rgb(53, 146, 199); + ; + } + } +}
\ No newline at end of file diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx index 4c90b6afd..21033f315 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx @@ -2,7 +2,7 @@ import { Mark, ResolvedPos } from "prosemirror-model"; import { EditorState, Plugin } from "prosemirror-state"; import { EditorView } from "prosemirror-view"; import * as ReactDOM from 'react-dom'; -import { Doc, DocCastAsync } from "../../../../fields/Doc"; +import { Doc, DocCastAsync, Opt } from "../../../../fields/Doc"; import { Cast, FieldValue, NumCast } from "../../../../fields/Types"; import { emptyFunction, returnEmptyString, returnFalse, Utils, emptyPath, returnZero, returnOne, returnEmptyFilter } from "../../../../Utils"; import { DocServer } from "../../../DocServer"; @@ -16,6 +16,11 @@ import React = require("react"); import { Docs } from "../../../documents/Documents"; import wiki from "wikijs"; import { DocumentType } from "../../../documents/DocumentTypes"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { action } from "mobx"; +import { LinkManager } from "../../../util/LinkManager"; +import { LinkDocPreview } from "../LinkDocPreview"; +import { DocumentLinksButton } from "../DocumentLinksButton"; export let formattedTextBoxCommentPlugin = new Plugin({ view(editorView) { return new FormattedTextBoxComment(editorView); } @@ -62,6 +67,10 @@ export class FormattedTextBoxComment { static mark: Mark; static textBox: FormattedTextBox | undefined; static linkDoc: Doc | undefined; + + static _deleteRef: Opt<HTMLDivElement | null>; + static _followRef: Opt<HTMLDivElement | null>; + constructor(view: any) { if (!FormattedTextBoxComment.tooltip) { const root = document.getElementById("root"); @@ -87,12 +96,25 @@ export class FormattedTextBoxComment { const textBox = FormattedTextBoxComment.textBox; if (FormattedTextBoxComment.linkDoc && !keep && textBox) { if (FormattedTextBoxComment.linkDoc.author) { - if (FormattedTextBoxComment.linkDoc.type !== DocumentType.LINK) { - textBox.props.addDocTab(FormattedTextBoxComment.linkDoc, e.ctrlKey ? "inTab" : "onRight"); + + if (FormattedTextBoxComment._deleteRef && FormattedTextBoxComment._deleteRef.contains(e.target as any)) { + this.deleteLink(); + } else if (FormattedTextBoxComment._followRef && FormattedTextBoxComment._followRef.contains(e.target as any)) { + if (FormattedTextBoxComment.linkDoc.type !== DocumentType.LINK) { + textBox.props.addDocTab(FormattedTextBoxComment.linkDoc, e.ctrlKey ? "inTab" : "onRight"); + } else { + DocumentManager.Instance.FollowLink(FormattedTextBoxComment.linkDoc, textBox.props.Document, + (doc: Doc, followLinkLocation: string) => textBox.props.addDocTab(doc, e.ctrlKey ? "inTab" : followLinkLocation)); + } } else { - DocumentManager.Instance.FollowLink(FormattedTextBoxComment.linkDoc, textBox.props.Document, - (doc: Doc, followLinkLocation: string) => textBox.props.addDocTab(doc, e.ctrlKey ? "inTab" : followLinkLocation)); + if (FormattedTextBoxComment.linkDoc.type !== DocumentType.LINK) { + textBox.props.addDocTab(FormattedTextBoxComment.linkDoc, e.ctrlKey ? "inTab" : "onRight"); + } else { + DocumentManager.Instance.FollowLink(FormattedTextBoxComment.linkDoc, textBox.props.Document, + (doc: Doc, followLinkLocation: string) => textBox.props.addDocTab(doc, e.ctrlKey ? "inTab" : followLinkLocation)); + } } + } } else if (textBox && (FormattedTextBoxComment.tooltipText as any).href) { textBox.props.addDocTab(Docs.Create.WebDocument((FormattedTextBoxComment.tooltipText as any).href, { title: (FormattedTextBoxComment.tooltipText as any).href, _width: 200, _height: 400, UseCors: true }), "onRight"); @@ -106,6 +128,15 @@ export class FormattedTextBoxComment { } } + @action + deleteLink = () => { + FormattedTextBoxComment.linkDoc ? LinkManager.Instance.deleteLink(FormattedTextBoxComment.linkDoc) : null; + LinkDocPreview.LinkInfo = undefined; + DocumentLinksButton.EditLink = undefined; + //FormattedTextBoxComment.tooltipText = undefined; + FormattedTextBoxComment.Hide(); + } + public static Hide() { FormattedTextBoxComment.textBox = undefined; FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = "none"); @@ -210,32 +241,55 @@ export class FormattedTextBoxComment { } if (target?.author) { FormattedTextBoxComment.showCommentbox("", view, nbef); - ReactDOM.render(<ContentFittingDocumentView - Document={target} - LibraryPath={emptyPath} - fitToBox={true} - moveDocument={returnFalse} - rootSelected={returnFalse} - ScreenToLocalTransform={Transform.Identity} - parentActive={returnFalse} - addDocument={returnFalse} - removeDocument={returnFalse} - addDocTab={returnFalse} - pinToPres={returnFalse} - dontRegisterView={true} - docFilters={returnEmptyFilter} - ContainingCollectionDoc={undefined} - ContainingCollectionView={undefined} - renderDepth={0} - PanelWidth={() => Math.min(350, NumCast(target._width, 350))} - PanelHeight={() => Math.min(250, NumCast(target._height, 250))} - focus={emptyFunction} - whenActiveChanged={returnFalse} - bringToFront={returnFalse} - ContentScaling={returnOne} - NativeWidth={returnZero} - NativeHeight={returnZero} - />, FormattedTextBoxComment.tooltipText); + const docPreview = <div style={{ backgroundColor: "white", border: "7px solid white" }}> + {target.title} + <div className="wrapper" style={{ float: "right" }}> + <div title="Delete link" className="FormattedTextBoxComment-button" style={{ + display: "inline", + paddingLeft: "5px", + paddingRight: "5px" + }} ref={(r) => this._deleteRef = r}> + <FontAwesomeIcon className="FormattedTextBox-fa-icon" icon="trash" size="sm" + /></div> + <div title="Follow link" className="FormattedTextBoxComment-button" style={{ + display: "inline", + paddingRight: "5px" + }} ref={(r) => this._followRef = r}> + <FontAwesomeIcon className="FormattedTextBox-fa-icon" icon="arrow-right" + size="sm" /> + </div> + </div> + <ContentFittingDocumentView + Document={target} + LibraryPath={emptyPath} + fitToBox={true} + moveDocument={returnFalse} + rootSelected={returnFalse} + ScreenToLocalTransform={Transform.Identity} + parentActive={returnFalse} + addDocument={returnFalse} + removeDocument={returnFalse} + addDocTab={returnFalse} + pinToPres={returnFalse} + dontRegisterView={true} + docFilters={returnEmptyFilter} + ContainingCollectionDoc={undefined} + ContainingCollectionView={undefined} + renderDepth={0} + PanelWidth={() => Math.min(350, NumCast(target._width, 350))} + PanelHeight={() => Math.min(250, NumCast(target._height, 250))} + focus={emptyFunction} + whenActiveChanged={returnFalse} + bringToFront={returnFalse} + ContentScaling={returnOne} + NativeWidth={returnZero} + NativeHeight={returnZero} + /> + </div>; + FormattedTextBoxComment.showCommentbox("", view, nbef); + + ReactDOM.render(docPreview, FormattedTextBoxComment.tooltipText); + FormattedTextBoxComment.tooltip.style.width = NumCast(target._width) ? `${NumCast(target._width)}` : "100%"; FormattedTextBoxComment.tooltip.style.height = NumCast(target._height) ? `${NumCast(target._height)}` : "100%"; } @@ -249,4 +303,4 @@ export class FormattedTextBoxComment { } destroy() { } -} +}
\ No newline at end of file diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 4a35ef018..b4bb556c7 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -832,12 +832,14 @@ export namespace Doc { })(doc); } export function BrushDoc(doc: Doc) { + console.log("brushed"); if (!doc || doc[AclSym] === AclPrivate || Doc.GetProto(doc)[AclSym] === AclPrivate) return doc; brushManager.BrushedDoc.set(doc, true); brushManager.BrushedDoc.set(Doc.GetProto(doc), true); return doc; } export function UnBrushDoc(doc: Doc) { + console.log("unbrushed"); if (!doc || doc[AclSym] === AclPrivate || Doc.GetProto(doc)[AclSym] === AclPrivate) return doc; brushManager.BrushedDoc.delete(doc); brushManager.BrushedDoc.delete(Doc.GetProto(doc)); |