Create drag & drop list of columns
Borrowed a lot from the customizable fields componentpull/3815/head
parent
9b1914e8a0
commit
12cbcd0d68
|
@ -1,21 +1,39 @@
|
|||
import React, {Component} from 'react'
|
||||
import uuid from 'uuid'
|
||||
import {DragDropContext} from 'react-dnd'
|
||||
import HTML5Backend from 'react-dnd-html5-backend'
|
||||
import DraggableColumn from 'src/logs/components/DraggableColumn'
|
||||
import {LogsTableColumn} from 'src/types/logs'
|
||||
|
||||
interface Props {
|
||||
columns: string[]
|
||||
columns: LogsTableColumn[]
|
||||
onMoveColumn: (dragIndex: number, hoverIndex: number) => void
|
||||
onUpdateColumn: (column: LogsTableColumn) => void
|
||||
}
|
||||
|
||||
class ColumnsOptions extends Component<Props> {
|
||||
public render() {
|
||||
const {columns} = this.props
|
||||
const {columns, onMoveColumn, onUpdateColumn} = this.props
|
||||
|
||||
return (
|
||||
<>
|
||||
<label className="form-label">Order Table Columns</label>
|
||||
{columns.map(c => <p key={uuid.v4()}>{c}</p>)}
|
||||
<label className="form-label">Table Columns</label>
|
||||
<div className="logs-options--columns">
|
||||
{columns.map((c, i) => (
|
||||
<DraggableColumn
|
||||
key={c.internalName}
|
||||
index={i}
|
||||
id={c.internalName}
|
||||
internalName={c.internalName}
|
||||
displayName={c.displayName}
|
||||
visible={c.visible}
|
||||
onUpdateColumn={onUpdateColumn}
|
||||
onMoveColumn={onMoveColumn}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default ColumnsOptions
|
||||
export default DragDropContext(HTML5Backend)(ColumnsOptions)
|
||||
|
|
|
@ -0,0 +1,214 @@
|
|||
import React, {Component, ChangeEvent} from 'react'
|
||||
import {findDOMNode} from 'react-dom'
|
||||
import {
|
||||
DragSourceSpec,
|
||||
DropTargetConnector,
|
||||
DragSourceMonitor,
|
||||
DragSource,
|
||||
DropTarget,
|
||||
DragSourceConnector,
|
||||
ConnectDragSource,
|
||||
ConnectDropTarget,
|
||||
ConnectDragPreview,
|
||||
} from 'react-dnd'
|
||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
import {LogsTableColumn} from 'src/types/logs'
|
||||
|
||||
const columnType = 'column'
|
||||
|
||||
interface Props {
|
||||
internalName: string
|
||||
displayName: string
|
||||
visible: boolean
|
||||
index: number
|
||||
id: string
|
||||
key: string
|
||||
onUpdateColumn: (column: LogsTableColumn) => void
|
||||
isDragging?: boolean
|
||||
connectDragSource?: ConnectDragSource
|
||||
connectDropTarget?: ConnectDropTarget
|
||||
connectDragPreview?: ConnectDragPreview
|
||||
onMoveColumn: (dragIndex: number, hoverIndex: number) => void
|
||||
}
|
||||
|
||||
const columnSource: DragSourceSpec<Props> = {
|
||||
beginDrag(props) {
|
||||
return {
|
||||
id: props.id,
|
||||
index: props.index,
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
const columnTarget = {
|
||||
hover(props, monitor, component) {
|
||||
const dragIndex = monitor.getItem().index
|
||||
const hoverIndex = props.index
|
||||
|
||||
// Don't replace items with themselves
|
||||
if (dragIndex === hoverIndex) {
|
||||
return
|
||||
}
|
||||
|
||||
// Determine rectangle on screen
|
||||
const hoverBoundingRect = findDOMNode(component).getBoundingClientRect()
|
||||
|
||||
// Get vertical middle
|
||||
const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2
|
||||
|
||||
// Determine mouse position
|
||||
const clientOffset = monitor.getClientOffset()
|
||||
|
||||
// Get pixels to the top
|
||||
const hoverClientY = clientOffset.y - hoverBoundingRect.top
|
||||
|
||||
// Only perform the move when the mouse has crossed half of the items height
|
||||
// When dragging downwards, only move when the cursor is below 50%
|
||||
// When dragging upwards, only move when the cursor is above 50%
|
||||
|
||||
// Dragging downwards
|
||||
if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) {
|
||||
return
|
||||
}
|
||||
|
||||
// Dragging upwards
|
||||
if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) {
|
||||
return
|
||||
}
|
||||
// Time to actually perform the action
|
||||
props.onMoveColumn(dragIndex, hoverIndex)
|
||||
|
||||
// Note: we're mutating the monitor item here!
|
||||
// Generally it's better to avoid mutations,
|
||||
// but it's good here for the sake of performance
|
||||
// to avoid expensive index searches.
|
||||
monitor.getItem().index = hoverIndex
|
||||
},
|
||||
}
|
||||
|
||||
function MyDropTarget(dropv1, dropv2, dropfunc1) {
|
||||
return target => DropTarget(dropv1, dropv2, dropfunc1)(target) as any
|
||||
}
|
||||
|
||||
function MyDragSource(dragv1, dragv2, dragfunc1) {
|
||||
return target => DragSource(dragv1, dragv2, dragfunc1)(target) as any
|
||||
}
|
||||
|
||||
@ErrorHandling
|
||||
@MyDropTarget(columnType, columnTarget, (connect: DropTargetConnector) => ({
|
||||
connectDropTarget: connect.dropTarget(),
|
||||
}))
|
||||
@MyDragSource(
|
||||
columnType,
|
||||
columnSource,
|
||||
(connect: DragSourceConnector, monitor: DragSourceMonitor) => ({
|
||||
connectDragSource: connect.dragSource(),
|
||||
connectDragPreview: connect.dragPreview(),
|
||||
isDragging: monitor.isDragging(),
|
||||
})
|
||||
)
|
||||
export default class DraggableColumn extends Component<Props> {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
this.handleColumnRename = this.handleColumnRename.bind(this)
|
||||
this.handleToggleVisible = this.handleToggleVisible.bind(this)
|
||||
}
|
||||
public render(): JSX.Element | null {
|
||||
const {
|
||||
internalName,
|
||||
displayName,
|
||||
connectDragPreview,
|
||||
connectDropTarget,
|
||||
visible,
|
||||
} = this.props
|
||||
|
||||
return connectDragPreview(
|
||||
connectDropTarget(
|
||||
<div className={this.columnClassName}>
|
||||
<div className={this.labelClassName}>
|
||||
{this.dragHandle}
|
||||
{this.visibilityToggle}
|
||||
<div className="customizable-field--name">{internalName}</div>
|
||||
</div>
|
||||
<input
|
||||
className="form-control input-sm customizable-field--input"
|
||||
type="text"
|
||||
spellCheck={false}
|
||||
id="internalName"
|
||||
value={displayName}
|
||||
onChange={this.handleColumnRename}
|
||||
placeholder={`Rename ${internalName}`}
|
||||
disabled={!visible}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private get dragHandle(): JSX.Element {
|
||||
const {connectDragSource} = this.props
|
||||
|
||||
return connectDragSource(
|
||||
<div className="customizable-field--drag">
|
||||
<span className="hamburger" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
private get visibilityToggle(): JSX.Element {
|
||||
const {visible, internalName} = this.props
|
||||
|
||||
if (visible) {
|
||||
return (
|
||||
<div
|
||||
className="customizable-field--visibility"
|
||||
onClick={this.handleToggleVisible}
|
||||
title={`Click to HIDE ${internalName}`}
|
||||
>
|
||||
<span className="icon eye-open" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className="customizable-field--visibility"
|
||||
onClick={this.handleToggleVisible}
|
||||
title={`Click to SHOW ${internalName}`}
|
||||
>
|
||||
<span className="icon eye-closed" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
private get labelClassName(): string {
|
||||
const {visible} = this.props
|
||||
|
||||
if (visible) {
|
||||
return 'customizable-field--label'
|
||||
}
|
||||
|
||||
return 'customizable-field--label__hidden'
|
||||
}
|
||||
|
||||
private get columnClassName(): string {
|
||||
const {isDragging} = this.props
|
||||
|
||||
if (isDragging) {
|
||||
return 'customizable-field dragging'
|
||||
}
|
||||
|
||||
return 'customizable-field'
|
||||
}
|
||||
|
||||
private handleColumnRename = (e: ChangeEvent<HTMLInputElement>): void => {
|
||||
const {onUpdateColumn, internalName, visible} = this.props
|
||||
onUpdateColumn({internalName, displayName: e.target.value, visible})
|
||||
}
|
||||
|
||||
private handleToggleVisible = (): void => {
|
||||
const {onUpdateColumn, internalName, displayName, visible} = this.props
|
||||
onUpdateColumn({internalName, displayName, visible: !visible})
|
||||
}
|
||||
}
|
|
@ -6,24 +6,32 @@ import Heading from 'src/shared/components/overlay/OverlayHeading'
|
|||
import Body from 'src/shared/components/overlay/OverlayBody'
|
||||
import SeverityOptions from 'src/logs/components/SeverityOptions'
|
||||
import ColumnsOptions from 'src/logs/components/ColumnsOptions'
|
||||
import {SeverityLevel, SeverityColor, SeverityFormat} from 'src/types/logs'
|
||||
import {
|
||||
SeverityLevel,
|
||||
SeverityColor,
|
||||
SeverityFormat,
|
||||
LogsTableColumn,
|
||||
} from 'src/types/logs'
|
||||
import {DEFAULT_SEVERITY_LEVELS} from 'src/logs/constants'
|
||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
|
||||
interface Props {
|
||||
severityLevels: SeverityLevel[]
|
||||
onUpdateSeverityLevels: (severityLevels: SeverityLevel[]) => void
|
||||
onDismissOverlay: () => void
|
||||
columns: string[]
|
||||
columns: LogsTableColumn[]
|
||||
onUpdateColumns: (columns: LogsTableColumn[]) => void
|
||||
severityFormat: SeverityFormat
|
||||
onUpdateSeverityFormat: (format: SeverityFormat) => void
|
||||
}
|
||||
|
||||
interface State {
|
||||
workingSeverityLevels: SeverityLevel[]
|
||||
workingColumns: string[]
|
||||
workingColumns: LogsTableColumn[]
|
||||
workingFormat: SeverityFormat
|
||||
}
|
||||
|
||||
@ErrorHandling
|
||||
class OptionsOverlay extends Component<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props)
|
||||
|
@ -39,13 +47,13 @@ class OptionsOverlay extends Component<Props, State> {
|
|||
const {workingSeverityLevels, workingColumns, workingFormat} = this.state
|
||||
|
||||
return (
|
||||
<Container maxWidth={700}>
|
||||
<Container maxWidth={800}>
|
||||
<Heading title="Configure Log Viewer">
|
||||
{this.overlayActionButtons}
|
||||
</Heading>
|
||||
<Body>
|
||||
<div className="row">
|
||||
<div className="col-sm-6">
|
||||
<div className="col-sm-5">
|
||||
<SeverityOptions
|
||||
severityLevels={workingSeverityLevels}
|
||||
onReset={this.handleResetSeverityLevels}
|
||||
|
@ -54,8 +62,12 @@ class OptionsOverlay extends Component<Props, State> {
|
|||
onChangeSeverityFormat={this.handleChangeSeverityFormat}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-sm-6">
|
||||
<ColumnsOptions columns={workingColumns} />
|
||||
<div className="col-sm-7">
|
||||
<ColumnsOptions
|
||||
columns={workingColumns}
|
||||
onMoveColumn={this.handleMoveColumn}
|
||||
onUpdateColumn={this.handleUpdateColumn}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Body>
|
||||
|
@ -102,11 +114,13 @@ class OptionsOverlay extends Component<Props, State> {
|
|||
onUpdateSeverityLevels,
|
||||
onDismissOverlay,
|
||||
onUpdateSeverityFormat,
|
||||
onUpdateColumns,
|
||||
} = this.props
|
||||
const {workingSeverityLevels, workingFormat} = this.state
|
||||
const {workingSeverityLevels, workingFormat, workingColumns} = this.state
|
||||
|
||||
onUpdateSeverityFormat(workingFormat)
|
||||
onUpdateSeverityLevels(workingSeverityLevels)
|
||||
onUpdateColumns(workingColumns)
|
||||
onDismissOverlay()
|
||||
}
|
||||
|
||||
|
@ -133,6 +147,37 @@ class OptionsOverlay extends Component<Props, State> {
|
|||
private handleChangeSeverityFormat = (format: SeverityFormat) => () => {
|
||||
this.setState({workingFormat: format})
|
||||
}
|
||||
|
||||
private handleMoveColumn = (dragIndex, hoverIndex) => {
|
||||
const {workingColumns} = this.state
|
||||
|
||||
const draggedField = workingColumns[dragIndex]
|
||||
|
||||
const columnsRemoved = _.concat(
|
||||
_.slice(workingColumns, 0, dragIndex),
|
||||
_.slice(workingColumns, dragIndex + 1)
|
||||
)
|
||||
|
||||
const columnsAdded = _.concat(
|
||||
_.slice(columnsRemoved, 0, hoverIndex),
|
||||
[draggedField],
|
||||
_.slice(columnsRemoved, hoverIndex)
|
||||
)
|
||||
|
||||
this.setState({workingColumns: columnsAdded})
|
||||
}
|
||||
|
||||
private handleUpdateColumn = (column: LogsTableColumn) => {
|
||||
const workingColumns = this.state.workingColumns.map(wc => {
|
||||
if (wc.internalName === column.internalName) {
|
||||
return column
|
||||
}
|
||||
|
||||
return wc
|
||||
})
|
||||
|
||||
this.setState({workingColumns})
|
||||
}
|
||||
}
|
||||
|
||||
export default OptionsOverlay
|
||||
|
|
|
@ -20,12 +20,12 @@ const SeverityConfig: SFC<Props> = ({
|
|||
onChangeSeverityFormat,
|
||||
}) => (
|
||||
<>
|
||||
<label className="form-label">Customize Severity Colors</label>
|
||||
<label className="form-label">Severity Colors</label>
|
||||
<div className="logs-options--color-list">
|
||||
{severityLevels.map(config => (
|
||||
<div key={uuid.v4()} className="logs-options--color-row">
|
||||
<div className="logs-options--color-column">
|
||||
<div className="logs-options--label">{config.severity}</div>
|
||||
<div className="logs-options--color-label">{config.severity}</div>
|
||||
</div>
|
||||
<div className="logs-options--color-column">
|
||||
<ColorDropdown
|
||||
|
|
|
@ -34,12 +34,14 @@ import {colorForSeverity} from 'src/logs/utils/colors'
|
|||
import {OverlayContext} from 'src/shared/components/OverlayTechnology'
|
||||
|
||||
import {Source, Namespace, TimeRange} from 'src/types'
|
||||
<<<<<<< HEAD
|
||||
import {Filter, SeverityLevel} from 'src/types/logs'
|
||||
|
||||
import {HistogramData, TimePeriod} from 'src/types/histogram'
|
||||
=======
|
||||
import {Filter, SeverityLevel, SeverityFormat} from 'src/types/logs'
|
||||
>>>>>>> Add UI for toggling severity format
|
||||
import {
|
||||
Filter,
|
||||
SeverityLevel,
|
||||
SeverityFormat,
|
||||
LogsTableColumn,
|
||||
} from 'src/types/logs'
|
||||
|
||||
// Mock
|
||||
import {DEFAULT_SEVERITY_LEVELS} from 'src/logs/constants'
|
||||
|
@ -300,10 +302,7 @@ class LogsPage extends PureComponent<Props, State> {
|
|||
}
|
||||
|
||||
private handleShowOptionsOverlay = (): void => {
|
||||
const {
|
||||
showOverlay,
|
||||
tableData: {columns},
|
||||
} = this.props
|
||||
const {showOverlay} = this.props
|
||||
const options = {
|
||||
dismissOnClickOutside: false,
|
||||
dismissOnEscape: false,
|
||||
|
@ -316,7 +315,8 @@ class LogsPage extends PureComponent<Props, State> {
|
|||
severityLevels={DEFAULT_SEVERITY_LEVELS} // Todo: replace with real
|
||||
onUpdateSeverityLevels={this.handleUpdateSeverityLevels}
|
||||
onDismissOverlay={onDismissOverlay}
|
||||
columns={columns}
|
||||
columns={this.fakeColumns}
|
||||
onUpdateColumns={this.handleUpdateColumns}
|
||||
onUpdateSeverityFormat={this.handleUpdateSeverityFormat}
|
||||
severityFormat="dotText" // Todo: repleace with real value
|
||||
/>
|
||||
|
@ -328,12 +328,25 @@ class LogsPage extends PureComponent<Props, State> {
|
|||
|
||||
private handleUpdateSeverityLevels = (levels: SeverityLevel[]) => {
|
||||
console.log(levels)
|
||||
// Save these new configs here
|
||||
// Todo: Handle saving of these new severity colors here
|
||||
}
|
||||
|
||||
private handleUpdateSeverityFormat = (format: SeverityFormat) => {
|
||||
console.log(format)
|
||||
// Save these new configs here
|
||||
// Todo: Handle saving of the new format here
|
||||
}
|
||||
|
||||
private get fakeColumns(): LogsTableColumn[] {
|
||||
const {
|
||||
tableData: {columns},
|
||||
} = this.props
|
||||
|
||||
return columns.map(c => ({internalName: c, displayName: '', visible: true}))
|
||||
}
|
||||
|
||||
private handleUpdateColumns = (columns: LogsTableColumn[]) => {
|
||||
console.log(columns)
|
||||
// Todo: Handle saving of column names, ordering, and visibility
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
width: 50%;
|
||||
}
|
||||
|
||||
.logs-options--label {
|
||||
.logs-options--color-label {
|
||||
height: 30px;
|
||||
border-radius: $radius;
|
||||
background-color: $g3-castle;
|
||||
|
@ -35,3 +35,9 @@
|
|||
margin-right: 4px;
|
||||
@include no-user-select();
|
||||
}
|
||||
|
||||
// Not very clean way of slightly darkening the disabled state
|
||||
// of draggable columns in the overlay
|
||||
.logs-options--columns .customizable-field--label__hidden {
|
||||
background-color: $g3-castle;
|
||||
}
|
||||
|
|
|
@ -38,3 +38,9 @@ export interface SeverityColor {
|
|||
}
|
||||
|
||||
export type SeverityFormat = 'dot' | 'dotText' | 'text'
|
||||
|
||||
export interface LogsTableColumn {
|
||||
internalName: string
|
||||
displayName: string
|
||||
visible: boolean
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue