diff options
| author | bobzel <zzzman@gmail.com> | 2023-06-23 21:44:01 -0400 |
|---|---|---|
| committer | bobzel <zzzman@gmail.com> | 2023-06-23 21:44:01 -0400 |
| commit | 85c017527f209c9d007d67ac70958843ab45e729 (patch) | |
| tree | e2649860002e0c60e98d84439a67235002ddd9a4 /src/client/views/DocumentButtonBar.tsx | |
| parent | e9d5dbeef2bf1dab9dfb863d970b70b3074e3d0a (diff) | |
| parent | 1429ab79eac9aa316082f52c14c576f6b3a97111 (diff) | |
Merge branch 'master' into heartbeat
Diffstat (limited to 'src/client/views/DocumentButtonBar.tsx')
| -rw-r--r-- | src/client/views/DocumentButtonBar.tsx | 301 |
1 files changed, 221 insertions, 80 deletions
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index ce77b7446..35b0b22a8 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -5,25 +5,27 @@ import { action, computed, observable, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import { Doc } from '../../fields/Doc'; import { RichTextField } from '../../fields/RichTextField'; -import { Cast, NumCast } from '../../fields/Types'; -import { emptyFunction, setupMoveUpEvents, simulateMouseClick } from '../../Utils'; +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 { IsFollowLinkScript } from '../util/LinkFollower'; 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 { PinProps } from './nodes/trails'; import { TemplateMenu } from './TemplateMenu'; import React = require('react'); const higflyout = require('@hig/flyout'); @@ -176,11 +178,11 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV e.preventDefault(); let googleDoc = await Cast(dataDoc.googleDoc, Doc); if (!googleDoc) { - const options = { _width: 600, _nativeWidth: 960, _nativeHeight: 800, useCors: false }; + const options = { _width: 600, _nativeWidth: 960, _nativeHeight: 800, data_useCors: false }; 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); @@ -209,26 +211,128 @@ 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> + ); + }; + const followLink = IsFollowLinkScript(targetDoc?.onClick); 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" - 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)))}> + className="documentButtonBar-icon documentButtonBar-follow" + style={{ backgroundColor: followLink ? Colors.LIGHT_BLUE : Colors.DARK_GRAY, color: followLink ? Colors.BLACK : Colors.WHITE }} + 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 expandPin = false; + + @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 => { + 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 = (pinDocLayout: boolean, pinDocContent: boolean, icon: IconProp) => { + 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>}> @@ -242,10 +346,10 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV onPointerEnter={action( e => (this.subPin = - (pinDocLayout ? 'Layout' : '') + - (pinDocLayout && pinDocContent ? ' &' : '') + - (pinDocContent ? ' Content View' : '') + - (pinDocLayout && pinDocContent ? '(shift+alt)' : pinDocLayout ? '(shift)' : pinDocContent ? '(alt)' : '')) + (pinLayoutView ? 'Layout' : '') + + (pinLayoutView && pinContentView ? ' &' : '') + + (pinContentView ? ' Content View' : '') + + (pinLayoutView && pinContentView ? '(shift+alt)' : pinLayoutView ? '(shift)' : pinContentView ? '(alt)' : '')) )} onPointerLeave={action(e => (this.subPin = ''))} onClick={e => { @@ -253,7 +357,13 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV .views() .filter(v => v) .map(dv => dv!.rootDoc); - TabDocView.PinDoc(docs, { pinDocLayout, pinDocContent, activeFrame: Cast(docs.lastElement()?.activeFrame, 'number', null) }); + 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(); }} /> @@ -264,25 +374,20 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV return !targetDoc ? null : ( <Tooltip title={<div className="dash-tooltip">{`Pin Document ${SelectionManager.Views().length > 1 ? 'multiple documents' : ''} to Trail`}</div>}> <div - className="documentButtonBar-icon" - style={{ color: 'white' }} - onPointerEnter={action(e => (this.expandPin = true))} - onPointerLeave={action(e => (this.expandPin = false))} + className="documentButtonBar-icon documentButtonBar-pin" onClick={e => { const docs = this.props .views() .filter(v => v) .map(dv => dv!.rootDoc); - TabDocView.PinDoc(docs, { pinDocLayout: e.shiftKey, pinDocContent: e.altKey, 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(); }}> - {this.expandPin ? ( - <div className="documentButtonBar-pinTypes"> - {pinBtn(true, false, 'window-maximize')} - {pinBtn(false, true, 'address-card')} - {pinBtn(true, true, 'id-card')} - </div> - ) : null} + <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> @@ -293,12 +398,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> @@ -311,7 +411,7 @@ 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> @@ -321,12 +421,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV 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} @@ -349,44 +444,46 @@ 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> ); } - @observable _aliasDown = false; + @observable _embedDown = false; onTemplateButton = action((e: React.PointerEvent): void => { this._tooltipOpen = false; - setupMoveUpEvents(this, e, this.onAliasButtonMoved, emptyFunction, emptyFunction); + setupMoveUpEvents(this, e, this.onEmbedButtonMoved, emptyFunction, emptyFunction); }); - onAliasButtonMoved = () => { + onEmbedButtonMoved = () => { 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.defaultDropAction = 'embed'; dragData.canEmbed = true; DragManager.StartDocumentDrag([dragDocView.ContentDiv!], dragData, left, top, { hideSource: false }); return true; @@ -401,14 +498,14 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV const view0 = this.view0; const views = this.props.views(); return !view0 ? null : ( - <Tooltip title={<div className="dash-tooltip">Tap to Customize Layout. Drag an embeddable alias</div>} open={this._tooltipOpen} onClose={action(() => (this._tooltipOpen = false))} placement="bottom"> + <Tooltip title={<div className="dash-tooltip">Tap to Customize Layout. Drag an embedding</div>} open={this._tooltipOpen} onClose={action(() => (this._tooltipOpen = false))} placement="bottom"> <div className="documentButtonBar-linkFlyout" ref={this._dragRef} onPointerEnter={action(() => !this._ref.current?.getBoundingClientRect().width && (this._tooltipOpen = true))}> <Flyout anchorPoint={anchorPoints.LEFT_TOP} - onOpen={action(() => (this._aliasDown = true))} - onClose={action(() => (this._aliasDown = false))} + onOpen={action(() => (this._embedDown = true))} + onClose={action(() => (this._embedDown = false))} content={ - !this._aliasDown ? null : ( + !this._embedDown ? null : ( <div ref={this._ref}> {' '} <TemplateMenu docViews={views.filter(v => v).map(v => v as DocumentView)} /> @@ -416,7 +513,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> @@ -436,6 +533,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, { link_relationship: '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; @@ -448,23 +584,28 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV <div className="documentButtonBar-button"> <DocumentLinksButton View={this.view0} AlwaysOn={true} InMenu={true} ShowCount={true} /> </div> - <div className="documentButtonBar-button"> - <DocumentLinksButton View={this.view0} AlwaysOn={true} InMenu={true} StartLink={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.link_displayLine = !IsFollowLinkScript(this.props.views().lastElement()?.rootDoc.onClick))} + linkCreateAnchor={() => this.props.views().lastElement()?.ComponentView?.getAnchor?.(true)} + linkFrom={() => this.props.views().lastElement()?.rootDoc} + /> </div> - ) : null} - {Doc.noviceMode ? 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.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' : '' }}> |
