diff options
Diffstat (limited to 'src/client/views/PropertiesView.tsx')
| -rw-r--r-- | src/client/views/PropertiesView.tsx | 307 |
1 files changed, 218 insertions, 89 deletions
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index 93a3fd253..076dc8261 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -4,7 +4,7 @@ import { faAnchor, faArrowRight } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Checkbox, Tooltip } from '@material-ui/core'; import { intersection } from 'lodash'; -import { action, autorun, computed, Lambda, observable } from 'mobx'; +import { action, autorun, computed, Lambda, observable, trace } from 'mobx'; import { observer } from 'mobx-react'; import { ColorState, SketchPicker } from 'react-color'; import { AclAdmin, AclAugment, AclEdit, AclPrivate, AclReadonly, AclSelfEdit, AclSym, AclUnset, DataSym, Doc, DocListCast, Field, HeightSym, NumListCast, Opt, StrListCast, WidthSym } from '../../fields/Doc'; @@ -27,12 +27,13 @@ import { InkStrokeProperties } from './InkStrokeProperties'; import { DocumentView, OpenWhere, StyleProviderFunc } from './nodes/DocumentView'; import { FilterBox } from './nodes/FilterBox'; import { KeyValueBox } from './nodes/KeyValueBox'; -import { PresBox } from './nodes/trails'; +import { PresBox, PresEffect, PresEffectDirection } from './nodes/trails'; import { PropertiesButtons } from './PropertiesButtons'; import { PropertiesDocBacklinksSelector } from './PropertiesDocBacklinksSelector'; import { PropertiesDocContextSelector } from './PropertiesDocContextSelector'; import './PropertiesView.scss'; import { DefaultStyleProvider } from './StyleProvider'; +import { Colors } from './global/globalEnums'; const higflyout = require('@hig/flyout'); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; @@ -54,7 +55,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { } @computed get selectedDoc() { - return SelectionManager.SelectedSchemaDoc() || this.selectedDocumentView?.rootDoc || Doc.ActiveDashboard; + return LinkManager.currentLink || SelectionManager.SelectedSchemaDoc() || this.selectedDocumentView?.rootDoc || Doc.ActiveDashboard; } @computed get selectedDocumentView() { if (SelectionManager.Views().length) return SelectionManager.Views()[0]; @@ -405,7 +406,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { */ @computed get expansionIcon() { return ( - <Tooltip title={<div className="dash-tooltip">{'Show more permissions'}</div>}> + <Tooltip title={<div className="dash-tooltip">Show more permissions</div>}> <div className="expansion-button" onPointerDown={() => { @@ -1407,6 +1408,37 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { }); @undoBatch + changeAnimationBehavior = action((behavior: string) => { + const lanch = this.destinationAnchor; + if (lanch) { + lanch.presEffect = behavior; + } + }); + @undoBatch + @action + updateEffectDirection = (effect: PresEffectDirection) => { + const lanch = this.destinationAnchor; + if (lanch) { + lanch.presEffectDirection = effect; + } + }; + + @undoBatch + animationDirection = (direction: PresEffectDirection, icon: string, gridColumn: number, gridRow: number, opts: object) => { + const lanch = this.destinationAnchor; + const color = lanch?.presEffectDirection === direction || (direction === PresEffectDirection.Center && !lanch?.presEffectDirection) ? Colors.LIGHT_BLUE : 'black'; + return ( + <Tooltip title={<div className="dash-tooltip">{direction}</div>}> + <div + style={{ ...opts, border: direction === PresEffectDirection.Center ? `solid 2px ${color}` : undefined, borderRadius: '100%', cursor: 'pointer', gridColumn, gridRow, justifySelf: 'center', color }} + onClick={() => this.updateEffectDirection(direction)}> + {icon ? <FontAwesomeIcon icon={icon as any} /> : null} + </div> + </Tooltip> + ); + }; + + @undoBatch changeFollowBehavior = action((follow: string) => { if (LinkManager.currentLink && this.selectedDoc) { this.selectedDoc.followLinkLocation = follow; @@ -1438,19 +1470,23 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { } }; - toggleAnchor = (e: React.PointerEvent) => { - setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => this.selectedDoc && (this.selectedDoc.linkAutoMove = !this.selectedDoc.linkAutoMove)))); + toggleProp = (e: React.PointerEvent, prop: string) => { + setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => this.selectedDoc && (this.selectedDoc[prop] = !this.selectedDoc[prop])))); }; - toggleArrow = (e: React.PointerEvent) => { - setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => this.selectedDoc && (this.selectedDoc.displayArrow = !this.selectedDoc.displayArrow)))); - }; + @computed get destinationAnchor() { + const ldoc = LinkManager.currentLink; + const lanch = LinkManager.currentLinkAnchor; + if (ldoc && lanch) return LinkManager.getOppositeAnchor(ldoc, lanch) ?? lanch; + return ldoc ? DocCast(ldoc.anchor2) : ldoc; + } - toggleZoomToTarget1 = (e: React.PointerEvent) => { - setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => this.selectedDoc && (DocCast(this.selectedDoc.anchor1).followLinkZoom = !DocCast(this.selectedDoc.anchor1).followLinkZoom)))); - }; - toggleZoomToTarget2 = (e: React.PointerEvent) => { - setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => this.selectedDoc && (DocCast(this.selectedDoc.anchor2).followLinkZoom = !DocCast(this.selectedDoc.anchor2).followLinkZoom)))); + @computed get sourceAnchor() { + return LinkManager.currentLinkAnchor ?? (LinkManager.currentLink && this.destinationAnchor ? LinkManager.getOppositeAnchor(LinkManager.currentLink, this.destinationAnchor) : LinkManager.currentLink); + } + + toggleAnchorProp = (e: React.PointerEvent, prop: string, anchor?: Doc) => { + anchor && setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => this.selectedDoc && (anchor[prop] = !anchor[prop])))); }; @computed @@ -1485,6 +1521,23 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { ); } + // Converts seconds to ms and updates presTransition + setZoom = (number: String, change?: number) => { + let scale = Number(number) / 100; + if (change) scale += change; + if (scale < 0.01) scale = 0.01; + if (scale > 1) scale = 1; + this.destinationAnchor && (this.destinationAnchor.presZoom = scale); + }; + // Converts seconds to ms and updates presTransition + setZoomSrc = (number: String, change?: number) => { + let scale = Number(number) / 100; + if (change) scale += change; + if (scale < 0.01) scale = 0.01; + if (scale > 1) scale = 1; + this.sourceAnchor && (this.sourceAnchor.presZoom = scale); + }; + /** * Handles adding and removing members from the sharing panel */ @@ -1498,6 +1551,10 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { render() { const isNovice = Doc.noviceMode; + const zoom = Number((NumCast(this.destinationAnchor?.presZoom) * 100).toPrecision(3)); + const zoomSrc = Number((NumCast(this.sourceAnchor?.presZoom) * 100).toPrecision(3)); + const targZoom = this.destinationAnchor?.followLinkZoom; + const srcZooom = this.sourceAnchor?.followLinkZoom; if (!this.selectedDoc && !this.isPres) { return ( <div className="propertiesView" style={{ width: this.props.width }}> @@ -1507,81 +1564,6 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { </div> ); } else { - if (this.selectedDoc && this.isLink) { - return ( - <div className="propertiesView"> - <div className="propertiesView-title">Linking</div> - <div className="propertiesView-section"> - <p className="propertiesView-label">Information</p> - <div className="propertiesView-input first"> - <p>Link Relationship</p> - {this.editRelationship} - </div> - <div className="propertiesView-input"> - <p>Description</p> - {this.editDescription} - </div> - </div> - <div className="propertiesView-section"> - <p className="propertiesView-label">Behavior</p> - <div className="propertiesView-input inline first"> - <p>Follow</p> - <select name="selectList" id="selectList" onChange={e => this.changeFollowBehavior(e.currentTarget.value)} value={StrCast(this.selectedDoc.followLinkLocation, 'default')}> - <option value="default">Default</option> - <option value="add:left">Open in new left pane</option> - <option value="add:right">Open in new right pane</option> - <option value="replace:left">Replace left tab</option> - <option value="replace:right">Replace right tab</option> - <option value="fullScreen">Open full screen</option> - <option value="add">Open in new tab</option> - <option value="replace">Replace current tab</option> - {this.selectedDoc.linksToAnnotation ? <option value="openExternal">Open in external page</option> : null} - </select> - </div> - <div className="propertiesView-input inline"> - <p>Auto-move anchor</p> - <button - style={{ background: this.selectedDoc.hidden ? 'gray' : !this.selectedDoc.linkAutoMove ? '' : '#4476f7', borderRadius: 3 }} - onPointerDown={this.toggleAnchor} - onClick={e => e.stopPropagation()} - className="propertiesButton"> - <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" /> - </button> - </div> - <div className="propertiesView-input inline"> - <p>Display arrow</p> - <button - style={{ background: this.selectedDoc.hidden ? 'gray' : !this.selectedDoc.displayArrow ? '' : '#4476f7', borderRadius: 3 }} - onPointerDown={this.toggleArrow} - onClick={e => e.stopPropagation()} - className="propertiesButton"> - <FontAwesomeIcon className="fa-icon" icon={faArrowRight as IconLookup} size="lg" /> - </button> - </div> - <div className="propertiesView-input inline"> - <p>Zoom to target</p> - <button - style={{ background: this.selectedDoc.hidden ? 'gray' : !Cast(this.selectedDoc.anchor1, Doc, null).followLinkZoom ? '' : '#4476f7', borderRadius: 3 }} - onPointerDown={this.toggleZoomToTarget1} - onClick={e => e.stopPropagation()} - className="propertiesButton"> - <FontAwesomeIcon className="fa-icon" icon={faArrowRight as IconLookup} size="lg" /> - </button> - </div> - <div className="propertiesView-input inline"> - <p>Zoom to source</p> - <button - style={{ background: this.selectedDoc.hidden ? 'gray' : !Cast(this.selectedDoc.anchor2, Doc, null).followLinkZoom ? '' : '#4476f7', borderRadius: 3 }} - onPointerDown={this.toggleZoomToTarget2} - onClick={e => e.stopPropagation()} - className="propertiesButton"> - <FontAwesomeIcon className="fa-icon" icon={faArrowRight as IconLookup} size="lg" /> - </button> - </div> - </div> - </div> - ); - } if (this.selectedDoc && !this.isPres) { return ( <div @@ -1599,6 +1581,153 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { {this.contextsSubMenu} {this.linksSubMenu} + {!this.selectedDoc || !LinkManager.currentLink ? null : ( + <> + <div className="propertiesView-section"> + <div className="propertiesView-input first"> + <p>Link Relationship</p> + {this.editRelationship} + </div> + <div className="propertiesView-input"> + <p>Description</p> + {this.editDescription} + </div> + </div> + <div className="propertiesView-section"> + <div className="propertiesView-input inline first"> + <p>Follow Behavior</p> + <select onChange={e => this.changeFollowBehavior(e.currentTarget.value)} value={StrCast(this.selectedDoc.followLinkLocation, 'default')}> + <option value="default">Default</option> + <option value="add:left">Open in new left pane</option> + <option value="add:right">Open in new right pane</option> + <option value="replace:left">Replace left tab</option> + <option value="replace:right">Replace right tab</option> + <option value="fullScreen">Open full screen</option> + <option value="add">Open in new tab</option> + <option value="replace">Replace current tab</option> + <option value="inPlace">Opin in place</option> + {this.selectedDoc.linksToAnnotation ? <option value="openExternal">Open in external page</option> : null} + </select> + </div> + <div className="propertiesView-input inline first"> + <p>Target Effect</p> + <select style={{ width: '100%' }} onChange={e => this.changeAnimationBehavior(e.currentTarget.value)} value={StrCast(this.destinationAnchor?.presEffect, 'default')}> + <option value="default">Default</option> + {[PresEffect.None, PresEffect.Zoom, PresEffect.Lightspeed, PresEffect.Fade, PresEffect.Flip, PresEffect.Rotate, PresEffect.Bounce, PresEffect.Roll].map(effect => ( + <option value={effect.toString()}>{effect.toString()}</option> + ))} + </select> + <div className="effectDirection" style={{ marginLeft: '10px', display: 'grid', width: 40 }}> + {this.animationDirection(PresEffectDirection.Left, 'angle-right', 1, 2, {})} + {this.animationDirection(PresEffectDirection.Right, 'angle-left', 3, 2, {})} + {this.animationDirection(PresEffectDirection.Top, 'angle-down', 2, 1, {})} + {this.animationDirection(PresEffectDirection.Bottom, 'angle-up', 2, 3, {})} + {this.animationDirection(PresEffectDirection.Center, '', 2, 2, { width: 10, height: 10, alignSelf: 'center' })} + </div> + </div> + {PresBox.inputter('0.1', '0.1', '10', NumCast(this.destinationAnchor?.presTransition) / 1000, true, (val: string) => + PresBox.SetTransitionTime(val, (timeInMS: number) => this.destinationAnchor && (this.destinationAnchor.presTransition = timeInMS)) + )}{' '} + <div + className={'slider-headers'} + style={{ + display: 'grid', + justifyContent: 'space-between', + width: '100%', + gridTemplateColumns: 'auto auto auto', + borderTop: 'solid', + }}> + <div className="slider-text">Fast</div> + <div className="slider-text">Medium</div> + <div className="slider-text">Slow</div> + </div>{' '} + <div className="propertiesView-input inline"> + <p>Play Target Audio</p> + <button + style={{ background: !this.sourceAnchor?.followLinkAudio ? '' : '#4476f7', borderRadius: 3 }} + onPointerDown={e => this.toggleAnchorProp(e, 'followLinkAudio', this.sourceAnchor)} + onClick={e => e.stopPropagation()} + className="propertiesButton"> + <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" /> + </button> + </div> + <div className="propertiesView-input inline"> + <p>Auto-move anchor</p> + <button + style={{ background: !this.selectedDoc.linkAutoMove ? '' : '#4476f7', borderRadius: 3 }} + onPointerDown={e => this.toggleProp(e, 'linkAutoMove')} + onClick={e => e.stopPropagation()} + className="propertiesButton"> + <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" /> + </button> + </div> + <div className="propertiesView-input inline"> + <p>Show link</p> + <button + style={{ background: !this.selectedDoc.linkDisplay ? '' : '#4476f7', borderRadius: 3 }} + onPointerDown={e => this.toggleProp(e, 'linkDisplay')} + onClick={e => e.stopPropagation()} + className="propertiesButton"> + <FontAwesomeIcon className="fa-icon" icon={faArrowRight as IconLookup} size="lg" /> + </button> + </div> + <div className="propertiesView-input inline"> + <p>Display arrow</p> + <button + style={{ background: !this.selectedDoc.linkDisplayArrow ? '' : '#4476f7', borderRadius: 3 }} + onPointerDown={e => this.toggleProp(e, 'linkDisplayArrow')} + onClick={e => e.stopPropagation()} + className="propertiesButton"> + <FontAwesomeIcon className="fa-icon" icon={faArrowRight as IconLookup} size="lg" /> + </button> + </div> + <div className="propertiesView-input inline"> + <p>Zoom to target (% screen filled)</p> + <div className="ribbon-property" style={{ display: !targZoom ? 'none' : 'flex' }}> + <input className="presBox-input" type="number" value={zoom} /> + <div className="ribbon-propertyUpDown" style={{ display: 'flex', flexDirection: 'column' }}> + <div className="ribbon-propertyUpDownItem" onClick={undoBatch(() => this.setZoom(String(zoom), 0.1))}> + <FontAwesomeIcon icon={'caret-up'} /> + </div> + <div className="ribbon-propertyUpDownItem" onClick={undoBatch(() => this.setZoom(String(zoom), -0.1))}> + <FontAwesomeIcon icon={'caret-down'} /> + </div> + </div> + </div> + <button + style={{ background: !targZoom ? '' : '#4476f7', borderRadius: 3 }} + onPointerDown={e => this.toggleAnchorProp(e, 'followLinkZoom', this.destinationAnchor)} + onClick={e => e.stopPropagation()} + className="propertiesButton"> + <FontAwesomeIcon className="fa-icon" icon={faArrowRight as IconLookup} size="lg" /> + </button> + </div> + {!targZoom ? null : PresBox.inputter('0', '1', '100', zoom, true, this.setZoom)} + <div className="propertiesView-input inline"> + <p>Zoom to source (% screen filled)</p> + <div className="ribbon-property" style={{ display: !srcZooom ? 'none' : 'flex' }}> + <input className="presBox-input" type="number" value={zoomSrc} /> + <div className="ribbon-propertyUpDown" style={{ display: 'flex', flexDirection: 'column' }}> + <div className="ribbon-propertyUpDownItem" onClick={undoBatch(() => this.setZoomSrc(String(zoomSrc), 0.1))}> + <FontAwesomeIcon icon={'caret-up'} /> + </div> + <div className="ribbon-propertyUpDownItem" onClick={undoBatch(() => this.setZoomSrc(String(zoomSrc), -0.1))}> + <FontAwesomeIcon icon={'caret-down'} /> + </div> + </div> + </div> + <button + style={{ background: !srcZooom ? '' : '#4476f7', borderRadius: 3 }} + onPointerDown={e => this.toggleAnchorProp(e, 'followLinkZoom', this.sourceAnchor)} + onClick={e => e.stopPropagation()} + className="propertiesButton"> + <FontAwesomeIcon className="fa-icon" icon={faArrowRight as IconLookup} size="lg" /> + </button> + </div> + {!srcZooom ? null : PresBox.inputter('0', '1', '100', zoomSrc, true, this.setZoomSrc)} + </div> + </> + )} {this.inkSubMenu} |
