feat(ui): update design of flux editor toolbar (#17488)
* feat(ui): redesign flux editor * chore(ui): delete threesizer * fix(ui): repair explorer e2e tests * refactor(ui): make variables easier to test * fix(ui): appease prettier * fix(ui): update selectors in dashboards view testpull/17525/head
parent
1816425e52
commit
f617657e8d
|
@ -133,17 +133,20 @@ describe('Dashboard', () => {
|
|||
cy.getByTestID('switch-to-script-editor').click()
|
||||
cy.getByTestID('toolbar-tab').click()
|
||||
// check to see if the default timeRange variables are available
|
||||
cy.get('.variables-toolbar--label').contains('timeRangeStart')
|
||||
cy.get('.variables-toolbar--label').contains('timeRangeStop')
|
||||
cy.get('.variables-toolbar--label')
|
||||
cy.get('.flux-toolbar--list-item').contains('timeRangeStart')
|
||||
cy.get('.flux-toolbar--list-item').contains('timeRangeStop')
|
||||
cy.get('.flux-toolbar--list-item')
|
||||
.first()
|
||||
.click()
|
||||
.within(() => {
|
||||
cy.get('.cf-button').click()
|
||||
})
|
||||
|
||||
cy.getByTestID('flux-editor')
|
||||
.should('be.visible')
|
||||
.click()
|
||||
.focused()
|
||||
.type(' ')
|
||||
cy.get('.variables-toolbar--label')
|
||||
cy.get('.flux-toolbar--list-item')
|
||||
.eq(2)
|
||||
.click()
|
||||
cy.getByTestID('save-cell--button').click()
|
||||
|
@ -182,7 +185,7 @@ describe('Dashboard', () => {
|
|||
.should('equal', 'c2')
|
||||
|
||||
cy.getByTestID('toolbar-tab').click()
|
||||
cy.get('.variables-toolbar--label')
|
||||
cy.get('.flux-toolbar--list-item')
|
||||
.first()
|
||||
.trigger('mouseover')
|
||||
// toggle the variable dropdown in the VEO cell dashboard
|
||||
|
@ -241,7 +244,7 @@ describe('Dashboard', () => {
|
|||
.should('equal', 'v2')
|
||||
|
||||
cy.getByTestID('toolbar-tab').click()
|
||||
cy.get('.variables-toolbar--label')
|
||||
cy.get('.flux-toolbar--list-item')
|
||||
.eq(2)
|
||||
.trigger('mouseover')
|
||||
// toggle the variable dropdown in the VEO cell dashboard
|
||||
|
|
|
@ -369,7 +369,7 @@ describe('DataExplorer', () => {
|
|||
cy.getByTestID('switch-to-script-editor')
|
||||
.should('be.visible')
|
||||
.click()
|
||||
cy.getByTestID('flux-function aggregate.rate').click()
|
||||
cy.getByTestID('flux--aggregate.rate--inject').click()
|
||||
// check to see if import is defaulted to the top
|
||||
cy.get('.view-line')
|
||||
.first()
|
||||
|
@ -384,9 +384,9 @@ describe('DataExplorer', () => {
|
|||
})
|
||||
|
||||
// can hover over flux functions
|
||||
cy.getByTestID('toolbar-popover--contents').should('not.exist')
|
||||
cy.getByTestID('flux-function aggregateWindow').trigger('mouseover')
|
||||
cy.getByTestID('toolbar-popover--contents').should('exist')
|
||||
cy.getByTestID('flux-docs--aggregateWindow').should('not.exist')
|
||||
cy.getByTestID('flux--aggregateWindow').trigger('mouseover')
|
||||
cy.getByTestID('flux-docs--aggregateWindow').should('exist')
|
||||
|
||||
cy.getByTestID('switch-query-builder-confirm--button').click()
|
||||
|
||||
|
@ -463,12 +463,12 @@ describe('DataExplorer', () => {
|
|||
it('imports the appropriate packages to build a query', () => {
|
||||
cy.getByTestID('functions-toolbar-tab').click()
|
||||
|
||||
cy.getByTestID('flux-function from').click()
|
||||
cy.getByTestID('flux-function range').click()
|
||||
cy.getByTestID('flux-function math.abs').click()
|
||||
cy.getByTestID('flux-function math.floor').click()
|
||||
cy.getByTestID('flux-function strings.title').click()
|
||||
cy.getByTestID('flux-function strings.trim').click()
|
||||
cy.getByTestID('flux--from--inject').click()
|
||||
cy.getByTestID('flux--range--inject').click()
|
||||
cy.getByTestID('flux--math.abs--inject').click()
|
||||
cy.getByTestID('flux--math.floor--inject').click()
|
||||
cy.getByTestID('flux--strings.title--inject').click()
|
||||
cy.getByTestID('flux--strings.trim--inject').click()
|
||||
|
||||
cy.wait(100)
|
||||
|
||||
|
@ -490,7 +490,7 @@ describe('DataExplorer', () => {
|
|||
it('can use the function selector to build a query', () => {
|
||||
cy.getByTestID('functions-toolbar-tab').click()
|
||||
|
||||
cy.getByTestID('flux-function from').click()
|
||||
cy.getByTestID('flux--from--inject').click()
|
||||
|
||||
getTimeMachineText().then(text => {
|
||||
const expected = FROM.example
|
||||
|
@ -498,7 +498,7 @@ describe('DataExplorer', () => {
|
|||
cy.fluxEqual(text, expected).should('be.true')
|
||||
})
|
||||
|
||||
cy.getByTestID('flux-function range').click()
|
||||
cy.getByTestID('flux--range--inject').click()
|
||||
|
||||
getTimeMachineText().then(text => {
|
||||
const expected = `${FROM.example}|>${RANGE.example}`
|
||||
|
@ -506,7 +506,7 @@ describe('DataExplorer', () => {
|
|||
cy.fluxEqual(text, expected).should('be.true')
|
||||
})
|
||||
|
||||
cy.getByTestID('flux-function mean').click()
|
||||
cy.getByTestID('flux--mean--inject').click()
|
||||
|
||||
getTimeMachineText().then(text => {
|
||||
const expected = `${FROM.example}|>${RANGE.example}|>${MEAN.example}`
|
||||
|
@ -517,7 +517,7 @@ describe('DataExplorer', () => {
|
|||
|
||||
it('can filter aggregation functions by name from script editor mode', () => {
|
||||
cy.get('.cf-input-field').type('covariance')
|
||||
cy.getByTestID('toolbar-function').should('have.length', 1)
|
||||
cy.get('.flux-toolbar--list-item').should('have.length', 1)
|
||||
})
|
||||
|
||||
it('shows the empty state when the query returns no results', () => {
|
||||
|
@ -556,13 +556,15 @@ describe('DataExplorer', () => {
|
|||
|
||||
cy.getByTestID('toolbar-tab').click()
|
||||
// checks to see if the default variables exist
|
||||
cy.get('.variables-toolbar--label').contains('timeRangeStart')
|
||||
cy.get('.variables-toolbar--label').contains('timeRangeStop')
|
||||
cy.get('.variables-toolbar--label').contains('windowPeriod')
|
||||
cy.getByTestID('variable--timeRangeStart')
|
||||
cy.getByTestID('variable--timeRangeStop')
|
||||
cy.getByTestID('variable--windowPeriod')
|
||||
//insert variable name by clicking on variable
|
||||
cy.get('.variables-toolbar--label')
|
||||
cy.get('.flux-toolbar--variable')
|
||||
.first()
|
||||
.click()
|
||||
.within(() => {
|
||||
cy.contains('Inject').click()
|
||||
})
|
||||
|
||||
cy.getByTestID('save-query-as').click()
|
||||
cy.getByTestID('task--radio-button').click()
|
||||
|
|
|
@ -88,7 +88,7 @@ const FluxEditorMonaco: FC<Props> = ({
|
|||
}
|
||||
|
||||
return (
|
||||
<div className="time-machine-editor" data-testid="flux-editor">
|
||||
<div className="flux-editor--monaco" data-testid="flux-editor">
|
||||
<GetResources resources={[ResourceType.Buckets]}>
|
||||
<FluxBucketProvider />
|
||||
</GetResources>
|
||||
|
|
|
@ -1,330 +0,0 @@
|
|||
import React, {
|
||||
CSSProperties,
|
||||
PureComponent,
|
||||
ReactElement,
|
||||
MouseEvent,
|
||||
} from 'react'
|
||||
import classnames from 'classnames'
|
||||
import calculateSize from 'calculate-size'
|
||||
|
||||
import DivisionHeader from 'src/shared/components/threesizer/DivisionHeader'
|
||||
import {
|
||||
HANDLE_VERTICAL,
|
||||
HANDLE_HORIZONTAL,
|
||||
MIN_HANDLE_PIXELS,
|
||||
} from 'src/shared/constants/index'
|
||||
|
||||
const NOOP = () => {}
|
||||
|
||||
interface Props {
|
||||
handlePixels: number
|
||||
id: string
|
||||
size: number
|
||||
name: string
|
||||
offset: number
|
||||
draggable: boolean
|
||||
orientation: string
|
||||
handleDisplay: string
|
||||
style: CSSProperties
|
||||
activeHandleID: string
|
||||
headerOrientation: string
|
||||
render: (visibility: string, pixels: number) => ReactElement<any>
|
||||
onHandleStartDrag: (id: string, e: MouseEvent<HTMLElement>) => void
|
||||
onDoubleClick: (id: string) => void
|
||||
onMaximize: (id: string) => void
|
||||
onMinimize: (id: string) => void
|
||||
headerButtons: JSX.Element[]
|
||||
}
|
||||
|
||||
class Division extends PureComponent<Props> {
|
||||
public static defaultProps = {
|
||||
name: '',
|
||||
handleDisplay: 'visible',
|
||||
style: {},
|
||||
headerButtons: [],
|
||||
}
|
||||
|
||||
private collapseThreshold: number = 0
|
||||
private divisionRef: React.RefObject<HTMLDivElement>
|
||||
private divisionPixels: number = 0
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.divisionRef = React.createRef<HTMLDivElement>()
|
||||
}
|
||||
|
||||
public componentDidMount() {
|
||||
const {name} = this.props
|
||||
this.calcDivisionPixels()
|
||||
|
||||
if (!name) {
|
||||
return 0
|
||||
}
|
||||
|
||||
const {width} = calculateSize(name, {
|
||||
font: '"Roboto", Helvetica, Arial, Tahoma, Verdana, sans-serif',
|
||||
fontSize: '16px',
|
||||
fontWeight: '500',
|
||||
})
|
||||
const NAME_OFFSET = 96
|
||||
|
||||
this.collapseThreshold = width + NAME_OFFSET
|
||||
}
|
||||
|
||||
public componentDidUpdate() {
|
||||
this.calcDivisionPixels()
|
||||
}
|
||||
|
||||
public render() {
|
||||
const {render} = this.props
|
||||
|
||||
return (
|
||||
<div
|
||||
className={this.containerClass}
|
||||
style={this.containerStyle}
|
||||
ref={this.divisionRef}
|
||||
>
|
||||
{this.renderDragHandle}
|
||||
<div className={this.contentsClass} style={this.contentStyle}>
|
||||
{this.renderHeader}
|
||||
<div className="threesizer--body">
|
||||
{render(this.visibility, this.divisionPixels)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
private get renderHeader(): JSX.Element {
|
||||
const {name, headerButtons, orientation} = this.props
|
||||
|
||||
if (!name) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (orientation === HANDLE_VERTICAL) {
|
||||
return (
|
||||
<DivisionHeader
|
||||
buttons={headerButtons}
|
||||
onMinimize={this.handleMinimize}
|
||||
onMaximize={this.handleMaximize}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private get visibility(): string {
|
||||
if (this.props.size === 0) {
|
||||
return 'hidden'
|
||||
}
|
||||
|
||||
return 'visible'
|
||||
}
|
||||
|
||||
private get title(): string {
|
||||
return 'Drag to resize.\nDouble click to expand.'
|
||||
}
|
||||
|
||||
private get contentStyle(): CSSProperties {
|
||||
if (this.props.orientation === HANDLE_HORIZONTAL) {
|
||||
return {
|
||||
height: `calc(100% - ${this.handlePixels}px)`,
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
width: `calc(100% - ${this.handlePixels}px)`,
|
||||
}
|
||||
}
|
||||
|
||||
private get renderDragHandle(): JSX.Element {
|
||||
const {draggable} = this.props
|
||||
|
||||
return (
|
||||
<div
|
||||
style={this.handleStyle}
|
||||
title={this.title}
|
||||
draggable={draggable}
|
||||
onDragStart={this.drag}
|
||||
className={this.handleClass}
|
||||
onDoubleClick={this.handleDoubleClick}
|
||||
>
|
||||
{this.renderDragHandleContents}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
private get renderDragHandleContents(): JSX.Element {
|
||||
const {name, handlePixels, orientation, headerButtons} = this.props
|
||||
|
||||
if (!name) {
|
||||
return
|
||||
}
|
||||
|
||||
if (
|
||||
orientation === HANDLE_HORIZONTAL &&
|
||||
handlePixels >= MIN_HANDLE_PIXELS
|
||||
) {
|
||||
return (
|
||||
<DivisionHeader
|
||||
buttons={headerButtons}
|
||||
onMinimize={this.handleMinimize}
|
||||
onMaximize={this.handleMaximize}
|
||||
name={name}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
if (handlePixels >= MIN_HANDLE_PIXELS) {
|
||||
return <div className={this.titleClass}>{name}</div>
|
||||
}
|
||||
}
|
||||
|
||||
private get handleStyle(): CSSProperties {
|
||||
const {handleDisplay: display, orientation, handlePixels} = this.props
|
||||
|
||||
if (orientation === HANDLE_HORIZONTAL) {
|
||||
return {
|
||||
display,
|
||||
height: `${handlePixels}px`,
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
display,
|
||||
width: `${handlePixels}px`,
|
||||
}
|
||||
}
|
||||
|
||||
private get containerStyle(): CSSProperties {
|
||||
const {style, orientation} = this.props
|
||||
if (orientation === HANDLE_HORIZONTAL) {
|
||||
return {
|
||||
...style,
|
||||
height: this.size,
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...style,
|
||||
width: this.size,
|
||||
}
|
||||
}
|
||||
|
||||
private get size(): string {
|
||||
const {size, offset} = this.props
|
||||
return `calc((100% - ${offset}px) * ${size} + ${this.handlePixels}px)`
|
||||
}
|
||||
|
||||
private get handlePixels(): number {
|
||||
if (this.props.handleDisplay === 'none') {
|
||||
return 0
|
||||
}
|
||||
|
||||
return this.props.handlePixels
|
||||
}
|
||||
|
||||
private get containerClass(): string {
|
||||
const {orientation} = this.props
|
||||
const isAnyHandleBeingDragged = !!this.props.activeHandleID
|
||||
return classnames('threesizer--division', {
|
||||
dragging: isAnyHandleBeingDragged,
|
||||
vertical: orientation === HANDLE_VERTICAL,
|
||||
horizontal: orientation === HANDLE_HORIZONTAL,
|
||||
})
|
||||
}
|
||||
|
||||
private get handleClass(): string {
|
||||
const {draggable, orientation, name} = this.props
|
||||
|
||||
const collapsed = orientation === HANDLE_VERTICAL && this.isTitleObscured
|
||||
|
||||
return classnames('threesizer--handle', {
|
||||
'threesizer--collapsed': collapsed,
|
||||
disabled: !draggable,
|
||||
dragging: this.isDragging,
|
||||
vertical: orientation === HANDLE_VERTICAL,
|
||||
horizontal: orientation === HANDLE_HORIZONTAL,
|
||||
named: name,
|
||||
})
|
||||
}
|
||||
|
||||
private get contentsClass(): string {
|
||||
const {headerOrientation, size} = this.props
|
||||
return classnames(`threesizer--contents ${headerOrientation}`, {
|
||||
'no-shadows': !size,
|
||||
})
|
||||
}
|
||||
|
||||
private get titleClass(): string {
|
||||
const {orientation} = this.props
|
||||
|
||||
const collapsed = orientation === HANDLE_VERTICAL && this.isTitleObscured
|
||||
|
||||
return classnames('threesizer--title', {
|
||||
'threesizer--collapsed': collapsed,
|
||||
vertical: orientation === HANDLE_VERTICAL,
|
||||
horizontal: orientation === HANDLE_HORIZONTAL,
|
||||
})
|
||||
}
|
||||
|
||||
private get isTitleObscured(): boolean {
|
||||
if (this.props.size === 0) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (!this.divisionRef.current || this.props.size >= 0.33) {
|
||||
return false
|
||||
}
|
||||
|
||||
const {width} = this.divisionRef.current.getBoundingClientRect()
|
||||
|
||||
return width <= this.collapseThreshold
|
||||
}
|
||||
|
||||
private get isDragging(): boolean {
|
||||
const {id, activeHandleID} = this.props
|
||||
return id === activeHandleID
|
||||
}
|
||||
|
||||
private drag = e => {
|
||||
const {draggable, id} = this.props
|
||||
|
||||
if (!draggable) {
|
||||
return NOOP
|
||||
}
|
||||
|
||||
this.props.onHandleStartDrag(id, e)
|
||||
}
|
||||
|
||||
private handleDoubleClick = (): void => {
|
||||
const {onDoubleClick, id} = this.props
|
||||
|
||||
onDoubleClick(id)
|
||||
}
|
||||
|
||||
private handleMinimize = (): void => {
|
||||
const {id, onMinimize} = this.props
|
||||
onMinimize(id)
|
||||
}
|
||||
|
||||
private handleMaximize = (): void => {
|
||||
const {id, onMaximize} = this.props
|
||||
onMaximize(id)
|
||||
}
|
||||
|
||||
private calcDivisionPixels = (): void => {
|
||||
const {orientation} = this.props
|
||||
|
||||
const {clientWidth, clientHeight} = this.divisionRef.current
|
||||
|
||||
let divisionPixels = clientWidth
|
||||
if (orientation === HANDLE_HORIZONTAL) {
|
||||
divisionPixels = clientHeight
|
||||
}
|
||||
|
||||
this.divisionPixels = divisionPixels
|
||||
}
|
||||
}
|
||||
|
||||
export default Division
|
|
@ -1,33 +0,0 @@
|
|||
import React, {PureComponent} from 'react'
|
||||
|
||||
interface Props {
|
||||
onMinimize: () => void
|
||||
onMaximize: () => void
|
||||
buttons: JSX.Element[]
|
||||
name?: string
|
||||
}
|
||||
|
||||
class DivisionHeader extends PureComponent<Props> {
|
||||
public render() {
|
||||
return (
|
||||
<div className="threesizer--header">
|
||||
{this.renderName}
|
||||
<div className="threesizer--header-controls">
|
||||
{this.props.buttons.map(b => b)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
private get renderName(): JSX.Element {
|
||||
const {name} = this.props
|
||||
|
||||
if (!name) {
|
||||
return
|
||||
}
|
||||
|
||||
return <div className="threesizer--header-name">{name}</div>
|
||||
}
|
||||
}
|
||||
|
||||
export default DivisionHeader
|
|
@ -1,226 +0,0 @@
|
|||
/*
|
||||
Resizable Container with 3 divisions
|
||||
------------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
.threesizer {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
&.dragging .threesizer--division {
|
||||
@include no-user-select();
|
||||
pointer-events: none;
|
||||
}
|
||||
&.vertical {
|
||||
flex-direction: row;
|
||||
}
|
||||
&.horizontal {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
.threesizer--division {
|
||||
/* overflow: hidden; */
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
transition: height 0.25s ease-in-out, width 0.25s ease-in-out;
|
||||
&.dragging {
|
||||
transition: none;
|
||||
}
|
||||
&.vertical {
|
||||
flex-direction: row;
|
||||
}
|
||||
&.horizontal {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
/* Draggable Handle With Title */
|
||||
|
||||
.threesizer--handle {
|
||||
@include no-user-select();
|
||||
background-color: $g4-onyx;
|
||||
transition: background-color 0.25s ease, color 0.25s ease;
|
||||
&.vertical {
|
||||
border-right: solid 2px $g3-castle;
|
||||
&:hover,
|
||||
&.dragging {
|
||||
cursor: col-resize;
|
||||
}
|
||||
}
|
||||
&.horizontal {
|
||||
border-bottom: solid 2px $g3-castle;
|
||||
&:hover,
|
||||
&.dragging {
|
||||
cursor: row-resize;
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
&.disabled {
|
||||
cursor: pointer;
|
||||
}
|
||||
color: $g16-pearl;
|
||||
background-color: $g5-pepper;
|
||||
}
|
||||
&.dragging {
|
||||
color: $c-laser;
|
||||
background-color: $g5-pepper;
|
||||
}
|
||||
}
|
||||
|
||||
.threesizer--title {
|
||||
padding-left: 14px;
|
||||
position: relative;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
color: $g11-sidewalk;
|
||||
z-index: 1;
|
||||
transition: transform 0.25s ease, letter-spacing 0.25s ease;
|
||||
&.vertical {
|
||||
transform: translate(20px, 14px);
|
||||
&.threesizer--collapsed {
|
||||
transform: translate(0, 5px) rotate(90deg) scale(0.75);
|
||||
letter-spacing: 0.15em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$threesizer-shadow-size: 9px;
|
||||
$threesizer-z-index: 2;
|
||||
$threesizer-shadow-start: fade-out($g0-obsidian, 0.82);
|
||||
$threesizer-shadow-stop: fade-out($g0-obsidian, 1);
|
||||
.threesizer--contents {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
flex-wrap: nowrap;
|
||||
position: relative;
|
||||
&.horizontal {
|
||||
flex-direction: row;
|
||||
}
|
||||
&.vertical {
|
||||
flex-direction: column;
|
||||
} // Bottom Shadow
|
||||
&.horizontal:after,
|
||||
&.vertical:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
z-index: $threesizer-z-index;
|
||||
}
|
||||
&.horizontal:after {
|
||||
width: 100%;
|
||||
height: $threesizer-shadow-size;
|
||||
@include gradient-v($threesizer-shadow-stop, $threesizer-shadow-start);
|
||||
}
|
||||
&.vertical:after {
|
||||
height: 100%;
|
||||
width: $threesizer-shadow-size;
|
||||
@include gradient-h($threesizer-shadow-stop, $threesizer-shadow-start);
|
||||
}
|
||||
}
|
||||
|
||||
// Hide bottom shadow on last division
|
||||
.threesizer--contents.no-shadows:before,
|
||||
.threesizer--contents.no-shadows:after,
|
||||
.threesizer--division:last-child .threesizer--contents:after {
|
||||
content: none;
|
||||
display: none;
|
||||
}
|
||||
|
||||
// Header
|
||||
.threesizer--header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
padding: 0 11px;
|
||||
background-color: $g2-kevlar;
|
||||
.horizontal>& {
|
||||
width: 44px;
|
||||
border-right: 2px solid $g4-onyx;
|
||||
}
|
||||
.vertical>& {
|
||||
height: 44px;
|
||||
border-bottom: 2px solid $g4-onyx;
|
||||
}
|
||||
}
|
||||
|
||||
.threesizer--header-name {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
color: $g11-sidewalk;
|
||||
padding-left: 4px;
|
||||
padding-right: 2px;
|
||||
}
|
||||
|
||||
.threesizer--header-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: nowrap;
|
||||
|
||||
> * {
|
||||
margin-left: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.threesizer--body {
|
||||
position: relative;
|
||||
|
||||
.horizontal>&:only-child {
|
||||
width: 100%;
|
||||
}
|
||||
.vertical>&:only-child {
|
||||
height: 100%;
|
||||
}
|
||||
.threesizer--header+& {
|
||||
flex: 1 1 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
// Division context menus
|
||||
.threesizer-context--buttons {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
// Header Dropdown Menu
|
||||
.threesizer--menu {
|
||||
.dropdown-menu {
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Hide Header children when collapsed
|
||||
.threesizer--handle.vertical.threesizer--collapsed+.threesizer--contents>.threesizer--header>* {
|
||||
display: none;
|
||||
}
|
||||
|
||||
// When threesizer has horizontal handles, division headers appear inside
|
||||
// the drag handle and styles must adapt
|
||||
.threesizer--handle.horizontal.named {
|
||||
background-color: transparent;
|
||||
border: 0;
|
||||
|
||||
.threesizer--header {
|
||||
border-right: 0;
|
||||
border-bottom: 2px solid $g4-onyx;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
justify-content: space-between;
|
||||
transition: background-color 0.25s ease;
|
||||
}
|
||||
|
||||
&:hover .threesizer--header,
|
||||
&.dragging .threesizer--header {
|
||||
background-color: $g3-castle;
|
||||
}
|
||||
}
|
|
@ -1,508 +0,0 @@
|
|||
import React, {Component, ReactElement, MouseEvent, CSSProperties} from 'react'
|
||||
import classnames from 'classnames'
|
||||
import uuid from 'uuid'
|
||||
import _ from 'lodash'
|
||||
|
||||
import Division from 'src/shared/components/threesizer/Division'
|
||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
|
||||
import {
|
||||
HANDLE_NONE,
|
||||
HANDLE_PIXELS,
|
||||
HANDLE_HORIZONTAL,
|
||||
HANDLE_VERTICAL,
|
||||
MIN_SIZE,
|
||||
MAX_SIZE,
|
||||
} from 'src/shared/constants/'
|
||||
|
||||
const initialDragEvent = {
|
||||
percentX: 0,
|
||||
percentY: 0,
|
||||
mouseX: null,
|
||||
mouseY: null,
|
||||
}
|
||||
|
||||
interface State {
|
||||
activeHandleID: string
|
||||
divisions: DivisionState[]
|
||||
dragDirection: string
|
||||
dragEvent: any
|
||||
}
|
||||
|
||||
export interface DivisionProps {
|
||||
name?: string
|
||||
handleDisplay?: string
|
||||
handlePixels?: number
|
||||
style?: CSSProperties
|
||||
size?: number
|
||||
headerButtons?: JSX.Element[]
|
||||
headerOrientation?: string
|
||||
render: (visibility: string, pixels: number) => ReactElement<any>
|
||||
}
|
||||
|
||||
interface DivisionState extends DivisionProps {
|
||||
id: string
|
||||
size: number
|
||||
}
|
||||
|
||||
interface Props {
|
||||
divisions: DivisionProps[]
|
||||
orientation: string
|
||||
containerClass: string
|
||||
}
|
||||
|
||||
@ErrorHandling
|
||||
class Threesizer extends Component<Props, State> {
|
||||
public static defaultProps = {
|
||||
orientation: HANDLE_HORIZONTAL,
|
||||
containerClass: '',
|
||||
}
|
||||
|
||||
private containerRef: HTMLElement
|
||||
private percentChangeX: number = 0
|
||||
private percentChangeY: number = 0
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
activeHandleID: null,
|
||||
divisions: this.initialDivisions,
|
||||
dragEvent: initialDragEvent,
|
||||
dragDirection: '',
|
||||
}
|
||||
}
|
||||
|
||||
public componentDidMount() {
|
||||
document.addEventListener('mouseup', this.handleStopDrag)
|
||||
document.addEventListener('mouseleave', this.handleStopDrag)
|
||||
}
|
||||
|
||||
public componentWillUnmount() {
|
||||
document.removeEventListener('mouseup', this.handleStopDrag)
|
||||
document.removeEventListener('mouseleave', this.handleStopDrag)
|
||||
}
|
||||
|
||||
public componentDidUpdate(__, prevState) {
|
||||
const {dragEvent} = this.state
|
||||
const {orientation} = this.props
|
||||
|
||||
if (_.isEqual(dragEvent, prevState.dragEvent)) {
|
||||
return
|
||||
}
|
||||
|
||||
this.percentChangeX = this.pixelsToPercentX(
|
||||
prevState.dragEvent.mouseX,
|
||||
dragEvent.mouseX
|
||||
)
|
||||
|
||||
this.percentChangeY = this.pixelsToPercentY(
|
||||
prevState.dragEvent.mouseY,
|
||||
dragEvent.mouseY
|
||||
)
|
||||
|
||||
const {percentX, percentY} = dragEvent
|
||||
const {dragEvent: prevDrag} = prevState
|
||||
|
||||
if (orientation === HANDLE_VERTICAL) {
|
||||
const left = percentX < prevDrag.percentX
|
||||
|
||||
if (left) {
|
||||
return this.move.left()
|
||||
}
|
||||
|
||||
return this.move.right()
|
||||
}
|
||||
|
||||
const up = percentY < prevDrag.percentY
|
||||
|
||||
if (up) {
|
||||
return this.move.up()
|
||||
}
|
||||
|
||||
return this.move.down()
|
||||
}
|
||||
|
||||
public render() {
|
||||
const {activeHandleID, divisions} = this.state
|
||||
const {orientation} = this.props
|
||||
|
||||
return (
|
||||
<div
|
||||
className={this.className}
|
||||
onMouseUp={this.handleStopDrag}
|
||||
onMouseMove={this.handleDrag}
|
||||
ref={r => (this.containerRef = r)}
|
||||
>
|
||||
{divisions.map((d, i) => {
|
||||
const headerOrientation = _.get(d, 'headerOrientation', orientation)
|
||||
|
||||
return (
|
||||
<Division
|
||||
key={d.id}
|
||||
id={d.id}
|
||||
name={d.name}
|
||||
size={d.size}
|
||||
style={d.style}
|
||||
offset={this.offset}
|
||||
draggable={i > 0}
|
||||
orientation={orientation}
|
||||
handlePixels={d.handlePixels}
|
||||
handleDisplay={d.handleDisplay}
|
||||
activeHandleID={activeHandleID}
|
||||
onMaximize={this.handleMaximize}
|
||||
onMinimize={this.handleMinimize}
|
||||
headerOrientation={headerOrientation}
|
||||
onDoubleClick={this.handleDoubleClick}
|
||||
render={this.props.divisions[i].render}
|
||||
onHandleStartDrag={this.handleStartDrag}
|
||||
headerButtons={this.props.divisions[i].headerButtons}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
private get offset(): number {
|
||||
const handlesPixelCount = this.state.divisions.reduce((acc, d) => {
|
||||
if (d.handleDisplay === HANDLE_NONE) {
|
||||
return acc
|
||||
}
|
||||
|
||||
return acc + d.handlePixels
|
||||
}, 0)
|
||||
|
||||
return handlesPixelCount
|
||||
}
|
||||
|
||||
private get className(): string {
|
||||
const {orientation, containerClass} = this.props
|
||||
const {activeHandleID} = this.state
|
||||
|
||||
return classnames(`threesizer ${containerClass}`, {
|
||||
dragging: activeHandleID,
|
||||
horizontal: orientation === HANDLE_HORIZONTAL,
|
||||
vertical: orientation === HANDLE_VERTICAL,
|
||||
})
|
||||
}
|
||||
|
||||
private get initialDivisions() {
|
||||
const {divisions} = this.props
|
||||
|
||||
const size = 1 / divisions.length
|
||||
return divisions.map(d => ({
|
||||
...d,
|
||||
id: uuid.v4(),
|
||||
size: d.size || size,
|
||||
handlePixels: d.handlePixels || HANDLE_PIXELS,
|
||||
}))
|
||||
}
|
||||
|
||||
private handleDoubleClick = (id: string): void => {
|
||||
const clickedDiv = this.state.divisions.find(d => d.id === id)
|
||||
|
||||
if (!clickedDiv) {
|
||||
return
|
||||
}
|
||||
|
||||
const isMaxed = clickedDiv.size === 1
|
||||
|
||||
if (isMaxed) {
|
||||
return this.equalize()
|
||||
}
|
||||
|
||||
const divisions = this.state.divisions.map(d => {
|
||||
if (d.id !== id) {
|
||||
return {...d, size: 0}
|
||||
}
|
||||
|
||||
return {...d, size: 1}
|
||||
})
|
||||
|
||||
this.setState({divisions})
|
||||
}
|
||||
|
||||
private handleMaximize = (id: string): void => {
|
||||
const maxDiv = this.state.divisions.find(d => d.id === id)
|
||||
|
||||
if (!maxDiv) {
|
||||
return
|
||||
}
|
||||
|
||||
const divisions = this.state.divisions.map(d => {
|
||||
if (d.id !== id) {
|
||||
return {...d, size: 0}
|
||||
}
|
||||
|
||||
return {...d, size: 1}
|
||||
})
|
||||
|
||||
this.setState({divisions})
|
||||
}
|
||||
|
||||
private handleMinimize = (id: string): void => {
|
||||
const minDiv = this.state.divisions.find(d => d.id === id)
|
||||
const numDivisions = this.state.divisions.length
|
||||
|
||||
if (!minDiv) {
|
||||
return
|
||||
}
|
||||
|
||||
let size
|
||||
if (numDivisions <= 1) {
|
||||
size = 1
|
||||
} else {
|
||||
size = 1 / (this.state.divisions.length - 1)
|
||||
}
|
||||
|
||||
const divisions = this.state.divisions.map(d => {
|
||||
if (d.id !== id) {
|
||||
return {...d, size}
|
||||
}
|
||||
|
||||
return {...d, size: 0}
|
||||
})
|
||||
|
||||
this.setState({divisions})
|
||||
}
|
||||
|
||||
private equalize = () => {
|
||||
const denominator = this.state.divisions.length
|
||||
const divisions = this.state.divisions.map(d => {
|
||||
return {...d, size: 1 / denominator}
|
||||
})
|
||||
|
||||
this.setState({divisions})
|
||||
}
|
||||
|
||||
private handleStartDrag = (activeHandleID, e: MouseEvent<HTMLElement>) => {
|
||||
const dragEvent = this.mousePosWithinContainer(e)
|
||||
this.setState({activeHandleID, dragEvent})
|
||||
}
|
||||
|
||||
private handleStopDrag = () => {
|
||||
this.setState({activeHandleID: '', dragEvent: initialDragEvent})
|
||||
}
|
||||
|
||||
private mousePosWithinContainer = (e: MouseEvent<HTMLElement>) => {
|
||||
const {pageY, pageX} = e
|
||||
const {top, left, width, height} = this.containerRef.getBoundingClientRect()
|
||||
|
||||
const mouseX = pageX - left
|
||||
const mouseY = pageY - top
|
||||
|
||||
const percentX = mouseX / width
|
||||
const percentY = mouseY / height
|
||||
|
||||
return {
|
||||
mouseX,
|
||||
mouseY,
|
||||
percentX,
|
||||
percentY,
|
||||
}
|
||||
}
|
||||
|
||||
private pixelsToPercentX = (startValue, endValue) => {
|
||||
if (!startValue || !endValue) {
|
||||
return 0
|
||||
}
|
||||
|
||||
const delta = Math.abs(startValue - endValue)
|
||||
const {width} = this.containerRef.getBoundingClientRect()
|
||||
|
||||
return delta / width
|
||||
}
|
||||
|
||||
private pixelsToPercentY = (startValue, endValue) => {
|
||||
if (!startValue || !endValue) {
|
||||
return 0
|
||||
}
|
||||
|
||||
const delta = startValue - endValue
|
||||
const {height} = this.containerRef.getBoundingClientRect()
|
||||
|
||||
return Math.abs(delta / height)
|
||||
}
|
||||
|
||||
private handleDrag = (e: MouseEvent<HTMLElement>) => {
|
||||
const {activeHandleID} = this.state
|
||||
if (!activeHandleID) {
|
||||
return
|
||||
}
|
||||
|
||||
const dragEvent = this.mousePosWithinContainer(e)
|
||||
this.setState({dragEvent})
|
||||
}
|
||||
|
||||
private get move() {
|
||||
const {activeHandleID} = this.state
|
||||
|
||||
const activePosition = _.findIndex(
|
||||
this.state.divisions,
|
||||
d => d.id === activeHandleID
|
||||
)
|
||||
|
||||
return {
|
||||
up: this.up(activePosition),
|
||||
down: this.down(activePosition),
|
||||
left: this.left(activePosition),
|
||||
right: this.right(activePosition),
|
||||
}
|
||||
}
|
||||
|
||||
private up = activePosition => () => {
|
||||
const divisions = this.state.divisions.map((d, i) => {
|
||||
if (!activePosition) {
|
||||
return d
|
||||
}
|
||||
|
||||
const first = i === 0
|
||||
const before = i === activePosition - 1
|
||||
const current = i === activePosition
|
||||
|
||||
if (first && !before) {
|
||||
const second = this.state.divisions[1]
|
||||
if (second && second.size === 0) {
|
||||
return {...d, size: this.shorter(d.size)}
|
||||
}
|
||||
|
||||
return {...d}
|
||||
}
|
||||
|
||||
if (before) {
|
||||
return {...d, size: this.shorter(d.size)}
|
||||
}
|
||||
|
||||
if (current) {
|
||||
return {...d, size: this.taller(d.size)}
|
||||
}
|
||||
|
||||
return {...d}
|
||||
})
|
||||
|
||||
this.setState({divisions})
|
||||
}
|
||||
|
||||
private left = activePosition => () => {
|
||||
const divisions = this.state.divisions.map((d, i) => {
|
||||
if (!activePosition) {
|
||||
return d
|
||||
}
|
||||
|
||||
const first = i === 0
|
||||
const before = i === activePosition - 1
|
||||
const active = i === activePosition
|
||||
|
||||
if (first && !before) {
|
||||
const second = this.state.divisions[1]
|
||||
if (second && second.size === 0) {
|
||||
return {...d, size: this.thinner(d.size)}
|
||||
}
|
||||
|
||||
return {...d}
|
||||
}
|
||||
|
||||
if (before) {
|
||||
return {...d, size: this.thinner(d.size)}
|
||||
}
|
||||
|
||||
if (active) {
|
||||
return {...d, size: this.fatter(d.size)}
|
||||
}
|
||||
|
||||
return {...d}
|
||||
})
|
||||
|
||||
this.setState({divisions})
|
||||
}
|
||||
|
||||
private right = activePosition => () => {
|
||||
const divisions = this.state.divisions.map((d, i, divs) => {
|
||||
const before = i === activePosition - 1
|
||||
const active = i === activePosition
|
||||
const after = i === activePosition + 1
|
||||
|
||||
if (before) {
|
||||
return {...d, size: this.fatter(d.size)}
|
||||
}
|
||||
|
||||
if (active) {
|
||||
return {...d, size: this.thinner(d.size)}
|
||||
}
|
||||
|
||||
if (after) {
|
||||
const leftIndex = i - 1
|
||||
const left = _.get(divs, leftIndex, {size: 'none'})
|
||||
|
||||
if (left && left.size === 0) {
|
||||
return {...d, size: this.thinner(d.size)}
|
||||
}
|
||||
|
||||
return {...d}
|
||||
}
|
||||
|
||||
return {...d}
|
||||
})
|
||||
|
||||
this.setState({divisions})
|
||||
}
|
||||
|
||||
private down = activePosition => () => {
|
||||
const divisions = this.state.divisions.map((d, i, divs) => {
|
||||
const before = i === activePosition - 1
|
||||
const current = i === activePosition
|
||||
const after = i === activePosition + 1
|
||||
|
||||
if (before) {
|
||||
return {...d, size: this.taller(d.size)}
|
||||
}
|
||||
|
||||
if (current) {
|
||||
return {...d, size: this.shorter(d.size)}
|
||||
}
|
||||
|
||||
if (after) {
|
||||
const above = divs[i - 1]
|
||||
if (above && above.size === 0) {
|
||||
return {...d, size: this.shorter(d.size)}
|
||||
}
|
||||
|
||||
return {...d}
|
||||
}
|
||||
|
||||
return {...d}
|
||||
})
|
||||
|
||||
this.setState({divisions})
|
||||
}
|
||||
|
||||
private taller = (size: number): number => {
|
||||
const newSize = size + this.percentChangeY
|
||||
return this.enforceMax(newSize)
|
||||
}
|
||||
|
||||
private fatter = (size: number): number => {
|
||||
const newSize = size + this.percentChangeX
|
||||
return this.enforceMax(newSize)
|
||||
}
|
||||
|
||||
private shorter = (size: number): number => {
|
||||
const newSize = size - this.percentChangeY
|
||||
return this.enforceMin(newSize)
|
||||
}
|
||||
|
||||
private thinner = (size: number): number => {
|
||||
const newSize = size - this.percentChangeX
|
||||
return this.enforceMin(newSize)
|
||||
}
|
||||
|
||||
private enforceMax = (size: number): number => {
|
||||
return size > MAX_SIZE ? MAX_SIZE : size
|
||||
}
|
||||
|
||||
private enforceMin = (size: number): number => {
|
||||
return size < MIN_SIZE ? MIN_SIZE : size
|
||||
}
|
||||
}
|
||||
|
||||
export default Threesizer
|
|
@ -14,7 +14,6 @@
|
|||
@import 'src/shared/components/avatar/Avatar.scss';
|
||||
@import 'src/shared/components/tables/TableGraphs.scss';
|
||||
@import 'src/shared/components/notifications/Notifications.scss';
|
||||
@import 'src/shared/components/threesizer/Threesizer.scss';
|
||||
@import 'src/shared/components/graph_tips/GraphTips.scss';
|
||||
@import 'src/shared/components/cells/Dashboards.scss';
|
||||
@import 'src/shared/components/code_mirror/CodeMirror.scss';
|
||||
|
@ -81,16 +80,14 @@
|
|||
@import 'src/timeMachine/components/SelectorList.scss';
|
||||
@import 'src/timeMachine/components/Queries.scss';
|
||||
@import 'src/timeMachine/components/EditorShortcutsTooltip.scss';
|
||||
@import 'src/timeMachine/components/SearchBar.scss';
|
||||
@import 'src/timeMachine/components/QueriesSwitcher.scss';
|
||||
@import 'src/timeMachine/components/TimeMachineFluxEditor.scss';
|
||||
@import 'src/timeMachine/components/FluxToolbar.scss';
|
||||
@import 'src/timeMachine/components/TagSelector.scss';
|
||||
@import 'src/timeMachine/components/QueryTab.scss';
|
||||
@import 'src/timeMachine/components/TimeMachine.scss';
|
||||
@import 'src/timeMachine/components/QueryBuilder.scss';
|
||||
@import 'src/timeMachine/components/RawFluxDataTable.scss';
|
||||
@import 'src/timeMachine/components/ToolbarTab.scss';
|
||||
@import 'src/timeMachine/components/variableToolbar/VariableToolbar.scss';
|
||||
@import 'src/timeMachine/components/fluxFunctionsToolbar/FluxFunctionsToolbar.scss';
|
||||
@import 'src/timeMachine/components/view_options/HistogramOptions.scss';
|
||||
@import 'src/timeMachine/components/view_options/ViewOptions.scss';
|
||||
|
|
|
@ -0,0 +1,163 @@
|
|||
$flux-toolbar--search-height: $cf-form-sm-height + $cf-marg-c;
|
||||
$flux-toolbar--item-height: $cf-marg-d;
|
||||
|
||||
.flux-toolbar {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
flex: 1 0 0;
|
||||
}
|
||||
|
||||
.flux-toolbar--tabs {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
height: $cf-form-md-height;
|
||||
flex: 0 0 $cf-form-md-height;
|
||||
}
|
||||
|
||||
.flux-toolbar--tab {
|
||||
border-radius: $cf-radius $cf-radius 0 0;
|
||||
flex: 1 0 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: $cf-form-sm-font;
|
||||
line-height: $cf-form-sm-font;
|
||||
font-weight: $cf-font-weight--medium;
|
||||
background-color: transparent;
|
||||
color: $g8-storm;
|
||||
margin-right: $cf-marg-a;
|
||||
transition: color 0.25s ease, background-color 0.25s ease;
|
||||
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
background-color: $g2-kevlar;
|
||||
color: $g13-mist;
|
||||
}
|
||||
|
||||
&.flux-toolbar--tab__active {
|
||||
background-color: $g3-castle;
|
||||
color: $g15-platinum;
|
||||
}
|
||||
}
|
||||
|
||||
.flux-toolbar--tab-contents {
|
||||
background-color: $g3-castle;
|
||||
flex: 1 0 0;
|
||||
position: relative;
|
||||
border-radius: 0 0 $cf-radius $cf-radius;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.flux-toolbar--search {
|
||||
padding: 0 $cf-marg-b;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: $flux-toolbar--search-height;
|
||||
}
|
||||
|
||||
.flux-toolbar--scroll-area {
|
||||
height: calc(100% - #{$flux-toolbar--search-height}) !important;
|
||||
}
|
||||
|
||||
.flux-toolbar--list {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.flux-toolbar--list-item,
|
||||
.flux-toolbar--heading {
|
||||
font-size: $cf-form-sm-font;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.flux-toolbar--list-item {
|
||||
padding-left: $cf-marg-b;
|
||||
padding-right: $cf-marg-b + $cf-marg-a + $cf-border;
|
||||
height: $flux-toolbar--item-height;
|
||||
line-height: $flux-toolbar--item-height;
|
||||
font-family: $cf-code-font;
|
||||
font-weight: $cf-font-weight--bold;
|
||||
transition: background-color 0.25s ease, color 0.25s ease;
|
||||
background-color: $g3-castle;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
> code {
|
||||
font-size: $cf-form-sm-font;
|
||||
background-color: $g1-raven;
|
||||
border-radius: $cf-radius-sm;
|
||||
padding: 0 $cf-marg-b;
|
||||
height: $cf-form-xs-height;
|
||||
line-height: $cf-form-xs-height;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
transition: background-color 0.25s ease, color 0.25s ease, text-shadow 0.25s ease;
|
||||
}
|
||||
|
||||
.flux-toolbar--injector {
|
||||
opacity: 0;
|
||||
transition: opacity 0.25s ease, color 0.25s ease, box-shadow 0.25s ease;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: $g4-onyx;
|
||||
|
||||
> code {
|
||||
background-color: $g2-kevlar;
|
||||
}
|
||||
|
||||
.flux-toolbar--injector {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.flux-toolbar--heading {
|
||||
padding: $cf-marg-b $cf-marg-c;
|
||||
padding-bottom: $cf-marg-b + $cf-marg-a;
|
||||
font-weight: $cf-font-weight--medium;
|
||||
color: $g15-platinum;
|
||||
border-bottom: $cf-border solid $g5-pepper;
|
||||
margin-bottom: $cf-marg-a;
|
||||
}
|
||||
|
||||
.flux-toolbar--variable {
|
||||
> code {
|
||||
color: $c-honeydew;
|
||||
}
|
||||
|
||||
&:hover > code {
|
||||
color: $c-krypton;
|
||||
text-shadow: 0 0 4px $c-rainforest;
|
||||
}
|
||||
}
|
||||
|
||||
.flux-toolbar--function {
|
||||
> code {
|
||||
color: $c-laser;
|
||||
}
|
||||
|
||||
&:hover > code {
|
||||
color: $c-hydrogen;
|
||||
text-shadow: 0 0 4px $c-ocean;
|
||||
}
|
||||
}
|
||||
|
||||
.flux-toolbar--popover {
|
||||
font-size: 13px;
|
||||
padding: $cf-marg-c + $cf-marg-a;
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
// Libraries
|
||||
import React, {FC, useState} from 'react'
|
||||
|
||||
// Components
|
||||
import FluxFunctionsToolbar from 'src/timeMachine/components/fluxFunctionsToolbar/FluxFunctionsToolbar'
|
||||
import VariableToolbar from 'src/timeMachine/components/variableToolbar/VariableToolbar'
|
||||
import FluxToolbarTab from 'src/timeMachine/components/FluxToolbarTab'
|
||||
|
||||
// Types
|
||||
import {FluxToolbarFunction} from 'src/types'
|
||||
|
||||
interface Props {
|
||||
activeQueryBuilderTab: string
|
||||
onInsertFluxFunction: (func: FluxToolbarFunction) => void
|
||||
onInsertVariable: (variableName: string) => void
|
||||
}
|
||||
|
||||
type FluxToolbarTabs = 'functions' | 'variables'
|
||||
|
||||
const FluxToolbar: FC<Props> = ({
|
||||
activeQueryBuilderTab,
|
||||
onInsertFluxFunction,
|
||||
onInsertVariable,
|
||||
}) => {
|
||||
const [activeTab, setActiveTab] = useState<FluxToolbarTabs>('functions')
|
||||
|
||||
const handleTabClick = (id: FluxToolbarTabs): void => {
|
||||
setActiveTab(id)
|
||||
}
|
||||
|
||||
let activeToolbar = (
|
||||
<FluxFunctionsToolbar onInsertFluxFunction={onInsertFluxFunction} />
|
||||
)
|
||||
|
||||
if (activeTab === 'variables') {
|
||||
activeToolbar = <VariableToolbar onClickVariable={onInsertVariable} />
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flux-toolbar">
|
||||
<div className="flux-toolbar--tabs">
|
||||
<FluxToolbarTab
|
||||
id="functions"
|
||||
onClick={handleTabClick}
|
||||
name="Functions"
|
||||
active={activeTab === 'functions'}
|
||||
testID="functions-toolbar-tab"
|
||||
/>
|
||||
{activeQueryBuilderTab !== 'customCheckQuery' && (
|
||||
<FluxToolbarTab
|
||||
id="variables"
|
||||
onClick={handleTabClick}
|
||||
name="Variables"
|
||||
active={activeTab === 'variables'}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="flux-toolbar--tab-contents">{activeToolbar}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default FluxToolbar
|
|
@ -19,7 +19,7 @@ interface State {
|
|||
|
||||
const DEBOUNCE_MS = 100
|
||||
|
||||
class SearchBar extends PureComponent<Props, State> {
|
||||
class FluxToolbarSearch extends PureComponent<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props)
|
||||
|
||||
|
@ -32,7 +32,7 @@ class SearchBar extends PureComponent<Props, State> {
|
|||
|
||||
public render() {
|
||||
return (
|
||||
<div className="search-bar">
|
||||
<div className="flux-toolbar--search">
|
||||
<Input
|
||||
type={InputType.Text}
|
||||
icon={IconFont.Search}
|
||||
|
@ -53,4 +53,4 @@ class SearchBar extends PureComponent<Props, State> {
|
|||
}
|
||||
}
|
||||
|
||||
export default SearchBar
|
||||
export default FluxToolbarSearch
|
|
@ -0,0 +1,40 @@
|
|||
// Libraries
|
||||
import React, {FC} from 'react'
|
||||
import classnames from 'classnames'
|
||||
|
||||
interface Props {
|
||||
onClick: (id: string) => void
|
||||
id: string
|
||||
name: string
|
||||
active: boolean
|
||||
testID?: string
|
||||
}
|
||||
|
||||
const ToolbarTab: FC<Props> = ({
|
||||
onClick,
|
||||
name,
|
||||
active,
|
||||
testID = 'toolbar-tab',
|
||||
id,
|
||||
}) => {
|
||||
const toolbarTabClass = classnames('flux-toolbar--tab', {
|
||||
'flux-toolbar--tab__active': active,
|
||||
})
|
||||
|
||||
const handleClick = (): void => {
|
||||
onClick(id)
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={toolbarTabClass}
|
||||
onClick={handleClick}
|
||||
title={name}
|
||||
data-testid={testID}
|
||||
>
|
||||
{name}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ToolbarTab
|
|
@ -1,8 +0,0 @@
|
|||
@import "src/style/modules";
|
||||
|
||||
.search-bar {
|
||||
padding: $ix-marg-b;
|
||||
flex-shrink: 0;
|
||||
border-bottom: $ix-border solid $g4-onyx;
|
||||
background-color: $g3-castle;
|
||||
}
|
|
@ -1,24 +1,32 @@
|
|||
.toolbar-tab-container {
|
||||
width: 100%;
|
||||
display: inline-flex;
|
||||
align-items: stretch;
|
||||
height: 38px;
|
||||
background-color: $g2-kevlar;
|
||||
padding: $ix-marg-b;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
$flux-editor--right-panel: 330px;
|
||||
|
||||
.time-machine-flux-editor {
|
||||
.flux-editor {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
top: $cf-marg-b;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-direction: row;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.time-machine-editor {
|
||||
// Monaco Editor
|
||||
.flux-editor--left-panel {
|
||||
flex: 1 0 0;
|
||||
position: relative;
|
||||
border-radius: $cf-radius;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
// Variables / Functions List
|
||||
.flux-editor--right-panel {
|
||||
flex: 0 0 ($flux-editor--right-panel - $cf-marg-b);
|
||||
margin-left: $cf-marg-b;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.flux-editor--monaco {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
@ -27,10 +35,9 @@
|
|||
}
|
||||
|
||||
.time-machine-editor--embedded {
|
||||
@extend .time-machine-editor;
|
||||
|
||||
border-radius: 4px;
|
||||
border: 2px solid $g5-pepper;
|
||||
@extend .flux-editor--monaco;
|
||||
border-radius: $cf-radius;
|
||||
border: $cf-border solid $g5-pepper;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
|
|
|
@ -4,10 +4,7 @@ import {connect} from 'react-redux'
|
|||
|
||||
// Components
|
||||
import FluxEditor from 'src/shared/components/FluxMonacoEditor'
|
||||
import Threesizer from 'src/shared/components/threesizer/Threesizer'
|
||||
import FluxFunctionsToolbar from 'src/timeMachine/components/fluxFunctionsToolbar/FluxFunctionsToolbar'
|
||||
import VariableToolbar from 'src/timeMachine/components/variableToolbar/VariableToolbar'
|
||||
import ToolbarTab from 'src/timeMachine/components/ToolbarTab'
|
||||
import FluxToolbar from 'src/timeMachine/components/FluxToolbar'
|
||||
|
||||
// Actions
|
||||
import {setActiveQueryText} from 'src/timeMachine/actions'
|
||||
|
@ -20,9 +17,6 @@ import {
|
|||
generateImport,
|
||||
} from 'src/timeMachine/utils/insertFunction'
|
||||
|
||||
// Constants
|
||||
import {HANDLE_VERTICAL, HANDLE_NONE} from 'src/shared/constants'
|
||||
|
||||
// Types
|
||||
import {AppState, FluxToolbarFunction, EditorType} from 'src/types'
|
||||
|
||||
|
@ -44,17 +38,8 @@ const TimeMachineFluxEditor: FC<Props> = ({
|
|||
onSetActiveQueryText,
|
||||
activeTab,
|
||||
}) => {
|
||||
const [displayFluxFunctions, setDisplayFluxFunctions] = useState(true)
|
||||
const [editorInstance, setEditorInstance] = useState<EditorType>(null)
|
||||
|
||||
const showFluxFunctions = () => {
|
||||
setDisplayFluxFunctions(true)
|
||||
}
|
||||
|
||||
const hideFluxFunctions = () => {
|
||||
setDisplayFluxFunctions(false)
|
||||
}
|
||||
|
||||
const handleInsertVariable = (variableName: string): void => {
|
||||
const p = editorInstance.getPosition()
|
||||
editorInstance.executeEdits('', [
|
||||
|
@ -112,59 +97,23 @@ const TimeMachineFluxEditor: FC<Props> = ({
|
|||
onSetActiveQueryText(editorInstance.getValue())
|
||||
}
|
||||
|
||||
const divisions = [
|
||||
{
|
||||
size: 0.75,
|
||||
handleDisplay: HANDLE_NONE,
|
||||
render: () => {
|
||||
return (
|
||||
<FluxEditor
|
||||
script={activeQueryText}
|
||||
onChangeScript={onSetActiveQueryText}
|
||||
onSubmitScript={onSubmitQueries}
|
||||
setEditorInstance={setEditorInstance}
|
||||
/>
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
style: {overflow: 'hidden'},
|
||||
render: () => {
|
||||
return (
|
||||
<>
|
||||
<div className="toolbar-tab-container">
|
||||
{activeTab !== 'customCheckQuery' && (
|
||||
<ToolbarTab
|
||||
onSetActive={hideFluxFunctions}
|
||||
name="Variables"
|
||||
active={!displayFluxFunctions}
|
||||
/>
|
||||
)}
|
||||
<ToolbarTab
|
||||
onSetActive={showFluxFunctions}
|
||||
name="Functions"
|
||||
active={displayFluxFunctions}
|
||||
testID="functions-toolbar-tab"
|
||||
/>
|
||||
</div>
|
||||
{displayFluxFunctions ? (
|
||||
<FluxFunctionsToolbar
|
||||
onInsertFluxFunction={handleInsertFluxFunction}
|
||||
/>
|
||||
) : (
|
||||
<VariableToolbar onClickVariable={handleInsertVariable} />
|
||||
)}
|
||||
</>
|
||||
)
|
||||
},
|
||||
handlePixels: 6,
|
||||
size: 0.25,
|
||||
},
|
||||
]
|
||||
|
||||
return (
|
||||
<div className="time-machine-flux-editor">
|
||||
<Threesizer orientation={HANDLE_VERTICAL} divisions={divisions} />
|
||||
<div className="flux-editor">
|
||||
<div className="flux-editor--left-panel">
|
||||
<FluxEditor
|
||||
script={activeQueryText}
|
||||
onChangeScript={onSetActiveQueryText}
|
||||
onSubmitScript={onSubmitQueries}
|
||||
setEditorInstance={setEditorInstance}
|
||||
/>
|
||||
</div>
|
||||
<div className="flux-editor--right-panel">
|
||||
<FluxToolbar
|
||||
activeQueryBuilderTab={activeTab}
|
||||
onInsertFluxFunction={handleInsertFluxFunction}
|
||||
onInsertVariable={handleInsertVariable}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,29 +0,0 @@
|
|||
.toolbar-tab {
|
||||
background: rgba($g3-castle, 0.5);
|
||||
margin-right: $ix-border;
|
||||
border-radius: $ix-radius $ix-radius 0 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: $g10-wolf;
|
||||
font-weight: 700;
|
||||
padding: 0 ($ix-marg-c - $ix-marg-a);
|
||||
font-size: $ix-text-tiny;
|
||||
user-select: none;
|
||||
white-space: nowrap;
|
||||
transition: color 0.25s ease, background-color 0.25s ease;
|
||||
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
&.active {
|
||||
flex: 0 0 auto;
|
||||
background: $g3-castle;
|
||||
color: $g16-pearl;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: $g16-pearl;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
|
@ -1,29 +1,40 @@
|
|||
// Libraries
|
||||
import React, {PureComponent} from 'react'
|
||||
import React, {FC} from 'react'
|
||||
import classnames from 'classnames'
|
||||
|
||||
interface Props {
|
||||
onSetActive: () => void
|
||||
onClick: (id: string) => void
|
||||
id: string
|
||||
name: string
|
||||
active: boolean
|
||||
testID: string
|
||||
testID?: string
|
||||
}
|
||||
|
||||
export default class ToolbarTab extends PureComponent<Props> {
|
||||
public static defaultProps = {
|
||||
testID: 'toolbar-tab',
|
||||
const ToolbarTab: FC<Props> = ({
|
||||
onClick,
|
||||
name,
|
||||
active,
|
||||
testID = 'toolbar-tab',
|
||||
id,
|
||||
}) => {
|
||||
const toolbarTabClass = classnames('flux-toolbar--tab', {
|
||||
'flux-toolbar--tab__active': active,
|
||||
})
|
||||
|
||||
const handleClick = (): void => {
|
||||
onClick(id)
|
||||
}
|
||||
|
||||
public render() {
|
||||
const {active, onSetActive, name, testID} = this.props
|
||||
return (
|
||||
<div
|
||||
className={`toolbar-tab ${active ? 'active' : ''}`}
|
||||
onClick={onSetActive}
|
||||
title={name}
|
||||
data-testid={testID}
|
||||
>
|
||||
{name}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<div
|
||||
className={toolbarTabClass}
|
||||
onClick={handleClick}
|
||||
title={name}
|
||||
data-testid={testID}
|
||||
>
|
||||
{name}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ToolbarTab
|
||||
|
|
|
@ -1,86 +1,44 @@
|
|||
@import "src/style/modules";
|
||||
|
||||
.flux-functions-toolbar {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: $g3-castle;
|
||||
font-size: 13px;
|
||||
.flux-function-docs {
|
||||
width: 420px;
|
||||
height: 330px;
|
||||
}
|
||||
|
||||
.flux-functions-toolbar--list {
|
||||
padding-bottom: $ix-marg-a;
|
||||
}
|
||||
.flux-function-docs--heading {
|
||||
font-weight: $cf-font-weight--bold;
|
||||
margin-top: $cf-marg-b;
|
||||
margin-bottom: $cf-marg-b;
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
|
||||
.flux-functions-toolbar--category {
|
||||
dt, dd {
|
||||
height: 30px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
dt {
|
||||
background-color: $g6-smoke;
|
||||
font-weight: 600;
|
||||
color: $g18-cloud;
|
||||
}
|
||||
|
||||
dd {
|
||||
font-family: "RobotoMono", monospace;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
dd:hover, dd:active {
|
||||
background-color: $g4-onyx;
|
||||
color: $c-laser;
|
||||
article:first-child & {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.flux-functions-toolbar--helper {
|
||||
color: $g10-wolf;
|
||||
padding: 15px;
|
||||
visibility: hidden;
|
||||
.flux-function-docs--snippet {
|
||||
background-color: $g1-raven;
|
||||
border-radius: $cf-radius;
|
||||
margin: $cf-marg-a 0;
|
||||
padding: $cf-marg-b;
|
||||
font-family: $cf-code-font;
|
||||
}
|
||||
|
||||
.flux-functions-toolbar--function {
|
||||
position: relative;
|
||||
|
||||
&:hover {
|
||||
.flux-functions-toolbar--helper {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.flux-functions-toolbar--heading {
|
||||
font-weight: 600;
|
||||
margin-bottom: $ix-marg-a;
|
||||
}
|
||||
|
||||
.flux-functions-toolbar--snippet {
|
||||
background-color: $g3-castle;
|
||||
border-radius: $radius;
|
||||
margin: $ix-marg-a 0;
|
||||
padding: $ix-marg-b;
|
||||
font-family: "RobotoMono", monospace;
|
||||
}
|
||||
|
||||
.flux-functions-toolbar--arguments {
|
||||
.flux-function-docs--arguments {
|
||||
span:first-child {
|
||||
font-weight: 600;
|
||||
font-weight: $cf-font-weight--bold;
|
||||
color: $c-pool;
|
||||
margin-right: $ix-marg-a;
|
||||
margin-right: $cf-marg-a;
|
||||
}
|
||||
|
||||
span:nth-child(2) {
|
||||
color: $c-rainforest;
|
||||
font-style: italic;
|
||||
margin-right: 2px;
|
||||
margin-right: $cf-border;
|
||||
}
|
||||
|
||||
div {
|
||||
margin: $ix-marg-a 0 $ix-marg-c 0;
|
||||
margin: $cf-marg-a 0 $cf-marg-c 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,8 +5,8 @@ import {connect} from 'react-redux'
|
|||
// Components
|
||||
import TransformToolbarFunctions from 'src/timeMachine/components/fluxFunctionsToolbar/TransformToolbarFunctions'
|
||||
import FunctionCategory from 'src/timeMachine/components/fluxFunctionsToolbar/FunctionCategory'
|
||||
import SearchBar from 'src/timeMachine/components/SearchBar'
|
||||
import FancyScrollbar from 'src/shared/components/fancy_scrollbar/FancyScrollbar'
|
||||
import FluxToolbarSearch from 'src/timeMachine/components/FluxToolbarSearch'
|
||||
import {DapperScrollbars} from '@influxdata/clockface'
|
||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
|
||||
// Actions
|
||||
|
@ -46,10 +46,13 @@ class FluxFunctionsToolbar extends PureComponent<Props, State> {
|
|||
const {searchTerm} = this.state
|
||||
|
||||
return (
|
||||
<div className="flux-functions-toolbar">
|
||||
<SearchBar onSearch={this.handleSearch} resourceName="Functions" />
|
||||
<FancyScrollbar>
|
||||
<div className="flux-functions-toolbar--list">
|
||||
<>
|
||||
<FluxToolbarSearch
|
||||
onSearch={this.handleSearch}
|
||||
resourceName="Functions"
|
||||
/>
|
||||
<DapperScrollbars className="flux-toolbar--scroll-area">
|
||||
<div className="flux-toolbar--list">
|
||||
<TransformToolbarFunctions
|
||||
funcs={FLUX_FUNCTIONS}
|
||||
searchTerm={searchTerm}
|
||||
|
@ -66,8 +69,8 @@ class FluxFunctionsToolbar extends PureComponent<Props, State> {
|
|||
}
|
||||
</TransformToolbarFunctions>
|
||||
</div>
|
||||
</FancyScrollbar>
|
||||
</div>
|
||||
</DapperScrollbars>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -17,14 +17,14 @@ const FunctionCategory: SFC<Props> = props => {
|
|||
const {category, funcs, onClickFunction} = props
|
||||
|
||||
return (
|
||||
<dl className="flux-functions-toolbar--category">
|
||||
<dt>{category}</dt>
|
||||
<dl className="flux-toolbar--category">
|
||||
<dt className="flux-toolbar--heading">{category}</dt>
|
||||
{funcs.map(func => (
|
||||
<ToolbarFunction
|
||||
onClickFunction={onClickFunction}
|
||||
key={`${func.name}_${func.desc}`}
|
||||
func={func}
|
||||
testID="toolbar-function"
|
||||
testID={func.name}
|
||||
/>
|
||||
))}
|
||||
</dl>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import React, {FunctionComponent} from 'react'
|
||||
|
||||
// Components
|
||||
import FancyScrollbar from 'src/shared/components/fancy_scrollbar/FancyScrollbar'
|
||||
import {DapperScrollbars} from '@influxdata/clockface'
|
||||
import TooltipDescription from 'src/timeMachine/components/fluxFunctionsToolbar/TooltipDescription'
|
||||
import TooltipArguments from 'src/timeMachine/components/fluxFunctionsToolbar/TooltipArguments'
|
||||
import TooltipExample from 'src/timeMachine/components/fluxFunctionsToolbar/TooltipExample'
|
||||
|
@ -11,22 +11,24 @@ import TooltipLink from 'src/timeMachine/components/fluxFunctionsToolbar/Tooltip
|
|||
// Types
|
||||
import {FluxToolbarFunction} from 'src/types/shared'
|
||||
|
||||
const MAX_HEIGHT = 400
|
||||
|
||||
interface Props {
|
||||
func: FluxToolbarFunction
|
||||
}
|
||||
|
||||
const FunctionTooltipContents: FunctionComponent<Props> = ({
|
||||
func: {desc, args, example, link},
|
||||
func: {desc, args, example, link, name},
|
||||
}) => {
|
||||
return (
|
||||
<FancyScrollbar autoHeight={true} maxHeight={MAX_HEIGHT} autoHide={false}>
|
||||
<TooltipDescription description={desc} />
|
||||
<TooltipArguments argsList={args} />
|
||||
<TooltipExample example={example} />
|
||||
<TooltipLink link={link} />
|
||||
</FancyScrollbar>
|
||||
<div className="flux-function-docs" data-testid={`flux-docs--${name}`}>
|
||||
<DapperScrollbars autoHide={false}>
|
||||
<div className="flux-toolbar--popover">
|
||||
<TooltipDescription description={desc} />
|
||||
<TooltipArguments argsList={args} />
|
||||
<TooltipExample example={example} />
|
||||
<TooltipLink link={link} />
|
||||
</div>
|
||||
</DapperScrollbars>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -8,6 +8,9 @@ import {
|
|||
PopoverPosition,
|
||||
PopoverInteraction,
|
||||
Appearance,
|
||||
Button,
|
||||
ComponentSize,
|
||||
ComponentColor,
|
||||
} from '@influxdata/clockface'
|
||||
|
||||
// Types
|
||||
|
@ -20,22 +23,19 @@ interface Props {
|
|||
}
|
||||
|
||||
const defaultProps = {
|
||||
testID: 'toolbar-function',
|
||||
testID: 'flux-function',
|
||||
}
|
||||
|
||||
const ToolbarFunction: FC<Props> = ({func, onClickFunction, testID}) => {
|
||||
const functionRef = createRef<HTMLDivElement>()
|
||||
const functionRef = createRef<HTMLDListElement>()
|
||||
const handleClickFunction = () => {
|
||||
onClickFunction(func)
|
||||
}
|
||||
return (
|
||||
<div
|
||||
className="flux-functions-toolbar--function"
|
||||
ref={functionRef}
|
||||
data-testid={testID}
|
||||
>
|
||||
<>
|
||||
<Popover
|
||||
appearance={Appearance.Outline}
|
||||
enableDefaultStyles={false}
|
||||
position={PopoverPosition.ToTheLeft}
|
||||
triggerRef={functionRef}
|
||||
showEvent={PopoverInteraction.Hover}
|
||||
|
@ -45,14 +45,21 @@ const ToolbarFunction: FC<Props> = ({func, onClickFunction, testID}) => {
|
|||
contents={() => <FunctionTooltipContents func={func} />}
|
||||
/>
|
||||
<dd
|
||||
onClick={handleClickFunction}
|
||||
data-testid={`flux-function ${func.name}`}
|
||||
ref={functionRef}
|
||||
data-testid={`flux--${testID}`}
|
||||
className="flux-toolbar--list-item flux-toolbar--function"
|
||||
>
|
||||
{func.name}
|
||||
|
||||
<span className="flux-functions-toolbar--helper">Click to Add</span>
|
||||
<code>{func.name}</code>
|
||||
<Button
|
||||
testID={`flux--${testID}--inject`}
|
||||
text="Inject"
|
||||
onClick={handleClickFunction}
|
||||
size={ComponentSize.ExtraSmall}
|
||||
className="flux-toolbar--injector"
|
||||
color={ComponentColor.Primary}
|
||||
/>
|
||||
</dd>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -14,8 +14,8 @@ class TooltipArguments extends PureComponent<Props> {
|
|||
public render() {
|
||||
return (
|
||||
<article>
|
||||
<div className="flux-functions-toolbar--heading">Arguments</div>
|
||||
<div className="flux-functions-toolbar--snippet">{this.arguments}</div>
|
||||
<div className="flux-function-docs--heading">Arguments</div>
|
||||
<div className="flux-function-docs--snippet">{this.arguments}</div>
|
||||
</article>
|
||||
)
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ class TooltipArguments extends PureComponent<Props> {
|
|||
if (argsList.length > 0) {
|
||||
return argsList.map(a => {
|
||||
return (
|
||||
<div className="flux-functions-toolbar--arguments" key={a.name}>
|
||||
<div className="flux-function-docs--arguments" key={a.name}>
|
||||
<span>{a.name}:</span>
|
||||
<span>{a.type}</span>
|
||||
<div>{a.desc}</div>
|
||||
|
@ -35,7 +35,7 @@ class TooltipArguments extends PureComponent<Props> {
|
|||
})
|
||||
}
|
||||
|
||||
return <div className="flux-functions-toolbar--arguments">None</div>
|
||||
return <div className="flux-function-docs--arguments">None</div>
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ interface Props {
|
|||
|
||||
const TooltipDescription: SFC<Props> = ({description}) => (
|
||||
<article className="flux-functions-toolbar--description">
|
||||
<div className="flux-functions-toolbar--heading">Description</div>
|
||||
<div className="flux-function-docs--heading">Description</div>
|
||||
<span>{description}</span>
|
||||
</article>
|
||||
)
|
||||
|
|
|
@ -6,8 +6,8 @@ interface Props {
|
|||
|
||||
const TooltipExample: SFC<Props> = ({example}) => (
|
||||
<article>
|
||||
<div className="flux-functions-toolbar--heading">Example</div>
|
||||
<div className="flux-functions-toolbar--snippet">{example}</div>
|
||||
<div className="flux-function-docs--heading">Example</div>
|
||||
<div className="flux-function-docs--snippet">{example}</div>
|
||||
</article>
|
||||
)
|
||||
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
// Libraries
|
||||
import {SFC, ReactElement} from 'react'
|
||||
import React, {SFC, ReactElement} from 'react'
|
||||
import {groupBy} from 'lodash'
|
||||
|
||||
// Components
|
||||
import {EmptyState, ComponentSize} from '@influxdata/clockface'
|
||||
|
||||
// Types
|
||||
import {FluxToolbarFunction} from 'src/types/shared'
|
||||
|
||||
|
@ -22,6 +25,14 @@ const TransformToolbarFunctions: SFC<Props> = props => {
|
|||
|
||||
const groupedFunctions = groupBy(filteredFunctions, 'category')
|
||||
|
||||
if (filteredFunctions.length === 0) {
|
||||
return (
|
||||
<EmptyState size={ComponentSize.ExtraSmall}>
|
||||
<EmptyState.Text>No functions match your search</EmptyState.Text>
|
||||
</EmptyState>
|
||||
)
|
||||
}
|
||||
|
||||
return children(groupedFunctions) as ReactElement<any>
|
||||
}
|
||||
|
||||
|
|
|
@ -1,22 +1,26 @@
|
|||
// Libraries
|
||||
import React, {FC, useRef} from 'react'
|
||||
import {get} from 'lodash'
|
||||
|
||||
// Components
|
||||
import VariableTooltipContents from 'src/timeMachine/components/variableToolbar/VariableTooltipContents'
|
||||
import {
|
||||
Button,
|
||||
Popover,
|
||||
PopoverPosition,
|
||||
PopoverInteraction,
|
||||
Appearance,
|
||||
ComponentColor,
|
||||
ComponentSize,
|
||||
} from '@influxdata/clockface'
|
||||
|
||||
// Types
|
||||
import {Variable} from 'src/types'
|
||||
import VariableLabel from 'src/timeMachine/components/variableToolbar/VariableLabel'
|
||||
|
||||
interface Props {
|
||||
variable: Variable
|
||||
onClickVariable: (variableName: string) => void
|
||||
testID?: string
|
||||
}
|
||||
|
||||
function shouldShowTooltip(variable: Variable): boolean {
|
||||
|
@ -46,31 +50,67 @@ function shouldShowTooltip(variable: Variable): boolean {
|
|||
return true
|
||||
}
|
||||
|
||||
const VariableItem: FC<Props> = ({variable, onClickVariable}) => {
|
||||
const VariableItem: FC<Props> = ({
|
||||
variable,
|
||||
onClickVariable,
|
||||
testID = 'variable',
|
||||
}) => {
|
||||
const trigger = useRef<HTMLDivElement>(null)
|
||||
|
||||
const handleClick = (): void => {
|
||||
const variableName = get(variable, 'name', 'variableName')
|
||||
onClickVariable(variableName)
|
||||
}
|
||||
|
||||
if (!shouldShowTooltip(variable)) {
|
||||
return (
|
||||
<div className="variables-toolbar--item" ref={trigger}>
|
||||
<VariableLabel name={variable.name} onClickVariable={onClickVariable} />
|
||||
<div
|
||||
className="flux-toolbar--list-item flux-toolbar--variable"
|
||||
data-testid={`variable--${testID}`}
|
||||
>
|
||||
<code data-testid={`variable-name--${testID}`}>{variable.name}</code>
|
||||
<Button
|
||||
testID={`variable--${testID}--inject`}
|
||||
text="Inject"
|
||||
onClick={handleClick}
|
||||
size={ComponentSize.ExtraSmall}
|
||||
className="flux-toolbar--injector"
|
||||
color={ComponentColor.Success}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="variables-toolbar--item" ref={trigger}>
|
||||
<VariableLabel name={variable.name} onClickVariable={onClickVariable} />
|
||||
<>
|
||||
<div
|
||||
className="flux-toolbar--list-item flux-toolbar--variable"
|
||||
ref={trigger}
|
||||
data-testid={`variable--${testID}`}
|
||||
>
|
||||
<code data-testid={`variable-name--${testID}`}>{variable.name}</code>
|
||||
<Button
|
||||
testID={`variable--${testID}--inject`}
|
||||
text="Inject"
|
||||
onClick={handleClick}
|
||||
size={ComponentSize.ExtraSmall}
|
||||
className="flux-toolbar--injector"
|
||||
color={ComponentColor.Success}
|
||||
/>
|
||||
</div>
|
||||
<Popover
|
||||
appearance={Appearance.Outline}
|
||||
position={PopoverPosition.ToTheLeft}
|
||||
triggerRef={trigger}
|
||||
showEvent={PopoverInteraction.Hover}
|
||||
hideEvent={PopoverInteraction.Hover}
|
||||
color={ComponentColor.Success}
|
||||
distanceFromTrigger={8}
|
||||
testID="toolbar-popover"
|
||||
enableDefaultStyles={false}
|
||||
contents={() => <VariableTooltipContents variableID={variable.id} />}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,44 +0,0 @@
|
|||
.variable-toolbar {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: $g3-castle;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.variables-toolbar--list {
|
||||
padding-bottom: $ix-marg-a;
|
||||
}
|
||||
|
||||
.variables-toolbar--item {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.variables-toolbar--label,
|
||||
.variables-toolbar--separator {
|
||||
height: 30px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-left: $ix-marg-b;
|
||||
}
|
||||
|
||||
.variables-toolbar--label {
|
||||
font-family: 'RobotoMono', monospace;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover,
|
||||
&:active {
|
||||
background-color: $g4-onyx;
|
||||
color: $c-laser;
|
||||
}
|
||||
}
|
||||
|
||||
.variables-toolbar--separator {
|
||||
background-color: $g6-smoke;
|
||||
font-weight: 600;
|
||||
color: $g18-cloud;
|
||||
}
|
||||
|
||||
.variable-tooltip--contents .form--element {
|
||||
margin-bottom: 0;
|
||||
}
|
|
@ -3,8 +3,12 @@ import React, {useState, FunctionComponent} from 'react'
|
|||
import {connect} from 'react-redux'
|
||||
|
||||
// Components
|
||||
import SearchBar from 'src/timeMachine/components/SearchBar'
|
||||
import FancyScrollbar from 'src/shared/components/fancy_scrollbar/FancyScrollbar'
|
||||
import FluxToolbarSearch from 'src/timeMachine/components/FluxToolbarSearch'
|
||||
import {
|
||||
DapperScrollbars,
|
||||
EmptyState,
|
||||
ComponentSize,
|
||||
} from '@influxdata/clockface'
|
||||
import VariableItem from 'src/timeMachine/components/variableToolbar/VariableItem'
|
||||
|
||||
// Utils
|
||||
|
@ -26,24 +30,32 @@ const VariableToolbar: FunctionComponent<OwnProps & StateProps> = ({
|
|||
onClickVariable,
|
||||
}) => {
|
||||
const [searchTerm, setSearchTerm] = useState('')
|
||||
const filteredVariables = variables.filter(v => v.name.includes(searchTerm))
|
||||
|
||||
let content: JSX.Element | JSX.Element[] = (
|
||||
<EmptyState size={ComponentSize.ExtraSmall}>
|
||||
<EmptyState.Text>No variables match your search</EmptyState.Text>
|
||||
</EmptyState>
|
||||
)
|
||||
|
||||
if (Boolean(filteredVariables.length)) {
|
||||
content = filteredVariables.map(v => (
|
||||
<VariableItem
|
||||
variable={v}
|
||||
key={v.id}
|
||||
onClickVariable={onClickVariable}
|
||||
testID={v.name}
|
||||
/>
|
||||
))
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="variable-toolbar">
|
||||
<SearchBar onSearch={setSearchTerm} resourceName="Variables" />
|
||||
<FancyScrollbar style={{marginBottom: '40px'}}>
|
||||
<div className="variables-toolbar--list">
|
||||
{variables
|
||||
.filter(v => v.name.includes(searchTerm))
|
||||
.map(v => (
|
||||
<VariableItem
|
||||
variable={v}
|
||||
key={v.id}
|
||||
onClickVariable={onClickVariable}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</FancyScrollbar>
|
||||
</div>
|
||||
<>
|
||||
<FluxToolbarSearch onSearch={setSearchTerm} resourceName="Variables" />
|
||||
<DapperScrollbars className="flux-toolbar--scroll-area">
|
||||
<div className="flux-toolbar--list">{content}</div>
|
||||
</DapperScrollbars>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -35,7 +35,10 @@ const VariableTooltipContents: FunctionComponent<Props> = ({
|
|||
execute()
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<div
|
||||
className="flux-toolbar--popover"
|
||||
data-testid="flux-toolbar--variable-popover"
|
||||
>
|
||||
<Form.Element label="Value">
|
||||
<VariableDropdown
|
||||
variableID={variableID}
|
||||
|
|
Loading…
Reference in New Issue