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

View File

@ -1,5 +1,6 @@
import React, {PureComponent, MouseEvent} from 'react'
import classnames from 'classnames'
import _ from 'lodash'
import FuncArgs from 'src/flux/components/FuncArgs'
import FuncArgsPreview from 'src/flux/components/FuncArgsPreview'
@ -14,6 +15,7 @@ import {Service} from 'src/types'
interface Props {
func: Func
funcs: Func[]
service: Service
bodyID: string
index: number
@ -29,7 +31,7 @@ interface Props {
}
interface State {
isExpanded: boolean
editing: boolean
}
@ErrorHandling
@ -42,53 +44,107 @@ export default class FuncNode extends PureComponent<Props, State> {
super(props)
this.state = {
isExpanded: false,
editing: this.isLast,
}
}
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 {
func,
bodyID,
service,
isYielding,
onChangeArg,
declarationID,
onGenerateScript,
declarationsFromBody,
} = this.props
const {isExpanded} = this.state
const {editing} = this.state
if (!editing || isYielding) {
return
}
return (
<div
className={this.nodeClassName}
onMouseEnter={this.handleMouseEnter}
onMouseLeave={this.handleMouseLeave}
onClick={this.handleClick}
>
<div className="func-node--connector" />
<div className="func-node--name">{func.name}</div>
<FuncArgsPreview func={func} />
<FuncArgs
func={func}
bodyID={bodyID}
service={service}
onChangeArg={onChangeArg}
declarationID={declarationID}
onGenerateScript={onGenerateScript}
declarationsFromBody={declarationsFromBody}
onStopPropagation={this.handleClickArgs}
/>
)
}
{isExpanded && (
<FuncArgs
func={func}
bodyID={bodyID}
service={service}
onChangeArg={onChangeArg}
declarationID={declarationID}
onGenerateScript={onGenerateScript}
onDeleteFunc={this.handleDelete}
declarationsFromBody={declarationsFromBody}
onStopPropagation={this.handleClickArgs}
/>
)}
private get funcMenu(): JSX.Element {
return (
<div className="func-node--menu">
{this.yieldToggleButton}
<button
className="btn btn-sm btn-square btn-danger"
onClick={this.handleDelete}
title="Delete this Function"
>
<span className="icon trash" />
</button>
</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 {
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 => {
@ -98,19 +154,11 @@ export default class FuncNode extends PureComponent<Props, State> {
this.props.onDelete({funcID: func.id, bodyID, declarationID})
}
private handleMouseEnter = (e: MouseEvent<HTMLElement>): void => {
e.stopPropagation()
this.setState({isExpanded: true})
private handleToggleEdit = (): void => {
this.setState({editing: !this.state.editing})
}
private handleMouseLeave = (e: MouseEvent<HTMLElement>): void => {
e.stopPropagation()
this.setState({isExpanded: false})
}
private handleClick = (e: MouseEvent<HTMLElement>): void => {
private handleToggleYield = (e: MouseEvent<HTMLElement>): void => {
e.stopPropagation()
const {
@ -128,7 +176,16 @@ export default class FuncNode extends PureComponent<Props, State> {
onToggleYieldWithLast(index)
}
}
private handleClickArgs = (e: MouseEvent<HTMLElement>): void => {
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;
border-radius: $radius;
padding: 10px;
padding: 6px;
display: flex;
align-items: stretch;
position: absolute;
top: 0;
left: calc(100% + #{$flux-node-tooltip-gap});
z-index: 9999;
box-shadow: 0 0 10px 2px $g2-kevlar; // Caret
}
.func-node--editor .func-node--connector {
// Vertical Line
&:before {
content: '';
border-width: 9px;
border-style: solid;
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
height: calc(100% + #{($flux-node-tooltip-gap / 2) + $flux-node-gap});
}
// Horizontal Line
&:after {
content: '';
height: 50%;
width: $flux-node-tooltip-gap * 3;
position: absolute;
top: 0;
left: -$flux-node-tooltip-gap * 3;
content: none;
}
}
@ -284,7 +296,6 @@ $flux-invalid-hover: $c-dreamsicle;
display: flex;
flex-direction: column;
justify-content: center;
margin-left: 8px;
}
.func-node--build {
@ -313,7 +324,7 @@ $flux-invalid-hover: $c-dreamsicle;
font-size: 13px;
font-weight: 600;
color: $g10-wolf;
padding-right: 8px;
padding: 0 8px;
@include no-user-select();
}