Refactor func node UI & UX

- Nothing happens when hovering a func node
- Delete & toggle yield buttons appear on hover, stay visible when
editing or yielding
- Clicking a func enters/exits edit mode
- Last func in each body is in edit more for educational purposes
pull/3684/head
Alex P 2018-06-14 15:30:28 -07:00
parent 368f471199
commit d851e52643
3 changed files with 132 additions and 62 deletions

View File

@ -94,6 +94,7 @@ class ExpressionNode extends PureComponent<Props, State> {
key={i} key={i}
index={i} index={i}
func={func} func={func}
funcs={funcs}
bodyID={bodyID} bodyID={bodyID}
service={service} service={service}
onChangeArg={onChangeArg} onChangeArg={onChangeArg}
@ -139,6 +140,7 @@ class ExpressionNode extends PureComponent<Props, State> {
key={i} key={i}
index={i} index={i}
func={func} func={func}
funcs={funcs}
bodyID={bodyID} bodyID={bodyID}
service={service} service={service}
onChangeArg={onChangeArg} onChangeArg={onChangeArg}

View File

@ -1,5 +1,6 @@
import React, {PureComponent, MouseEvent} from 'react' import React, {PureComponent, MouseEvent} from 'react'
import classnames from 'classnames' import classnames from 'classnames'
import _ from 'lodash'
import FuncArgs from 'src/flux/components/FuncArgs' import FuncArgs from 'src/flux/components/FuncArgs'
import FuncArgsPreview from 'src/flux/components/FuncArgsPreview' import FuncArgsPreview from 'src/flux/components/FuncArgsPreview'
@ -14,6 +15,7 @@ import {Service} from 'src/types'
interface Props { interface Props {
func: Func func: Func
funcs: Func[]
service: Service service: Service
bodyID: string bodyID: string
index: number index: number
@ -29,7 +31,7 @@ interface Props {
} }
interface State { interface State {
isExpanded: boolean editing: boolean
} }
@ErrorHandling @ErrorHandling
@ -42,53 +44,107 @@ export default class FuncNode extends PureComponent<Props, State> {
super(props) super(props)
this.state = { this.state = {
isExpanded: false, editing: this.isLast,
} }
} }
public render() { public render() {
const {func} = this.props
return (
<>
<div
className={this.nodeClassName}
onClick={this.handleToggleEdit}
title="Edit function arguments"
>
<div className="func-node--connector" />
<div className="func-node--name">{func.name}</div>
<FuncArgsPreview func={func} />
{this.funcMenu}
</div>
{this.funcArgs}
</>
)
}
private get funcArgs(): JSX.Element {
const { const {
func, func,
bodyID, bodyID,
service, service,
isYielding,
onChangeArg, onChangeArg,
declarationID, declarationID,
onGenerateScript, onGenerateScript,
declarationsFromBody, declarationsFromBody,
} = this.props } = this.props
const {isExpanded} = this.state const {editing} = this.state
if (!editing || isYielding) {
return
}
return ( return (
<div <FuncArgs
className={this.nodeClassName} func={func}
onMouseEnter={this.handleMouseEnter} bodyID={bodyID}
onMouseLeave={this.handleMouseLeave} service={service}
onClick={this.handleClick} onChangeArg={onChangeArg}
> declarationID={declarationID}
<div className="func-node--connector" /> onGenerateScript={onGenerateScript}
<div className="func-node--name">{func.name}</div> declarationsFromBody={declarationsFromBody}
<FuncArgsPreview func={func} /> onStopPropagation={this.handleClickArgs}
/>
)
}
{isExpanded && ( private get funcMenu(): JSX.Element {
<FuncArgs return (
func={func} <div className="func-node--menu">
bodyID={bodyID} {this.yieldToggleButton}
service={service} <button
onChangeArg={onChangeArg} className="btn btn-sm btn-square btn-danger"
declarationID={declarationID} onClick={this.handleDelete}
onGenerateScript={onGenerateScript} title="Delete this Function"
onDeleteFunc={this.handleDelete} >
declarationsFromBody={declarationsFromBody} <span className="icon trash" />
onStopPropagation={this.handleClickArgs} </button>
/>
)}
</div> </div>
) )
} }
private get yieldToggleButton(): JSX.Element {
const {isYielding} = this.props
if (isYielding) {
return (
<button
className="btn btn-sm btn-square btn-warning"
onClick={this.handleToggleYield}
title="Hide Data Table"
>
<span className="icon eye-closed" />
</button>
)
}
return (
<button
className="btn btn-sm btn-square btn-warning"
onClick={this.handleToggleYield}
title="See Data Table returned by this Function"
>
<span className="icon eye-open" />
</button>
)
}
private get nodeClassName(): string { private get nodeClassName(): string {
const {isYielding} = this.props const {isYielding} = this.props
return classnames('func-node', {active: isYielding}) const {editing} = this.state
return classnames('func-node', {active: isYielding || editing})
} }
private handleDelete = (e: MouseEvent<HTMLElement>): void => { private handleDelete = (e: MouseEvent<HTMLElement>): void => {
@ -98,19 +154,11 @@ export default class FuncNode extends PureComponent<Props, State> {
this.props.onDelete({funcID: func.id, bodyID, declarationID}) this.props.onDelete({funcID: func.id, bodyID, declarationID})
} }
private handleMouseEnter = (e: MouseEvent<HTMLElement>): void => { private handleToggleEdit = (): void => {
e.stopPropagation() this.setState({editing: !this.state.editing})
this.setState({isExpanded: true})
} }
private handleMouseLeave = (e: MouseEvent<HTMLElement>): void => { private handleToggleYield = (e: MouseEvent<HTMLElement>): void => {
e.stopPropagation()
this.setState({isExpanded: false})
}
private handleClick = (e: MouseEvent<HTMLElement>): void => {
e.stopPropagation() e.stopPropagation()
const { const {
@ -128,7 +176,16 @@ export default class FuncNode extends PureComponent<Props, State> {
onToggleYieldWithLast(index) onToggleYieldWithLast(index)
} }
} }
private handleClickArgs = (e: MouseEvent<HTMLElement>): void => { private handleClickArgs = (e: MouseEvent<HTMLElement>): void => {
e.stopPropagation() e.stopPropagation()
} }
private get isLast(): boolean {
const {funcs, func} = this.props
const lastFunc = _.last(funcs)
return lastFunc.id === func.id
}
} }

View File

@ -248,35 +248,47 @@ $flux-invalid-hover: $c-dreamsicle;
} }
} }
.func-node--tooltip { .func-node--menu {
display: flex;
align-items: center;
position: absolute;
top: 50%;
right: 0;
transform: translate(100%, -50%);
opacity: 0;
transition: opacity 0.25s ease;
> button.btn {
margin-left: 4px;
}
}
.func-node:hover .func-node--menu,
.func-node.editing .func-node--menu,
.func-node.active .func-node--menu {
opacity: 1;
}
.func-node--editor {
position: relative;
margin-left: $flux-node-gap;
margin-bottom: $flux-node-gap;
margin-top: $flux-node-tooltip-gap / 2;
background-color: $g3-castle; background-color: $g3-castle;
border-radius: $radius; border-radius: $radius;
padding: 10px; padding: 6px;
display: flex; display: flex;
align-items: stretch; align-items: stretch;
position: absolute; }
top: 0;
left: calc(100% + #{$flux-node-tooltip-gap}); .func-node--editor .func-node--connector {
z-index: 9999; // Vertical Line
box-shadow: 0 0 10px 2px $g2-kevlar; // Caret
&:before { &:before {
content: ''; height: calc(100% + #{($flux-node-tooltip-gap / 2) + $flux-node-gap});
border-width: 9px; }
border-style: solid; // Horizontal Line
border-color: transparent;
border-right-color: $g3-castle;
position: absolute;
top: $flux-node-height / 2;
left: 0;
transform: translate(-100%, -50%);
} // Invisible block to continue hovering
&:after { &:after {
content: ''; content: none;
height: 50%;
width: $flux-node-tooltip-gap * 3;
position: absolute;
top: 0;
left: -$flux-node-tooltip-gap * 3;
} }
} }
@ -284,7 +296,6 @@ $flux-invalid-hover: $c-dreamsicle;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: center; justify-content: center;
margin-left: 8px;
} }
.func-node--build { .func-node--build {
@ -313,7 +324,7 @@ $flux-invalid-hover: $c-dreamsicle;
font-size: 13px; font-size: 13px;
font-weight: 600; font-weight: 600;
color: $g10-wolf; color: $g10-wolf;
padding-right: 8px; padding: 0 8px;
@include no-user-select(); @include no-user-select();
} }