Merge pull request #3598 from influxdata/flux/vertical-builder

Make Flux Builder Stack Vertically
pull/10616/head
Alex Paxton 2018-06-08 11:05:21 -07:00 committed by GitHub
commit f8c8f97bec
7 changed files with 262 additions and 143 deletions

View File

@ -1,6 +1,7 @@
import React, {PureComponent} from 'react'
import _ from 'lodash'
import FancyScrollbar from 'src/shared/components/FancyScrollbar'
import ExpressionNode from 'src/flux/components/ExpressionNode'
import VariableName from 'src/flux/components/VariableName'
import FuncSelector from 'src/flux/components/FuncSelector'
@ -29,7 +30,7 @@ class BodyBuilder extends PureComponent<Props> {
if (d.funcs) {
return (
<div className="declaration" key={i}>
<VariableName name={d.name} />
<VariableName name={d.name} assignedToQuery={true} />
<ExpressionNode
bodyID={b.id}
declarationID={d.id}
@ -43,7 +44,7 @@ class BodyBuilder extends PureComponent<Props> {
return (
<div className="declaration" key={i}>
<VariableName name={b.source} />
<VariableName name={b.source} assignedToQuery={false} />
</div>
)
})
@ -62,18 +63,20 @@ class BodyBuilder extends PureComponent<Props> {
})
return (
<div className="body-builder">
{_.flatten(bodybuilder)}
<div className="declaration">
<FuncSelector
bodyID="fake-body-id"
declarationID="fake-declaration-id"
onAddNode={this.createNewBody}
funcs={this.newDeclarationFuncs}
connectorVisible={false}
/>
<FancyScrollbar className="body-builder--container" autoHide={true}>
<div className="body-builder">
{_.flatten(bodybuilder)}
<div className="declaration">
<FuncSelector
bodyID="fake-body-id"
declarationID="fake-declaration-id"
onAddNode={this.createNewBody}
funcs={this.newDeclarationFuncs}
connectorVisible={false}
/>
</div>
</div>
</div>
</FancyScrollbar>
)
}

View File

