import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { Tooltip } from '@material-ui/core'; import { action, computed, observable, runInAction } from "mobx"; import { observer } from "mobx-react"; import { Doc, DocCastAsync } from "../../fields/Doc"; import { RichTextField } from '../../fields/RichTextField'; import { Cast, NumCast, StrCast } from "../../fields/Types"; import { emptyFunction, setupMoveUpEvents, simulateMouseClick } from "../../Utils"; import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager'; import { Pulls, Pushes } from '../apis/google_docs/GoogleApiClientUtils'; import { Docs } from '../documents/Documents'; import { DocumentType } from '../documents/DocumentTypes'; import { CurrentUserUtils } from '../util/CurrentUserUtils'; import { DragManager } from '../util/DragManager'; import { SelectionManager } from '../util/SelectionManager'; import { SharingManager } from '../util/SharingManager'; import { CollectionDockingView } from './collections/CollectionDockingView'; import { TabDocView } from './collections/TabDocView'; import './DocumentButtonBar.scss'; import { MetadataEntryMenu } from './MetadataEntryMenu'; import { DocumentLinksButton } from './nodes/DocumentLinksButton'; import { DocumentView } from './nodes/DocumentView'; import { GoogleRef } from "./nodes/formattedText/FormattedTextBox"; import { TemplateMenu } from "./TemplateMenu"; import React = require("react"); import { PresBox } from './nodes/trails/PresBox'; import { undoBatch } from '../util/UndoManager'; import { CollectionViewType } from './collections/CollectionView'; import { Colors } from './global/globalEnums'; import { DashFieldView } from './nodes/formattedText/DashFieldView'; const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; const cloud: IconProp = "cloud-upload-alt"; const fetch: IconProp = "sync-alt"; enum UtilityButtonState { Default, OpenRight, OpenExternally } @observer export class DocumentButtonBar extends React.Component<{ views: () => (DocumentView | undefined)[], stack?: any }, {}> { private _dragRef = React.createRef(); 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 public isAnimatingFetch = false; @observable public isAnimatingPulse = false; @observable private openHover: UtilityButtonState = UtilityButtonState.Default; @observable public static Instance: DocumentButtonBar; public static hasPushedHack = false; public static hasPulledHack = false; constructor(props: { views: () => (DocumentView | undefined)[] }) { super(props); runInAction(() => DocumentButtonBar.Instance = this); } public startPullOutcome = action((success: boolean) => { if (!this._pullAnimating) { this._pullAnimating = true; this.pullIcon = success ? "check-circle" : "stop-circle"; setTimeout(() => runInAction(() => { this.pullIcon = "arrow-alt-circle-down"; this._pullAnimating = false; }), 1000); } }); public startPushOutcome = action((success: boolean) => { this.isAnimatingPulse = false; if (!this._pushAnimating) { this._pushAnimating = true; this.pushIcon = success ? "check-circle" : "stop-circle"; setTimeout(() => runInAction(() => { this.pushIcon = "arrow-alt-circle-up"; this._pushAnimating = false; }), 1000); } }); public setPullState = action((unchanged: boolean) => { this.isAnimatingFetch = false; if (!this._pullColorAnimating) { this._pullColorAnimating = true; this.pullColor = unchanged ? "lawngreen" : "red"; setTimeout(this.clearPullColor, 1000); } }); private clearPullColor = action(() => { this.pullColor = "white"; this._pullColorAnimating = false; }); get view0() { return this.props.views()?.[0]; } @computed get considerGoogleDocsPush() { const targetDoc = this.view0?.props.Document; const published = targetDoc && Doc.GetProto(targetDoc)[GoogleRef] !== undefined; const animation = this.isAnimatingPulse ? "shadow-pulse 1s linear infinite" : "none"; return !targetDoc ? (null) :
{`${published ? "Push" : "Publish"} to Google Docs`}
}>
{ await GoogleAuthenticationManager.Instance.fetchOrGenerateAccessToken(); !published && runInAction(() => this.isAnimatingPulse = true); DocumentButtonBar.hasPushedHack = false; targetDoc[Pushes] = NumCast(targetDoc[Pushes]) + 1; }}>
; } @computed get considerGoogleDocsPull() { const targetDoc = this.view0?.props.Document; const dataDoc = targetDoc && Doc.GetProto(targetDoc); const animation = this.isAnimatingFetch ? "spin 0.5s linear infinite" : "none"; const title = (() => { switch (this.openHover) { default: case UtilityButtonState.Default: return `${!dataDoc?.googleDocUnchanged ? "Pull from" : "Fetch"} Google Docs`; case UtilityButtonState.OpenRight: return "Open in Right Split"; case UtilityButtonState.OpenExternally: return "Open in new Browser Tab"; } })(); return !targetDoc || !dataDoc || !dataDoc[GoogleRef] ? (null) :
{title}
}>
{ if (e.altKey) { this.openHover = UtilityButtonState.OpenExternally; } else if (e.shiftKey) { this.openHover = UtilityButtonState.OpenRight; } })} onPointerLeave={action(() => this.openHover = UtilityButtonState.Default)} onClick={async e => { const googleDocUrl = `https://docs.google.com/document/d/${dataDoc[GoogleRef]}/edit`; if (e.shiftKey) { e.preventDefault(); let googleDoc = await Cast(dataDoc.googleDoc, Doc); if (!googleDoc) { const options = { _width: 600, _nativeWidth: 960, _nativeHeight: 800, useCors: false }; googleDoc = Docs.Create.WebDocument(googleDocUrl, options); dataDoc.googleDoc = googleDoc; } CollectionDockingView.AddSplit(googleDoc, "right"); } else if (e.altKey) { e.preventDefault(); window.open(googleDocUrl); } else { this.clearPullColor(); DocumentButtonBar.hasPulledHack = false; targetDoc[Pulls] = NumCast(targetDoc[Pulls]) + 1; dataDoc.googleDocUnchanged && runInAction(() => this.isAnimatingFetch = true); } }}> { switch (this.openHover) { default: case UtilityButtonState.Default: return dataDoc.googleDocUnchanged === false ? (this.pullIcon as any) : fetch; case UtilityButtonState.OpenRight: return "arrow-alt-circle-right"; case UtilityButtonState.OpenExternally: return "share"; } })()} />
; } @computed get followLinkButton() { const targetDoc = this.view0?.props.Document; return !targetDoc ? (null) : {"Set onClick to follow primary link"}}>
this.props.views().map(view => view?.docView?.toggleFollowLink(undefined, undefined, false)))}>
; } @computed get pinButton() { const targetDoc = this.view0?.props.Document; return !targetDoc ? (null) : {SelectionManager.Views().length > 1 ? "Pin multiple documents to presentation" : "Pin to presentation"}}>
TabDocView.PinDoc(this.props.views().filter(v => v).map(dv => dv!.rootDoc), {pinDocView: true}))} >
; } @computed get shareButton() { const targetDoc = this.view0?.props.Document; return !targetDoc ? (null) :
{"Open Sharing Manager"}
}>
SharingManager.Instance.open(this.view0, targetDoc)}>
; } @computed get menuButton() { const targetDoc = this.view0?.props.Document; return !targetDoc ? (null) :
{`Open Context Menu`}
}>
this.openContextMenu(e)}>
; } @computed get moreButton() { const targetDoc = this.view0?.props.Document; return !targetDoc ? (null) :
{`${CurrentUserUtils.propertiesWidth > 0 ? "Close" : "Open"} Properties Panel`}
}>
CurrentUserUtils.propertiesWidth = CurrentUserUtils.propertiesWidth > 0 ? 0 : 250)}>
; } @computed get metadataButton() { const view0 = this.view0; return !view0 ? (null) :
Show metadata panel
}>
dv).map(dv => dv!.props.Document)} suggestWithFunction /> /* tfs: @bcz This might need to be the data document? */}>
e.stopPropagation()} > {}
; } @observable _aliasDown = false; onAliasButtonDown = action((e: React.PointerEvent): void => { this.props.views()[0]?.select(false); this._tooltipOpen = false; setupMoveUpEvents(this, e, this.onAliasButtonMoved, emptyFunction, emptyFunction); }); onAliasButtonMoved = () => { if (this._dragRef.current) { const dragDocView = this.view0!; const dragData = new DragManager.DocumentDragData([dragDocView.props.Document]); const [left, top] = dragDocView.props.ScreenToLocalTransform().inverse().transformPoint(0, 0); dragData.defaultDropAction = "alias"; dragData.canEmbed = true; DragManager.StartDocumentDrag([dragDocView.ContentDiv!], dragData, left, top, { hideSource: false }); return true; } return false; } _ref = React.createRef(); @observable _tooltipOpen: boolean = false; @computed get templateButton() { const view0 = this.view0; const views = this.props.views(); return !view0 ? (null) : Tap to Customize Layout. Drag an embeddable alias} open={this._tooltipOpen} onClose={action(() => this._tooltipOpen = false)} placement="bottom">
!this._ref.current?.getBoundingClientRect().width && (this._tooltipOpen = true))} > this._aliasDown = true)} onClose={action(() => this._aliasDown = false)} content={!this._aliasDown ? (null) :
v).map(v => v as DocumentView)} />
}>
{}
; } openContextMenu = (e: React.MouseEvent) => { let child = SelectionManager.Views()[0].ContentDiv!.children[0]; while (child.children.length) { const next = Array.from(child.children).find(c => c.className?.toString().includes("SVGAnimatedString") || typeof (c.className) === "string"); if (next?.className?.toString().includes(DocumentView.ROOT_DIV)) break; if (next?.className?.toString().includes(DashFieldView.name)) break; if (next) child = next; else break; } simulateMouseClick(child, e.clientX, e.clientY - 30, e.screenX, e.screenY - 30); } render() { if (!this.view0) return (null); const isText = this.view0.props.Document[this.view0.LayoutFieldKey] instanceof RichTextField; const doc = this.view0?.props.Document; const considerPull = isText && this.considerGoogleDocsPull; const considerPush = isText && this.considerGoogleDocsPush; return
{(DocumentLinksButton.StartLink || Doc.UserDoc()["documentLinksButton-fullMenu"]) && DocumentLinksButton.StartLink !== doc ?
: (null)} {/*!Doc.UserDoc()["documentLinksButton-fullMenu"] ? (null) :
{this.templateButton}
/*
{this.metadataButton}
{this.contextButton}
*/} {!SelectionManager.Views()?.some(v => v.allLinks.length) ? (null) :
{this.followLinkButton}
}
{this.pinButton}
{!Doc.UserDoc()["documentLinksButton-fullMenu"] ? (null) :
{this.shareButton}
} {!Doc.UserDoc()["documentLinksButton-fullMenu"] ? (null) :
{this.considerGoogleDocsPush}
}
{this.considerGoogleDocsPull}
{this.menuButton}
{/* {Doc.noviceMode ? (null) :
{this.moreButton}
} */}
; } }