diff options
Diffstat (limited to 'src/client/views/DocumentButtonBar.tsx')
-rw-r--r-- | src/client/views/DocumentButtonBar.tsx | 338 |
1 files changed, 250 insertions, 88 deletions
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index 40fc8dae6..e83ea00cf 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -1,7 +1,7 @@ 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 { action, computed, observable, runInAction, trace } from 'mobx'; import { observer } from 'mobx-react'; import { Doc } from '../../fields/Doc'; import { RichTextField } from '../../fields/RichTextField'; @@ -9,23 +9,26 @@ import { Cast, DocCast, NumCast } from '../../fields/Types'; import { emptyFunction, returnFalse, setupMoveUpEvents, simulateMouseClick } from '../../Utils'; import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager'; import { Pulls, Pushes } from '../apis/google_docs/GoogleApiClientUtils'; -import { Docs } from '../documents/Documents'; +import { Docs, DocUtils } from '../documents/Documents'; import { DragManager } from '../util/DragManager'; import { SelectionManager } from '../util/SelectionManager'; -import { SettingsManager } from '../util/SettingsManager'; import { SharingManager } from '../util/SharingManager'; -import { undoBatch } from '../util/UndoManager'; +import { undoBatch, UndoManager } from '../util/UndoManager'; import { CollectionDockingView } from './collections/CollectionDockingView'; import { TabDocView } from './collections/TabDocView'; import './DocumentButtonBar.scss'; import { Colors } from './global/globalEnums'; +import { LinkPopup } from './linking/LinkPopup'; import { MetadataEntryMenu } from './MetadataEntryMenu'; import { DocumentLinksButton } from './nodes/DocumentLinksButton'; -import { DocumentView, DocumentViewInternal } from './nodes/DocumentView'; +import { DocumentView, DocumentViewInternal, OpenWhere, OpenWhereMod } from './nodes/DocumentView'; import { DashFieldView } from './nodes/formattedText/DashFieldView'; import { GoogleRef } from './nodes/formattedText/FormattedTextBox'; import { TemplateMenu } from './TemplateMenu'; import React = require('react'); +import { DocumentType } from '../documents/DocumentTypes'; +import { FontIconBox } from './nodes/button/FontIconBox'; +import { PinProps } from './nodes/trails'; const higflyout = require('@hig/flyout'); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; @@ -158,12 +161,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV })(); return !targetDoc || !dataDoc || !dataDoc[GoogleRef] ? null : ( - <Tooltip - title={ - <> - <div className="dash-tooltip">{title}</div> - </> - }> + <Tooltip title={<div className="dash-tooltip">{title}</div>}> <div className="documentButtonBar-button" style={{ backgroundColor: this.pullColor }} @@ -185,7 +183,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV googleDoc = Docs.Create.WebDocument(googleDocUrl, options); dataDoc.googleDoc = googleDoc; } - CollectionDockingView.AddSplit(googleDoc, 'right'); + CollectionDockingView.AddSplit(googleDoc, OpenWhereMod.right); } else if (e.altKey) { e.preventDefault(); window.open(googleDocUrl); @@ -201,14 +199,12 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV size="sm" style={{ WebkitAnimation: animation, MozAnimation: animation }} icon={(() => { + // prettier-ignore 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'; + 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'; } })()} /> @@ -216,35 +212,186 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV </Tooltip> ); } + @observable subFollow = ''; @computed get followLinkButton() { const targetDoc = this.view0?.props.Document; + const followBtn = (allDocs: boolean, click: (doc: Doc) => void, isSet: (doc?: Doc) => boolean, icon: IconProp) => { + const tooltip = `Follow ${this.subPin}documents`; + return !tooltip ? null : ( + <Tooltip title={<div className="dash-tooltip">{tooltip}</div>}> + <div className="documentButtonBar-followIcon" style={{ backgroundColor: isSet(targetDoc) ? Colors.LIGHT_BLUE : Colors.DARK_GRAY, color: isSet(targetDoc) ? Colors.BLACK : Colors.WHITE }}> + <FontAwesomeIcon + className="documentdecorations-icon" + style={{ width: 20 }} + key={icon.toString()} + size="sm" + icon={icon} + onPointerEnter={action(e => (this.subPin = allDocs ? 'All ' : ''))} + onPointerLeave={action(e => (this.subPin = ''))} + onClick={e => { + this.props.views().forEach(dv => click(dv!.rootDoc)); + e.stopPropagation(); + }} + /> + </div> + </Tooltip> + ); + }; return !targetDoc ? null : ( - <Tooltip title={<div className="dash-tooltip">{'Set onClick to follow primary link'}</div>}> + <Tooltip title={<div className="dash-tooltip">Set onClick to follow primary link</div>}> <div - className="documentButtonBar-icon" + className="documentButtonBar-icon documentButtonBar-follow" style={{ backgroundColor: targetDoc.isLinkButton ? Colors.LIGHT_BLUE : Colors.DARK_GRAY, color: targetDoc.isLinkButton ? Colors.BLACK : Colors.WHITE }} - onClick={undoBatch(e => this.props.views().map(view => view?.docView?.toggleFollowLink(undefined, undefined, false)))}> + onClick={undoBatch(e => this.props.views().map(view => view?.docView?.toggleFollowLink(undefined, false)))}> + <div className="documentButtonBar-followTypes"> + {followBtn( + true, + (doc: Doc) => (doc.followAllLinks = !doc.followAllLinks), + (doc?: Doc) => (doc?.followAllLinks ? true : false), + 'window-maximize' + )} + </div> <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="hand-point-right" /> </div> </Tooltip> ); } + + @observable subLink = ''; + @computed get linkButton() { + const targetDoc = this.view0?.props.Document; + return !targetDoc || !this.view0 ? null : ( + <div className="documentButtonBar-icon documentButtonBar-link"> + <div className="documentButtonBar-linkTypes"> + <Tooltip title={<div>search for target</div>}> + <div className="documentButtonBar-button"> + <button style={{ backgroundColor: 'transparent', width: 35, height: 35, display: 'flex', justifyContent: 'center', alignItems: 'center', position: 'relative' }} onPointerDown={this.toggleLinkSearch}> + <FontAwesomeIcon style={{ position: 'absolute', transform: 'scale(1.5)' }} icon={'search'} size="lg" /> + <FontAwesomeIcon style={{ position: 'absolute', transform: 'scale(0.5)', transformOrigin: 'center', top: 9, left: 2 }} icon={'link'} size="lg" /> + </button> + </div> + </Tooltip> + <Tooltip title={<div>open linked trail</div>}> + <div className="documentButtonBar-button"> + <button style={{ backgroundColor: 'transparent', width: 35, height: 35, display: 'flex', justifyContent: 'center', alignItems: 'center', position: 'relative' }} onPointerDown={this.toggleTrail}> + <FontAwesomeIcon icon="taxi" size="lg" /> + </button> + </div> + </Tooltip> + </div> + <div style={{ width: 25, height: 25 }}> + <DocumentLinksButton View={this.view0} AlwaysOn={true} InMenu={true} StartLink={true} /> + </div> + </div> + ); + } + @observable subEndLink = ''; + @computed + get endLinkButton() { + const linkBtn = (pinLayout: boolean, pinContent: boolean, icon: IconProp) => { + const tooltip = `Finish Link and Save ${this.subEndLink} data`; + return !this.view0 ? null : ( + <Tooltip title={<div className="dash-tooltip">{tooltip}</div>}> + <div className="documentButtonBar-pinIcon"> + <FontAwesomeIcon + className="documentdecorations-icon" + style={{ width: 20 }} + key={icon.toString()} + size="sm" + icon={icon} + onPointerEnter={action(e => (this.subEndLink = (pinLayout ? 'Layout' : '') + (pinLayout && pinContent ? ' &' : '') + (pinContent ? ' Content' : '')))} + onPointerLeave={action(e => (this.subEndLink = ''))} + onClick={e => { + const docs = this.props + .views() + .filter(v => v) + .map(dv => dv!.rootDoc); + this.view0 && + DocumentLinksButton.finishLinkClick(e.clientX, e.clientY, DocumentLinksButton.StartLink, this.view0.props.Document, true, this.view0, { + pinDocLayout: pinLayout, + pinData: !pinContent ? {} : { poslayoutview: true, dataannos: true, dataview: pinContent }, + } as PinProps); + + e.stopPropagation(); + }} + /> + </div> + </Tooltip> + ); + }; + return !this.view0 ? null : ( + <div className="documentButtonBar-icon documentButtonBar-pin"> + <div className="documentButtonBar-pinTypes"> + {linkBtn(true, false, 'window-maximize')} + {linkBtn(false, true, 'address-card')} + {linkBtn(true, true, 'id-card')} + </div> + <DocumentLinksButton View={this.view0} AlwaysOn={true} InMenu={true} StartLink={false} /> + </div> + ); + } + + @observable subPin = ''; @computed get pinButton() { const targetDoc = this.view0?.props.Document; + const pinBtn = (pinLayoutView: boolean, pinContentView: boolean, icon: IconProp) => { + const tooltip = `Pin Document and Save ${this.subPin} to trail`; + return !tooltip ? null : ( + <Tooltip title={<div className="dash-tooltip">{tooltip}</div>}> + <div className="documentButtonBar-pinIcon"> + <FontAwesomeIcon + className="documentdecorations-icon" + style={{ width: 20 }} + key={icon.toString()} + size="sm" + icon={icon} + onPointerEnter={action( + e => + (this.subPin = + (pinLayoutView ? 'Layout' : '') + + (pinLayoutView && pinContentView ? ' &' : '') + + (pinContentView ? ' Content View' : '') + + (pinLayoutView && pinContentView ? '(shift+alt)' : pinLayoutView ? '(shift)' : pinContentView ? '(alt)' : '')) + )} + onPointerLeave={action(e => (this.subPin = ''))} + onClick={e => { + const docs = this.props + .views() + .filter(v => v) + .map(dv => dv!.rootDoc); + TabDocView.PinDoc(docs, { + pinAudioPlay: true, + pinDocLayout: pinLayoutView, + pinData: { dataview: pinContentView }, + activeFrame: Cast(docs.lastElement()?.activeFrame, 'number', null), + currentFrame: Cast(docs.lastElement()?.currentFrame, 'number', null), + }); + e.stopPropagation(); + }} + /> + </div> + </Tooltip> + ); + }; return !targetDoc ? null : ( - <Tooltip title={<div className="dash-tooltip">{SelectionManager.Views().length > 1 ? 'Pin multiple documents to presentation' : 'Pin to presentation'}</div>}> + <Tooltip title={<div className="dash-tooltip">{`Pin Document ${SelectionManager.Views().length > 1 ? 'multiple documents' : ''} to Trail`}</div>}> <div - className="documentButtonBar-icon" - style={{ color: 'white' }} + className="documentButtonBar-icon documentButtonBar-pin" onClick={e => { const docs = this.props .views() .filter(v => v) .map(dv => dv!.rootDoc); - TabDocView.PinDoc(docs, { pinDocView: true, activeFrame: Cast(docs.lastElement()?.activeFrame, 'number', null) }); + TabDocView.PinDoc(docs, { pinAudioPlay: true, pinDocLayout: e.shiftKey, pinData: {dataview: e.altKey}, activeFrame: Cast(docs.lastElement()?.activeFrame, 'number', null) }); + e.stopPropagation(); }}> + <div className="documentButtonBar-pinTypes"> + {pinBtn(true, false, 'window-maximize')} + {pinBtn(false, true, 'address-card')} + {pinBtn(true, true, 'id-card')} + </div> <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="map-pin" /> </div> </Tooltip> @@ -255,12 +402,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV get shareButton() { const targetDoc = this.view0?.props.Document; return !targetDoc ? null : ( - <Tooltip - title={ - <> - <div className="dash-tooltip">{'Open Sharing Manager'}</div> - </> - }> + <Tooltip title={<div className="dash-tooltip">{'Open Sharing Manager'}</div>}> <div className="documentButtonBar-icon" style={{ color: 'white' }} onClick={e => SharingManager.Instance.open(this.view0, targetDoc)}> <FontAwesomeIcon className="documentdecorations-icon" icon="users" /> </div> @@ -273,40 +415,17 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV const targetDoc = this.view0?.props.Document; return !targetDoc ? null : ( <Tooltip title={<div className="dash-tooltip">{`Open Context Menu`}</div>}> - <div className="documentButtonBar-icon" style={{ color: 'white', cursor: 'pointer' }} onClick={e => this.openContextMenu(e)}> + <div className="documentButtonBar-icon" style={{ color: 'white', cursor: 'pointer' }} onClick={this.openContextMenu}> <FontAwesomeIcon className="documentdecorations-icon" icon="bars" /> </div> </Tooltip> ); } - - @computed - get moreButton() { - const targetDoc = this.view0?.props.Document; - return !targetDoc ? null : ( - <Tooltip - title={ - <> - <div className="dash-tooltip">{`${SettingsManager.propertiesWidth > 0 ? 'Close' : 'Open'} Properties Panel`}</div> - </> - }> - <div className="documentButtonBar-icon" style={{ color: 'white', cursor: 'e-resize' }} onClick={action(e => (SettingsManager.propertiesWidth = SettingsManager.propertiesWidth > 0 ? 0 : 250))}> - <FontAwesomeIcon className="documentdecorations-icon" icon="ellipsis-h" /> - </div> - </Tooltip> - ); - } - @computed get metadataButton() { const view0 = this.view0; return !view0 ? null : ( - <Tooltip - title={ - <> - <div className="dash-tooltip">Show metadata panel</div> - </> - }> + <Tooltip title={<div className="dash-tooltip">Show metadata panel</div>}> <div className="documentButtonBar-linkFlyout"> <Flyout anchorPoint={anchorPoints.LEFT_TOP} @@ -329,28 +448,30 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV } @observable _isRecording = false; + _stopFunc: () => void = emptyFunction; @computed get recordButton() { const targetDoc = this.view0?.props.Document; return !targetDoc ? null : ( - <Tooltip title={<div className="dash-tooltip">{'Click to record 5 second annotation'}</div>}> + <Tooltip title={<div className="dash-tooltip">Press to record audio annotation</div>}> <div className="documentButtonBar-icon" - style={{ backgroundColor: this._isRecording ? 'red' : targetDoc.isLinkButton ? Colors.LIGHT_BLUE : Colors.DARK_GRAY, color: targetDoc.isLinkButton ? Colors.BLACK : Colors.WHITE }} - onClick={undoBatch( - action(e => { - this._isRecording = true; - this.props.views().map( - view => - view && - DocumentViewInternal.recordAudioAnnotation( - view.dataDoc, - view.LayoutFieldKey, - action(() => (this._isRecording = false)) - ) - ); - }) - )}> + style={{ backgroundColor: this._isRecording ? Colors.ERROR_RED : Colors.DARK_GRAY, color: Colors.WHITE }} + onPointerDown={action((e: React.PointerEvent) => { + this._isRecording = true; + this.props.views().map(view => view && DocumentViewInternal.recordAudioAnnotation(view.dataDoc, view.LayoutFieldKey, stopFunc => (this._stopFunc = stopFunc), emptyFunction)); + const b = UndoManager.StartBatch('Recording'); + setupMoveUpEvents( + this, + e, + returnFalse, + action(() => { + this._isRecording = false; + this._stopFunc(); + }), + emptyFunction + ); + })}> <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="microphone" /> </div> </Tooltip> @@ -396,7 +517,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV ) }> <div className={'documentButtonBar-linkButton-empty'} ref={this._dragRef} onPointerDown={this.onTemplateButton}> - {<FontAwesomeIcon className="documentdecorations-icon" icon="edit" size="sm" />} + <FontAwesomeIcon className="documentdecorations-icon" icon="edit" size="sm" /> </div> </Flyout> </div> @@ -416,6 +537,45 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV simulateMouseClick(child, e.clientX, e.clientY - 30, e.screenX, e.screenY - 30); }; + @observable _showLinkPopup = false; + @action + toggleLinkSearch = (e: React.PointerEvent) => { + this._showLinkPopup = !this._showLinkPopup; + e.stopPropagation(); + }; + + @observable _captureEndLinkLayout = false; + @action + captureEndLinkLayout = (e: React.PointerEvent) => { + this._captureEndLinkLayout = !this._captureEndLinkLayout; + }; + @observable _captureEndLinkContent = false; + @action + captureEndLinkContent = (e: React.PointerEvent) => { + this._captureEndLinkContent = !this._captureEndLinkContent; + }; + + @action + captureEndLinkState = (e: React.PointerEvent) => { + this._captureEndLinkContent = this._captureEndLinkLayout = !this._captureEndLinkLayout; + }; + + @action + toggleTrail = (e: React.PointerEvent) => { + const rootView = this.props.views()[0]; + const rootDoc = rootView?.rootDoc; + if (rootDoc) { + const anchor = rootView.ComponentView?.getAnchor?.(false) ?? rootDoc; + const trail = DocCast(anchor.presTrail) ?? Doc.MakeCopy(DocCast(Doc.UserDoc().emptyTrail), true); + if (trail !== anchor.presTrail) { + DocUtils.MakeLink(anchor, trail, { linkRelationship: 'link trail' }); + anchor.presTrail = trail; + } + Doc.ActivePresentation = trail; + this.props.views().lastElement()?.props.addDocTab(trail, OpenWhere.replaceRight); + } + e.stopPropagation(); + }; render() { if (!this.view0) return null; @@ -426,25 +586,30 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV return ( <div className="documentButtonBar"> <div className="documentButtonBar-button"> - <DocumentLinksButton View={this.view0} AlwaysOn={true} InMenu={true} StartLink={true} /> + <DocumentLinksButton View={this.view0} AlwaysOn={true} InMenu={true} ShowCount={true} /> </div> - {(DocumentLinksButton.StartLink || Doc.UserDoc()['documentLinksButton-fullMenu']) && DocumentLinksButton.StartLink !== doc ? ( - <div className="documentButtonBar-button"> - <DocumentLinksButton View={this.view0} AlwaysOn={true} InMenu={true} StartLink={false} /> + {this._showLinkPopup ? ( + <div style={{ position: 'absolute', zIndex: 1000 }}> + <LinkPopup + key="popup" + showPopup={this._showLinkPopup} + linkCreated={link => (link.linkDisplay = !this.props.views().lastElement()?.rootDoc.isLinkButton)} + linkCreateAnchor={() => this.props.views().lastElement()?.ComponentView?.getAnchor?.(true)} + linkFrom={() => this.props.views().lastElement()?.rootDoc} + /> </div> - ) : null} - <div className="documentButtonBar-button">{this.recordButton}</div> + ) : ( + <div className="documentButtonBar-button">{this.linkButton}</div> + )} + + {DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== doc ? <div className="documentButtonBar-button">{this.endLinkButton} </div> : null} { Doc.noviceMode ? null : <div className="documentButtonBar-button">{this.templateButton}</div> - /*<div className="documentButtonBar-button"> - {this.metadataButton} - </div> - <div className="documentButtonBar-button"> - {this.contextButton} - </div> */ + /*<div className="documentButtonBar-button"> {this.metadataButton} </div> */ } {!SelectionManager.Views()?.some(v => v.allLinks.length) ? null : <div className="documentButtonBar-button">{this.followLinkButton}</div>} <div className="documentButtonBar-button">{this.pinButton}</div> + <div className="documentButtonBar-button">{this.recordButton}</div> {!Doc.UserDoc()['documentLinksButton-fullMenu'] ? null : <div className="documentButtonBar-button">{this.shareButton}</div>} {!Doc.UserDoc()['documentLinksButton-fullMenu'] ? null : ( <div className="documentButtonBar-button" style={{ display: !considerPush ? 'none' : '' }}> @@ -455,9 +620,6 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV {this.considerGoogleDocsPull} </div> <div className="documentButtonBar-button">{this.menuButton}</div> - {/* {Doc.noviceMode ? (null) : <div className="documentButtonBar-button"> - {this.moreButton} - </div>} */} </div> ); } |