aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/DocumentButtonBar.tsx
diff options
context:
space:
mode:
authorkimdahey <claire_kim1@brown.edu>2019-11-23 14:30:06 -0500
committerkimdahey <claire_kim1@brown.edu>2019-11-23 14:30:06 -0500
commit66424255021c7563df93aa9de9c1535bef1d9b50 (patch)
tree1149b7d16ab9680660aee470a34f456dae960639 /src/client/views/DocumentButtonBar.tsx
parentb4a23b21bbe0a44df1328419c7d94b97a772e54f (diff)
parent3b37cc31bb09b11238868c34a38a8e99f508479f (diff)
pulled from master
Diffstat (limited to 'src/client/views/DocumentButtonBar.tsx')
-rw-r--r--src/client/views/DocumentButtonBar.tsx366
1 files changed, 123 insertions, 243 deletions
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index ba87ecfb4..1fefc70f1 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -1,20 +1,19 @@
import { IconProp, library } from '@fortawesome/fontawesome-svg-core';
import { faArrowAltCircleDown, faArrowAltCircleUp, faCheckCircle, faCloudUploadAlt, faLink, faShare, faStopCircle, faSyncAlt, faTag, faTimes } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action, observable, runInAction } from "mobx";
+import { action, observable, runInAction, computed } from "mobx";
import { observer } from "mobx-react";
import { Doc } from "../../new_fields/Doc";
import { RichTextField } from '../../new_fields/RichTextField';
-import { NumCast } from "../../new_fields/Types";
+import { NumCast, StrCast } from "../../new_fields/Types";
import { emptyFunction } from "../../Utils";
import { Pulls, Pushes } from '../apis/google_docs/GoogleApiClientUtils';
-import { DragLinksAsDocuments, DragManager } from "../util/DragManager";
+import { DragManager } from "../util/DragManager";
import { LinkManager } from '../util/LinkManager';
import { UndoManager } from "../util/UndoManager";
import './DocumentButtonBar.scss';
import './collections/ParentDocumentSelector.scss';
import { LinkMenu } from "./linking/LinkMenu";
-import { MetadataEntryMenu } from './MetadataEntryMenu';
import { FormattedTextBox, GoogleRef } from "./nodes/FormattedTextBox";
import { TemplateMenu } from "./TemplateMenu";
import { Template, Templates } from "./Templates";
@@ -43,53 +42,53 @@ const fetch: IconProp = "sync-alt";
@observer
export class DocumentButtonBar extends React.Component<{ views: DocumentView[], stack?: any }, {}> {
private _linkButton = React.createRef<HTMLDivElement>();
- private _linkerButton = React.createRef<HTMLDivElement>();
- private _aliasButton = React.createRef<HTMLDivElement>();
- private _tooltipoff = React.createRef<HTMLDivElement>();
- private _textDoc?: Doc;
+ private _downX = 0;
+ private _downY = 0;
+ private _pullAnimating = false;
+ private _pushAnimating = false;
+ private _pullColorAnimating = false;
+
+ @observable private pushIcon: IconProp = "arrow-alt-circle-up";
+ @observable private pullIcon: IconProp = "arrow-alt-circle-down";
+ @observable private pullColor: string = "white";
+ @observable private isAnimatingFetch = false;
+ @observable private openHover = false;
+
public static Instance: DocumentButtonBar;
+ public static hasPushedHack = false;
+ public static hasPulledHack = false;
constructor(props: { views: DocumentView[] }) {
super(props);
DocumentButtonBar.Instance = this;
}
- @observable public pushIcon: IconProp = "arrow-alt-circle-up";
- @observable public pullIcon: IconProp = "arrow-alt-circle-down";
- @observable public pullColor: string = "white";
- @observable public isAnimatingFetch = false;
- @observable public openHover = false;
- public pullColorAnimating = false;
-
- private pullAnimating = false;
- private pushAnimating = false;
-
public startPullOutcome = action((success: boolean) => {
- if (!this.pullAnimating) {
- this.pullAnimating = true;
+ if (!this._pullAnimating) {
+ this._pullAnimating = true;
this.pullIcon = success ? "check-circle" : "stop-circle";
setTimeout(() => runInAction(() => {
this.pullIcon = "arrow-alt-circle-down";
- this.pullAnimating = false;
+ this._pullAnimating = false;
}), 1000);
}
});
public startPushOutcome = action((success: boolean) => {
- if (!this.pushAnimating) {
- this.pushAnimating = true;
+ if (!this._pushAnimating) {
+ this._pushAnimating = true;
this.pushIcon = success ? "check-circle" : "stop-circle";
setTimeout(() => runInAction(() => {
this.pushIcon = "arrow-alt-circle-up";
- this.pushAnimating = false;
+ this._pushAnimating = false;
}), 1000);
}
});
public setPullState = action((unchanged: boolean) => {
this.isAnimatingFetch = false;
- if (!this.pullColorAnimating) {
- this.pullColorAnimating = true;
+ if (!this._pullColorAnimating) {
+ this._pullColorAnimating = true;
this.pullColor = unchanged ? "lawngreen" : "red";
setTimeout(this.clearPullColor, 1000);
}
@@ -97,51 +96,36 @@ export class DocumentButtonBar extends React.Component<{ views: DocumentView[],
private clearPullColor = action(() => {
this.pullColor = "white";
- this.pullColorAnimating = false;
+ this._pullColorAnimating = false;
});
- onLinkerButtonDown = (e: React.PointerEvent): void => {
- e.stopPropagation();
- e.preventDefault();
- document.removeEventListener("pointermove", this.onLinkerButtonMoved);
- document.addEventListener("pointermove", this.onLinkerButtonMoved);
- document.removeEventListener("pointerup", this.onLinkerButtonUp);
- document.addEventListener("pointerup", this.onLinkerButtonUp);
- }
-
- onAliasButtonDown = (e: React.PointerEvent): void => {
- e.stopPropagation();
- e.preventDefault();
- document.removeEventListener("pointermove", this.onAliasButtonMoved);
- document.addEventListener("pointermove", this.onAliasButtonMoved);
- document.removeEventListener("pointerup", this.onAliasButtonUp);
- document.addEventListener("pointerup", this.onAliasButtonUp);
- }
-
- onLinkerButtonUp = (e: PointerEvent): void => {
- document.removeEventListener("pointermove", this.onLinkerButtonMoved);
- document.removeEventListener("pointerup", this.onLinkerButtonUp);
- e.stopPropagation();
- }
-
- onAliasButtonUp = (e: PointerEvent): void => {
- document.removeEventListener("pointermove", this.onAliasButtonMoved);
- document.removeEventListener("pointerup", this.onAliasButtonUp);
- e.stopPropagation();
- }
@action
- onLinkerButtonMoved = (e: PointerEvent): void => {
- if (this._linkerButton.current !== null) {
- document.removeEventListener("pointermove", this.onLinkerButtonMoved);
- document.removeEventListener("pointerup", this.onLinkerButtonUp);
- let selDoc = this.props.views[0];
- let container = selDoc.props.ContainingCollectionDoc ? selDoc.props.ContainingCollectionDoc.proto : undefined;
- let dragData = new DragManager.LinkDragData(selDoc.props.Document, container ? [container] : []);
- let _linkDrag = UndoManager.StartBatch("Drag Link");
- DragManager.StartLinkDrag(this._linkerButton.current, dragData, e.pageX, e.pageY, {
+ onLinkButtonMoved = (e: PointerEvent): void => {
+ if (this._linkButton.current !== null && (Math.abs(e.clientX - this._downX) > 3 || Math.abs(e.clientY - this._downY) > 3)) {
+ document.removeEventListener("pointermove", this.onLinkButtonMoved);
+ document.removeEventListener("pointerup", this.onLinkButtonUp);
+ let docView = this.props.views[0];
+ let container = docView.props.ContainingCollectionDoc?.proto;
+ let dragData = new DragManager.LinkDragData(docView.props.Document, container ? [container] : []);
+ let linkDrag = UndoManager.StartBatch("Drag Link");
+ DragManager.StartLinkDrag(this._linkButton.current, dragData, e.pageX, e.pageY, {
handlers: {
- dragComplete: () => _linkDrag && _linkDrag.end()
+ dragComplete: () => {
+ let tooltipmenu = FormattedTextBox.ToolTipTextMenu;
+ let linkDoc = dragData.linkDocument;
+ if (linkDoc && tooltipmenu) {
+ let proto = Doc.GetProto(linkDoc);
+ if (proto && docView) {
+ proto.sourceContext = docView.props.ContainingCollectionDoc;
+ }
+ let text = tooltipmenu.makeLink(linkDoc, StrCast(linkDoc.anchor2.title), e.ctrlKey ? "onRight" : "inTab");
+ if (linkDoc instanceof Doc && linkDoc.anchor2 instanceof Doc) {
+ proto.title = text === "" ? proto.title : text + " to " + linkDoc.anchor2.title; // TODODO open to more descriptive descriptions of following in text link
+ }
+ }
+ linkDrag && linkDrag.end();
+ }
},
hideSource: false
});
@@ -149,37 +133,15 @@ export class DocumentButtonBar extends React.Component<{ views: DocumentView[],
e.stopPropagation();
}
- @action
- onAliasButtonMoved = (e: PointerEvent): void => {
- if (this._aliasButton.current !== null) {
- document.removeEventListener("pointermove", this.onAliasButtonMoved);
- document.removeEventListener("pointerup", this.onAliasButtonUp);
-
- let dragDocView = this.props.views[0];
- let dragData = new DragManager.DocumentDragData([dragDocView.props.Document]);
- const [left, top] = dragDocView.props.ScreenToLocalTransform().scale(dragDocView.props.ContentScaling()).inverse().transformPoint(0, 0);
- dragData.offset = dragDocView.props.ScreenToLocalTransform().scale(dragDocView.props.ContentScaling()).transformDirection(e.clientX - left, e.clientY - top);
- dragData.embedDoc = true;
- dragData.dropAction = "alias";
- DragManager.StartDocumentDrag([dragDocView.ContentDiv!], dragData, e.x, e.y, {
- offsetX: dragData.offset[0],
- offsetY: dragData.offset[1],
- handlers: {
- dragComplete: action(emptyFunction),
- },
- hideSource: false
- });
- }
- e.stopPropagation();
- }
onLinkButtonDown = (e: React.PointerEvent): void => {
- e.stopPropagation();
- e.preventDefault();
+ this._downX = e.clientX;
+ this._downY = e.clientY;
document.removeEventListener("pointermove", this.onLinkButtonMoved);
document.addEventListener("pointermove", this.onLinkButtonMoved);
document.removeEventListener("pointerup", this.onLinkButtonUp);
document.addEventListener("pointerup", this.onLinkButtonUp);
+ e.stopPropagation();
}
onLinkButtonUp = (e: PointerEvent): void => {
@@ -188,173 +150,91 @@ export class DocumentButtonBar extends React.Component<{ views: DocumentView[],
e.stopPropagation();
}
- onLinkButtonMoved = async (e: PointerEvent) => {
- if (this._linkButton.current !== null && (e.movementX > 1 || e.movementY > 1)) {
- document.removeEventListener("pointermove", this.onLinkButtonMoved);
- document.removeEventListener("pointerup", this.onLinkButtonUp);
- DragLinksAsDocuments(this._linkButton.current, e.x, e.y, this.props.views[0].props.Document);
- }
- e.stopPropagation();
- }
-
- aliasDragger = () => {
- return (<div className="linkButtonWrapper">
- <div title="Drag Alias" className="linkButton-linker" ref={this._aliasButton} onPointerDown={this.onAliasButtonDown}>
- <FontAwesomeIcon className="documentdecorations-icon" icon="image" size="sm" />
- </div>
- </div>);
- }
-
- private get targetDoc() {
- return this.props.views[0].props.Document;
- }
-
- considerGoogleDocsPush = () => {
- let canPush = this.targetDoc.data && this.targetDoc.data instanceof RichTextField;
- if (!canPush) return (null);
- let published = Doc.GetProto(this.targetDoc)[GoogleRef] !== undefined;
- let icon: IconProp = published ? (this.pushIcon as any) : cloud;
- return (
- <div className={"linkButtonWrapper"}>
- <div title={`${published ? "Push" : "Publish"} to Google Docs`} className="linkButton-linker" onClick={() => {
- DocumentButtonBar.hasPushedHack = false;
- this.targetDoc[Pushes] = NumCast(this.targetDoc[Pushes]) + 1;
- }}>
- <FontAwesomeIcon className="documentdecorations-icon" icon={icon} size={published ? "sm" : "xs"} />
- </div>
- </div>
- );
+ @computed
+ get considerGoogleDocsPush() {
+ let targetDoc = this.props.views[0].props.Document;
+ let published = Doc.GetProto(targetDoc)[GoogleRef] !== undefined;
+ return <div title={`${published ? "Push" : "Publish"} to Google Docs`} className="documentButtonBar-linker" onClick={() => {
+ DocumentButtonBar.hasPushedHack = false;
+ targetDoc[Pushes] = NumCast(targetDoc[Pushes]) + 1;
+ }}>
+ <FontAwesomeIcon className="documentdecorations-icon" icon={published ? (this.pushIcon as any) : cloud} size={published ? "sm" : "xs"} />
+ </div>;
}
- considerGoogleDocsPull = () => {
- let canPull = this.targetDoc.data && this.targetDoc.data instanceof RichTextField;
- let dataDoc = Doc.GetProto(this.targetDoc);
- if (!canPull || !dataDoc[GoogleRef]) return (null);
- let icon = dataDoc.unchanged === false ? (this.pullIcon as any) : fetch;
- icon = this.openHover ? "share" : icon;
+ @computed
+ get considerGoogleDocsPull() {
+ let targetDoc = this.props.views[0].props.Document;
+ let dataDoc = Doc.GetProto(targetDoc);
let animation = this.isAnimatingFetch ? "spin 0.5s linear infinite" : "none";
- let title = `${!dataDoc.unchanged ? "Pull from" : "Fetch"} Google Docs`;
- return (
- <div className={"linkButtonWrapper"}>
- <div
- title={title}
- className="linkButton-linker"
- style={{
- backgroundColor: this.pullColor,
- transition: "0.2s ease all"
- }}
- onPointerEnter={e => e.altKey && runInAction(() => this.openHover = true)}
- onPointerLeave={() => runInAction(() => this.openHover = false)}
- onClick={e => {
- if (e.altKey) {
- e.preventDefault();
- window.open(`https://docs.google.com/document/d/${dataDoc[GoogleRef]}/edit`);
- } else {
- this.clearPullColor();
- DocumentButtonBar.hasPulledHack = false;
- this.targetDoc[Pulls] = NumCast(this.targetDoc[Pulls]) + 1;
- dataDoc.unchanged && runInAction(() => this.isAnimatingFetch = true);
- }
- }}>
- <FontAwesomeIcon
- style={{
- WebkitAnimation: animation,
- MozAnimation: animation
- }}
- className="documentdecorations-icon"
- icon={icon}
- size="sm"
- />
- </div>
- </div>
- );
- }
-
- public static hasPushedHack = false;
- public static hasPulledHack = false;
-
- considerTooltip = () => {
- let thisDoc = this.props.views[0].props.Document;
- let isTextDoc = thisDoc.data && thisDoc.data instanceof RichTextField;
- if (!isTextDoc) return null;
- this._textDoc = thisDoc;
- return (
- <div className="tooltipwrapper">
- <div title="Hide Tooltip" className="linkButton-linker" ref={this._tooltipoff} onPointerDown={this.onTooltipOff}>
- {/* <FontAwesomeIcon className="fa-image" icon="image" size="sm" /> */}
+ return !dataDoc[GoogleRef] ? (null) : <div className="documentButtonBar-linker"
+ title={`${!dataDoc.unchanged ? "Pull from" : "Fetch"} Google Docs`}
+ style={{ backgroundColor: this.pullColor }}
+ onPointerEnter={e => e.altKey && runInAction(() => this.openHover = true)}
+ onPointerLeave={action(() => this.openHover = false)}
+ onClick={e => {
+ if (e.altKey) {
+ e.preventDefault();
+ window.open(`https://docs.google.com/document/d/${dataDoc[GoogleRef]}/edit`);
+ } else {
+ this.clearPullColor();
+ DocumentButtonBar.hasPulledHack = false;
+ targetDoc[Pulls] = NumCast(targetDoc[Pulls]) + 1;
+ dataDoc.unchanged && runInAction(() => this.isAnimatingFetch = true);
+ }
+ }}>
+ <FontAwesomeIcon className="documentdecorations-icon" size="sm"
+ style={{ WebkitAnimation: animation, MozAnimation: animation }}
+ icon={this.openHover ? "share" : dataDoc.unchanged === false ? (this.pullIcon as any) : fetch}
+ />
+ </div>;
+ }
+
+ @computed
+ get linkButton() {
+ let linkCount = LinkManager.Instance.getAllRelatedLinks(this.props.views[0].props.Document).length;
+ return <div title="Drag(create link) Tap(view links)" className="documentButtonBar-linkFlyout" ref={this._linkButton}>
+ <Flyout anchorPoint={anchorPoints.RIGHT_TOP}
+ content={<LinkMenu docView={this.props.views[0]} addDocTab={this.props.views[0].props.addDocTab} changeFlyout={emptyFunction} />}>
+ <div className={"documentButtonBar-linkButton-" + (linkCount ? "nonempty" : "empty")} onPointerDown={this.onLinkButtonDown} >
+ {linkCount ? linkCount : <FontAwesomeIcon className="documentdecorations-icon" icon="link" size="sm" />}
</div>
- </div>
-
- );
+ </Flyout>
+ </div>;
}
- onTooltipOff = (e: React.PointerEvent): void => {
- e.stopPropagation();
- if (this._textDoc) {
- if (this._tooltipoff.current) {
- if (this._tooltipoff.current.title === "Hide Tooltip") {
- this._tooltipoff.current.title = "Show Tooltip";
- this._textDoc.tooltip = "hi";
- }
- else {
- this._tooltipoff.current.title = "Hide Tooltip";
- }
- }
- }
- }
-
- get metadataMenu() {
- return (
- <div className="linkButtonWrapper">
- <Flyout anchorPoint={anchorPoints.TOP_LEFT}
- content={<MetadataEntryMenu docs={() => this.props.views.map(dv => dv.props.Document)} suggestWithFunction />}>{/* tfs: @bcz This might need to be the data document? */}
- <div className="docDecs-tagButton" title="Add fields"><FontAwesomeIcon className="documentdecorations-icon" icon="tag" size="sm" /></div>
- </Flyout>
- </div>
- );
+ @computed
+ get contextButton() {
+ return <ParentDocSelector Views={this.props.views} Document={this.props.views[0].props.Document} addDocTab={(doc, data, where) => {
+ where === "onRight" ? CollectionDockingView.AddRightSplit(doc, data) :
+ this.props.stack ? CollectionDockingView.Instance.AddTab(this.props.stack, doc, data) :
+ this.props.views[0].props.addDocTab(doc, data, "onRight");
+ return true;
+ }} />;
}
render() {
- let linkButton = null;
- if (this.props.views.length > 0) {
- let selFirst = this.props.views[0];
-
- let linkCount = LinkManager.Instance.getAllRelatedLinks(selFirst.props.Document).length;
- linkButton = (<Flyout
- anchorPoint={anchorPoints.RIGHT_TOP}
- content={<LinkMenu docView={selFirst}
- addDocTab={selFirst.props.addDocTab}
- changeFlyout={emptyFunction} />}>
- <div className={"linkButton-" + (linkCount ? "nonempty" : "empty")} onPointerDown={this.onLinkButtonDown} >{linkCount}</div>
- </Flyout >);
- }
-
let templates: Map<Template, boolean> = new Map();
Array.from(Object.values(Templates.TemplateList)).map(template =>
templates.set(template, this.props.views.reduce((checked, doc) => checked || doc.getLayoutPropStr("show" + template.Name) ? true : false, false as boolean)));
- return (<div className="documentButtonBar">
- <div className="linkButtonWrapper">
- <div title="View Links" className="linkFlyout" ref={this._linkButton}> {linkButton} </div>
+ let isText = this.props.views[0].props.Document.data instanceof RichTextField; // bcz: Todo - can't assume layout is using the 'data' field. need to add fieldKey to DocumentView
+ let considerPull = isText && this.considerGoogleDocsPull;
+ let considerPush = isText && this.considerGoogleDocsPush;
+ return <div className="documentButtonBar">
+ <div className="documentButtonBar-button">
+ {this.linkButton}
</div>
- <div className="linkButtonWrapper">
- <div title="Drag Link" className="linkButton-linker" ref={this._linkerButton} onPointerDown={this.onLinkerButtonDown}>
- <FontAwesomeIcon className="documentdecorations-icon" icon="link" size="sm" />
- </div>
- </div>
- <div className="linkButtonWrapper">
+ <div className="documentButtonBar-button">
<TemplateMenu docs={this.props.views} templates={templates} />
</div>
- {this.metadataMenu}
- {this.aliasDragger()}
- {this.considerGoogleDocsPush()}
- {this.considerGoogleDocsPull()}
- <ParentDocSelector Document={this.props.views[0].props.Document} addDocTab={(doc, data, where) => {
- where === "onRight" ? CollectionDockingView.AddRightSplit(doc, data) : this.props.stack ? CollectionDockingView.Instance.AddTab(this.props.stack, doc, data) : this.props.views[0].props.addDocTab(doc, data, "onRight");
- return true;
- }} />
- {/* {this.considerTooltip()} */}
- </div>
- );
+ <div className="documentButtonBar-button" style={{ display: !considerPush ? "none" : "" }}>
+ {this.considerGoogleDocsPush}
+ </div>
+ <div className="documentButtonBar-button" style={{ display: !considerPull ? "none" : "" }}>
+ {this.considerGoogleDocsPull}
+ </div>
+ {this.contextButton}
+ </div>;
}
} \ No newline at end of file