@ -21,55 +21,82 @@ interface Props {
@ErrorHandling
export default class FuncArgs extends PureComponent<Props> {
public render() {
const {onDeleteFunc} = this.props
return (
<div className="func-node--tooltip">
<div className="func-args">{this.renderJoinOrArgs}</div>
<div className="func-arg--buttons">
<div
className="btn btn-sm btn-danger btn-square"
onClick={onDeleteFunc}
>
<span className="icon trash" />
</div>
{this.build}
</div>
</div>
)
}
get renderJoinOrArgs(): JSX.Element | JSX.Element[] {
const {func} = this.props
const {name: funcName} = func
if (funcName === funcNames.JOIN) {
return this.renderJoin
}
return this.renderArguments
}
get renderArguments(): JSX.Element | JSX.Element[] {
const {
func,
bodyID,
service,
onChangeArg,
onDeleteFunc,
declarationID,
onGenerateScript,
} = this.props
const {name: funcName, id: funcID} = func
return func.args.map(({key, value, type}) => (
<FuncArg
key={key}
type={type}
argKey={key}
value={value}
bodyID={bodyID}
funcID={funcID}
funcName={funcName}
service={service}
onChangeArg={onChangeArg}
declarationID={declarationID}
onGenerateScript={onGenerateScript}
/>
))
}
get renderJoin(): JSX.Element {
const {
func,
bodyID,
onChangeArg,
declarationID,
onGenerateScript,
declarationsFromBody,
} = this.props
const {name: funcName, id: funcID} = func
return (
<div className="func-node--tooltip">
{funcName === funcNames.JOIN ? (
<Join
func={func}
bodyID={bodyID}
declarationID={declarationID}
onChangeArg={onChangeArg}
declarationsFromBody={declarationsFromBody}
onGenerateScript={onGenerateScript}
/>
) : (
func.args.map(({key, value, type}) => (
<FuncArg
key={key}
type={type}
argKey={key}
value={value}
bodyID={bodyID}
funcID={funcID}
funcName={funcName}
service={service}
onChangeArg={onChangeArg}
declarationID={declarationID}
onGenerateScript={onGenerateScript}
/>
))
)}
<div className="func-node--buttons">
<div
className="btn btn-sm btn-danger func-node--delete"
onClick={onDeleteFunc}
>
Delete
</div>
{this.build}
</div>
</div>
<Join
func={func}
bodyID={bodyID}
declarationID={declarationID}
onChangeArg={onChangeArg}
declarationsFromBody={declarationsFromBody}
onGenerateScript={onGenerateScript}
/>
)
}

View File

@ -76,19 +76,19 @@ export default class FuncArgsPreview extends PureComponent<Props> {
case 'period':
case 'duration':
case 'array': {
return <span className="variable-value--number">{argument}</span>
return <span className="func-arg--number">{argument}</span>
}
case 'bool': {
return <span className="variable-value--boolean">{argument}</span>
return <span className="func-arg--boolean">{argument}</span>
}
case 'string': {
return <span className="variable-value--string">"{argument}"</span>
return <span className="func-arg--string">"{argument}"</span>
}
case 'object': {
return <span className="variable-value--object">{argument}</span>
return <span className="func-arg--object">{argument}</span>
}
case 'invalid': {
return <span className="variable-value--invalid">{argument}</span>
return <span className="func-arg--invalid">{argument}</span>
}
default: {
return <span>{argument}</span>

View File

@ -52,6 +52,7 @@ export default class FuncNode extends PureComponent<Props, State> {
onMouseEnter={this.handleMouseEnter}
onMouseLeave={this.handleMouseLeave}
>
<div className="func-node--connector" />
<div className="func-node--name">{func.name}</div>
<FuncArgsPreview func={func} />
{isExpanded && (

View File

@ -1,7 +1,8 @@
import React, {PureComponent} from 'react'
interface Props {
name?: string
name: string
assignedToQuery: boolean
}
interface State {
@ -10,7 +11,7 @@ interface State {
export default class VariableName extends PureComponent<Props, State> {
public static defaultProps: Partial<Props> = {
name: '',
assignedToQuery: false,
}
constructor(props) {
@ -22,7 +23,14 @@ export default class VariableName extends PureComponent<Props, State> {
}
public render() {
return <div className="variable-string">{this.nameElement}</div>
const {assignedToQuery} = this.props
return (
<div className="variable-node">
{assignedToQuery && <div className="variable-node--connector" />}
{this.nameElement}
</div>
)
}
private get nameElement(): JSX.Element {
@ -32,7 +40,7 @@ export default class VariableName extends PureComponent<Props, State> {
return this.colorizeSyntax
}
return <span className="variable-name">{name}</span>
return <span className="variable-node--name">{name}</span>
}
private get colorizeSyntax(): JSX.Element {
@ -42,14 +50,13 @@ export default class VariableName extends PureComponent<Props, State> {
const varValue = this.props.name.replace(/^[^=]+=/, '')
const valueIsString = varValue.endsWith('"')
return (
<>
<span className="variable-name">{varName}</span>
<span className="variable-node--name">{varName}</span>
{' = '}
<span
className={
valueIsString ? 'variable-value--string' : 'variable-value--number'
valueIsString ? 'variable-node--string' : 'variable-node--number'
}
>
{varValue}

View File

@ -10,25 +10,28 @@ $flux-func-selector--height: 30px;
display: flex;
align-items: center;
position: relative;
flex-direction: column;
&.open {
z-index: 9999;
height: $flux-func-selector--height + $flux-func-selector--gap;
}
}
.func-selector--connector {
width: $flux-func-selector--gap;
height: $flux-func-selector--height;
width: $flux-node-gap;
height: $flux-func-selector--gap;
position: relative;
&:after {
content: '';
position: absolute;
top: 50%;
width: 100%;
height: 4px;
transform: translateY(-50%);
@include gradient-h($g4-onyx, $c-pool);
top: -130%;
width: $flux-connector-line;
left: 50%;
height: 230%;
transform: translateX(-50%);
@include gradient-v($g4-onyx, $c-pool);
}
}
@ -51,7 +54,7 @@ $flux-func-selector--height: 30px;
top: 0;
.func-selector--connector + & {
left: $flux-func-selector--gap;
top: $flux-func-selector--gap;
}
}

View File

@ -1,18 +1,23 @@
/*
Flux Builder Styles
------------------------------------------------------------------------------
*/
$flux-builder-min-width: 440px;
$flux-node-height: 30px;
$flux-node-tooltip-gap: 4px;
$flux-node-gap: 5px;
$flux-connector-line: 2px;
$flux-node-gap: 30px;
$flux-node-padding: 10px;
$flux-arg-min-width: 120px;
$flux-number-color: $c-neutrino;
$flux-object-color: $c-viridian;
$flux-string-color: $c-honeydew;
$flux-boolean-color: $c-viridian;
$flux-invalid-color: $c-viridian;
/*
Shared Node styles
------------------
*/
// Shared Node styles
%flux-node {
min-height: $flux-node-height;
border-radius: $radius;
@ -22,66 +27,108 @@ $flux-invalid-color: $c-viridian;
position: relative;
background-color: $g4-onyx;
transition: background-color 0.25s ease;
margin-bottom: $flux-node-tooltip-gap / 2;
margin-top: $flux-node-tooltip-gap / 2;
&:hover {
cursor: pointer;
background-color: $g6-smoke;
}
}
.body-builder {
padding: 30px;
min-width: 440px;
overflow: hidden;
height: 100%;
width: 100%;
.body-builder--container {
background-color: $g1-raven;
}
.body-builder {
padding: $flux-node-height;
padding-bottom: 0;
min-width: $flux-builder-min-width;
width: 100%;
}
.declaration {
width: 100%;
margin-bottom: 24px;
margin-bottom: $flux-node-gap;
padding-bottom: $flux-node-gap;
border-bottom: 2px solid $g2-kevlar;
display: flex;
flex-wrap: nowrap;
align-items: center;
flex-direction: column;
align-items: flex-start;
&:last-of-type {
margin-bottom: 0;
border: 0;
}
}
.variable-string {
.variable-node {
@extend %flux-node;
color: $g11-sidewalk;
line-height: $flux-node-height;
white-space: nowrap;
@include no-user-select();
margin-top: 0;
&:hover {
background-color: $g4-onyx;
cursor: default;
}
}
.variable-blank {
font-style: italic;
.variable-node--connector {
width: 8px;
height: 8px;
border-radius: 50%;
background-color: $c-pool;
position: absolute;
z-index: 2;
top: 50%;
left: $flux-node-gap / 2;
transform: translate(-50%, -50%);
&:before {
content: '';
position: absolute;
top: 100%;
left: 50%;
width: $flux-connector-line;
height: $flux-node-gap;
@include gradient-v($c-pool, $g4-onyx);
transform: translateX(-50%);
}
}
.variable-name {
.variable-node--name {
color: $c-pool;
.variable-node--connector + & {
margin-left: $flux-node-gap - $flux-node-padding;
}
}
.variable-value--string {
.variable-node--string,
.func-arg--string {
color: $flux-string-color;
}
.variable-value--boolean {
.variable-node--boolean,
.func-arg--boolean {
color: $flux-boolean-color;
}
.variable-value--number {
.variable-node--number,
.func-arg--number {
color: $flux-number-color;
}
.variable-value--object {
.variable-node--object,
.func-arg--object {
color: $flux-object-color;
}
.variable-value--invalid {
.variable-node--invalid,
.func-arg--invalid {
color: $flux-invalid-color;
}
@ -89,24 +136,66 @@ $flux-invalid-color: $c-viridian;
@extend %flux-node;
display: flex;
align-items: center;
margin-left: $flux-node-gap;
}
// Connection Line
.func-node--connector {
width: $flux-node-gap;
height: 100%;
position: absolute;
top: 0;
left: 0;
transform: translateX(-100%);
z-index: 0;
// Connection Lines
&:before,
&:after {
content: '';
height: 4px;
width: $flux-node-gap;
background-color: $g4-onyx;
position: absolute;
top: 50%;
left: 0;
transform: translate(-100%, -50%);
}
// Vertical Line
&:before {
width: $flux-connector-line;
height: calc(100% + #{$flux-node-tooltip-gap});
top: -$flux-node-tooltip-gap / 2;
left: $flux-node-gap / 2;
transform: translateX(-50%);
}
// Horizontal Line
&:after {
height: $flux-connector-line;
width: $flux-node-gap / 2;
top: 50%;
left: $flux-node-gap / 2;
transform: translateY(-50%);
}
}
// When a query exists unassigned to a variable
.func-node:first-child {
margin-left: 0;
padding-left: $flux-node-gap;
&:first-child:after {
content: none;
margin-left: 0;
.func-node--connector {
transform: translateX(0);
z-index: 2;
// Vertical Line
&:before {
height: $flux-node-gap;
top: $flux-node-gap / 2;
@include gradient-v($c-comet, $g4-onyx);
}
// Dot
&:after {
width: 8px;
height: 8px;
border-radius: 50%;
background-color: $c-comet;
top: $flux-node-gap / 2;
transform: translate(-50%, -50%);
}
}
}
@ -139,56 +228,65 @@ $flux-invalid-color: $c-viridian;
}
}
.func-node--tooltip,
.variable-name--tooltip {
.func-node--tooltip {
background-color: $g3-castle;
border-radius: $radius;
padding: 10px;
display: flex;
align-items: stretch;
flex-direction: column;
position: absolute;
top: calc(100% + #{$flux-node-tooltip-gap});
left: 0;
top: 0;
left: calc(100% + #{$flux-node-tooltip-gap});
z-index: 9999;
box-shadow: 0 0 10px 2px $g2-kevlar; // Caret
box-shadow: 0 0 10px 2px $g2-kevlar;
// Caret
&:before {
content: '';
border-width: 9px;
border-style: solid;
border-color: transparent;
border-bottom-color: $g3-castle;
border-right-color: $g3-castle;
position: absolute;
top: 0;
left: $flux-node-padding + 3px;
transform: translate(-50%, -100%);
} // Invisible block to continue hovering
top: $flux-node-height / 2;
left: 0;
transform: translate(-100%, -50%);
}
// Invisible block to continue hovering
&:after {
content: '';
width: 80%;
height: 7px;
height: 50%;
width: $flux-node-tooltip-gap * 3;
position: absolute;
top: -7px;
left: 0;
top: 0;
left: -$flux-node-tooltip-gap * 3;
}
}
.func-node--buttons {
.func-arg--buttons {
display: flex;
margin-top: 12px;
flex-direction: column;
justify-content: center;
margin-left: 8px;
}
.func-node--delete,
.func-node--build {
width: 60px;
margin-top: 4px;
}
.func-node--sub .func-arg {
.func-args {
display: flex;
flex-direction: column;
}
.func-arg {
min-width: $flux-arg-min-width;
display: flex;
flex-wrap: nowrap;
align-items: center;
margin-bottom: 4px;
&:last-of-type {
margin-bottom: 0;
}
@ -212,26 +310,6 @@ $flux-invalid-color: $c-viridian;
width: 300px;
}
.variable-name--tooltip {
flex-direction: row;
align-items: center;
justify-content: space-between;
flex-wrap: nowrap;
}
.variable-name--input {
width: 140px;
}
.variable-name--operator {
width: 20px;
height: 30px;
text-align: center;
line-height: 30px;
font-weight: 600;
@include no-user-select();
}
/*
Filter Preview Styles
------------------------------------------------------------------------------