commit
588531b2fa
|
@ -16,6 +16,7 @@
|
|||
1. [#2698](https://github.com/influxdata/chronograf/pull/2698): Improve clarity of terminology surrounding InfluxDB & Kapacitor connections
|
||||
1. [#2746](https://github.com/influxdata/chronograf/pull/2746): Separate saving TICKscript from exiting editor page
|
||||
1. [#2774](https://github.com/influxdata/chronograf/pull/2774): Enable Save (⌘ + Enter) and Cancel (Escape) hotkeys in Cell Editor Overlay
|
||||
1. [#2788](https://github.com/influxdata/chronograf/pull/2788): Enable customization of Single Stat graph's "Base Color"
|
||||
|
||||
### Bug Fixes
|
||||
1. [#2684](https://github.com/influxdata/chronograf/pull/2684): Fix TICKscript Sensu alerts when no group by tags selected
|
||||
|
@ -23,6 +24,7 @@
|
|||
1. [#2756](https://github.com/influxdata/chronograf/pull/2756): Display only 200 most recent TICKscript log messages and prevent overlapping
|
||||
1. [#2757](https://github.com/influxdata/chronograf/pull/2757): Added "TO" field to kapacitor SMTP config, and improved error messages for config saving and testing
|
||||
1. [#2761](https://github.com/influxdata/chronograf/pull/2761): Remove cli options from sysvinit service file
|
||||
1. [#2788](https://github.com/influxdata/chronograf/pull/2788): Fix disappearance of text in Single Stat graphs during editing
|
||||
1. [#2780](https://github.com/influxdata/chronograf/pull/2780): Fix routing on alert save
|
||||
|
||||
## v1.4.0.1 [2017-1-9]
|
||||
|
|
|
@ -28,9 +28,10 @@ import {
|
|||
DEFAULT_VALUE_MIN,
|
||||
DEFAULT_VALUE_MAX,
|
||||
GAUGE_COLORS,
|
||||
SINGLE_STAT_TEXT,
|
||||
SINGLE_STAT_BG,
|
||||
validateColors,
|
||||
validateGaugeColors,
|
||||
validateSingleStatColors,
|
||||
getSingleStatType,
|
||||
stringifyColorValues,
|
||||
} from 'src/dashboards/constants/gaugeColors'
|
||||
|
||||
class CellEditorOverlay extends Component {
|
||||
|
@ -49,7 +50,8 @@ class CellEditorOverlay extends Component {
|
|||
source,
|
||||
}))
|
||||
)
|
||||
const colorsTypeContainsText = _.some(colors, {type: SINGLE_STAT_TEXT})
|
||||
|
||||
const singleStatType = getSingleStatType(colors)
|
||||
|
||||
this.state = {
|
||||
cellWorkingName: name,
|
||||
|
@ -58,8 +60,9 @@ class CellEditorOverlay extends Component {
|
|||
activeQueryIndex: 0,
|
||||
isDisplayOptionsTabActive: false,
|
||||
axes,
|
||||
colorSingleStatText: colorsTypeContainsText,
|
||||
colors: validateColors(colors, type, colorsTypeContainsText),
|
||||
singleStatType,
|
||||
gaugeColors: validateGaugeColors(colors),
|
||||
singleStatColors: validateSingleStatColors(colors, singleStatType),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -80,27 +83,21 @@ class CellEditorOverlay extends Component {
|
|||
this.overlayRef.focus()
|
||||
}
|
||||
|
||||
handleAddThreshold = () => {
|
||||
const {colors, cellWorkingType} = this.state
|
||||
const sortedColors = _.sortBy(colors, color => Number(color.value))
|
||||
handleAddGaugeThreshold = () => {
|
||||
const {gaugeColors} = this.state
|
||||
const sortedColors = _.sortBy(gaugeColors, color => color.value)
|
||||
|
||||
if (sortedColors.length <= MAX_THRESHOLDS) {
|
||||
const randomColor = _.random(0, GAUGE_COLORS.length - 1)
|
||||
|
||||
const maxValue =
|
||||
cellWorkingType === 'gauge'
|
||||
? Number(sortedColors[sortedColors.length - 1].value)
|
||||
: DEFAULT_VALUE_MAX
|
||||
const minValue =
|
||||
cellWorkingType === 'gauge'
|
||||
? Number(sortedColors[0].value)
|
||||
: DEFAULT_VALUE_MIN
|
||||
const maxValue = sortedColors[sortedColors.length - 1].value
|
||||
const minValue = sortedColors[0].value
|
||||
|
||||
const colorsValues = _.mapValues(colors, 'value')
|
||||
const colorsValues = _.mapValues(gaugeColors, 'value')
|
||||
let randomValue
|
||||
|
||||
do {
|
||||
randomValue = `${_.round(_.random(minValue, maxValue, true), 2)}`
|
||||
randomValue = _.round(_.random(minValue, maxValue, true), 2)
|
||||
} while (_.includes(colorsValues, randomValue))
|
||||
|
||||
const newThreshold = {
|
||||
|
@ -111,68 +108,134 @@ class CellEditorOverlay extends Component {
|
|||
name: GAUGE_COLORS[randomColor].name,
|
||||
}
|
||||
|
||||
this.setState({colors: [...colors, newThreshold]})
|
||||
this.setState({gaugeColors: [...gaugeColors, newThreshold]})
|
||||
}
|
||||
}
|
||||
|
||||
handleAddSingleStatThreshold = () => {
|
||||
const {singleStatColors, singleStatType} = this.state
|
||||
|
||||
const randomColor = _.random(0, GAUGE_COLORS.length - 1)
|
||||
|
||||
const maxValue = DEFAULT_VALUE_MIN
|
||||
const minValue = DEFAULT_VALUE_MAX
|
||||
|
||||
let randomValue = _.round(_.random(minValue, maxValue, true), 2)
|
||||
|
||||
if (singleStatColors.length > 0) {
|
||||
const colorsValues = _.mapValues(singleStatColors, 'value')
|
||||
do {
|
||||
randomValue = _.round(_.random(minValue, maxValue, true), 2)
|
||||
} while (_.includes(colorsValues, randomValue))
|
||||
}
|
||||
|
||||
const newThreshold = {
|
||||
type: singleStatType,
|
||||
id: uuid.v4(),
|
||||
value: randomValue,
|
||||
hex: GAUGE_COLORS[randomColor].hex,
|
||||
name: GAUGE_COLORS[randomColor].name,
|
||||
}
|
||||
|
||||
this.setState({singleStatColors: [...singleStatColors, newThreshold]})
|
||||
}
|
||||
|
||||
handleDeleteThreshold = threshold => () => {
|
||||
const {colors} = this.state
|
||||
const {cellWorkingType} = this.state
|
||||
|
||||
const newColors = colors.filter(color => color.id !== threshold.id)
|
||||
if (cellWorkingType === 'gauge') {
|
||||
const gaugeColors = this.state.gaugeColors.filter(
|
||||
color => color.id !== threshold.id
|
||||
)
|
||||
|
||||
this.setState({colors: newColors})
|
||||
this.setState({gaugeColors})
|
||||
}
|
||||
|
||||
if (cellWorkingType === 'single-stat') {
|
||||
const singleStatColors = this.state.singleStatColors.filter(
|
||||
color => color.id !== threshold.id
|
||||
)
|
||||
|
||||
this.setState({singleStatColors})
|
||||
}
|
||||
}
|
||||
|
||||
handleChooseColor = threshold => chosenColor => {
|
||||
const {colors} = this.state
|
||||
const {cellWorkingType} = this.state
|
||||
|
||||
const newColors = colors.map(
|
||||
color =>
|
||||
color.id === threshold.id
|
||||
? {...color, hex: chosenColor.hex, name: chosenColor.name}
|
||||
: color
|
||||
)
|
||||
if (cellWorkingType === 'gauge') {
|
||||
const gaugeColors = this.state.gaugeColors.map(
|
||||
color =>
|
||||
color.id === threshold.id
|
||||
? {...color, hex: chosenColor.hex, name: chosenColor.name}
|
||||
: color
|
||||
)
|
||||
|
||||
this.setState({colors: newColors})
|
||||
this.setState({gaugeColors})
|
||||
}
|
||||
|
||||
if (cellWorkingType === 'single-stat') {
|
||||
const singleStatColors = this.state.singleStatColors.map(
|
||||
color =>
|
||||
color.id === threshold.id
|
||||
? {...color, hex: chosenColor.hex, name: chosenColor.name}
|
||||
: color
|
||||
)
|
||||
|
||||
this.setState({singleStatColors})
|
||||
}
|
||||
}
|
||||
|
||||
handleUpdateColorValue = (threshold, newValue) => {
|
||||
const {colors} = this.state
|
||||
const newColors = colors.map(
|
||||
color => (color.id === threshold.id ? {...color, value: newValue} : color)
|
||||
)
|
||||
this.setState({colors: newColors})
|
||||
handleUpdateColorValue = (threshold, value) => {
|
||||
const {cellWorkingType} = this.state
|
||||
|
||||
if (cellWorkingType === 'gauge') {
|
||||
const gaugeColors = this.state.gaugeColors.map(
|
||||
color => (color.id === threshold.id ? {...color, value} : color)
|
||||
)
|
||||
|
||||
this.setState({gaugeColors})
|
||||
}
|
||||
|
||||
if (cellWorkingType === 'single-stat') {
|
||||
const singleStatColors = this.state.singleStatColors.map(
|
||||
color => (color.id === threshold.id ? {...color, value} : color)
|
||||
)
|
||||
|
||||
this.setState({singleStatColors})
|
||||
}
|
||||
}
|
||||
|
||||
handleValidateColorValue = (threshold, e) => {
|
||||
const {colors, cellWorkingType} = this.state
|
||||
const sortedColors = _.sortBy(colors, color => Number(color.value))
|
||||
const thresholdValue = Number(threshold.value)
|
||||
const targetValueNumber = Number(e.target.value)
|
||||
handleValidateColorValue = (threshold, targetValue) => {
|
||||
const {gaugeColors, singleStatColors, cellWorkingType} = this.state
|
||||
const thresholdValue = threshold.value
|
||||
let allowedToUpdate = false
|
||||
|
||||
if (cellWorkingType === 'single-stat') {
|
||||
// If type is single-stat then value only has to be unique
|
||||
return !sortedColors.some(color => color.value === e.target.value)
|
||||
const sortedColors = _.sortBy(singleStatColors, color => color.value)
|
||||
return !sortedColors.some(color => color.value === targetValue)
|
||||
}
|
||||
|
||||
const minValue = Number(sortedColors[0].value)
|
||||
const maxValue = Number(sortedColors[sortedColors.length - 1].value)
|
||||
const sortedColors = _.sortBy(gaugeColors, color => color.value)
|
||||
|
||||
const minValue = sortedColors[0].value
|
||||
const maxValue = sortedColors[sortedColors.length - 1].value
|
||||
|
||||
// If lowest value, make sure it is less than the next threshold
|
||||
if (thresholdValue === minValue) {
|
||||
const nextValue = Number(sortedColors[1].value)
|
||||
allowedToUpdate = targetValueNumber < nextValue
|
||||
const nextValue = sortedColors[1].value
|
||||
allowedToUpdate = targetValue < nextValue
|
||||
}
|
||||
// If highest value, make sure it is greater than the previous threshold
|
||||
if (thresholdValue === maxValue) {
|
||||
const previousValue = Number(sortedColors[sortedColors.length - 2].value)
|
||||
allowedToUpdate = previousValue < targetValueNumber
|
||||
const previousValue = sortedColors[sortedColors.length - 2].value
|
||||
allowedToUpdate = previousValue < targetValue
|
||||
}
|
||||
// If not min or max, make sure new value is greater than min, less than max, and unique
|
||||
if (thresholdValue !== minValue && thresholdValue !== maxValue) {
|
||||
const greaterThanMin = targetValueNumber > minValue
|
||||
const lessThanMax = targetValueNumber < maxValue
|
||||
const greaterThanMin = targetValue > minValue
|
||||
const lessThanMax = targetValue < maxValue
|
||||
|
||||
const colorsWithoutMinOrMax = sortedColors.slice(
|
||||
1,
|
||||
|
@ -180,7 +243,7 @@ class CellEditorOverlay extends Component {
|
|||
)
|
||||
|
||||
const isUnique = !colorsWithoutMinOrMax.some(
|
||||
color => color.value === e.target.value
|
||||
color => color.value === targetValue
|
||||
)
|
||||
|
||||
allowedToUpdate = greaterThanMin && lessThanMax && isUnique
|
||||
|
@ -189,16 +252,15 @@ class CellEditorOverlay extends Component {
|
|||
return allowedToUpdate
|
||||
}
|
||||
|
||||
handleToggleSingleStatText = () => {
|
||||
const {colors, colorSingleStatText} = this.state
|
||||
const formattedColors = colors.map(color => ({
|
||||
handleToggleSingleStatType = type => () => {
|
||||
const singleStatColors = this.state.singleStatColors.map(color => ({
|
||||
...color,
|
||||
type: colorSingleStatText ? SINGLE_STAT_BG : SINGLE_STAT_TEXT,
|
||||
type,
|
||||
}))
|
||||
|
||||
this.setState({
|
||||
colorSingleStatText: !colorSingleStatText,
|
||||
colors: formattedColors,
|
||||
singleStatType: type,
|
||||
singleStatColors,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -298,7 +360,8 @@ class CellEditorOverlay extends Component {
|
|||
cellWorkingType: type,
|
||||
cellWorkingName: name,
|
||||
axes,
|
||||
colors,
|
||||
gaugeColors,
|
||||
singleStatColors,
|
||||
} = this.state
|
||||
|
||||
const {cell} = this.props
|
||||
|
@ -314,6 +377,13 @@ class CellEditorOverlay extends Component {
|
|||
}
|
||||
})
|
||||
|
||||
let colors = []
|
||||
if (type === 'gauge') {
|
||||
colors = stringifyColorValues(gaugeColors)
|
||||
} else if (type === 'single-stat' || type === 'line-plus-single-stat') {
|
||||
colors = stringifyColorValues(singleStatColors)
|
||||
}
|
||||
|
||||
this.props.onSave({
|
||||
...cell,
|
||||
name,
|
||||
|
@ -324,14 +394,8 @@ class CellEditorOverlay extends Component {
|
|||
})
|
||||
}
|
||||
|
||||
handleSelectGraphType = graphType => () => {
|
||||
const {colors, colorSingleStatText} = this.state
|
||||
const validatedColors = validateColors(
|
||||
colors,
|
||||
graphType,
|
||||
colorSingleStatText
|
||||
)
|
||||
this.setState({cellWorkingType: graphType, colors: validatedColors})
|
||||
handleSelectGraphType = cellWorkingType => () => {
|
||||
this.setState({cellWorkingType})
|
||||
}
|
||||
|
||||
handleClickDisplayOptionsTab = isDisplayOptionsTabActive => () => {
|
||||
|
@ -474,13 +538,14 @@ class CellEditorOverlay extends Component {
|
|||
|
||||
const {
|
||||
axes,
|
||||
colors,
|
||||
gaugeColors,
|
||||
singleStatColors,
|
||||
activeQueryIndex,
|
||||
cellWorkingName,
|
||||
cellWorkingType,
|
||||
isDisplayOptionsTabActive,
|
||||
queriesWorkingDraft,
|
||||
colorSingleStatText,
|
||||
singleStatType,
|
||||
} = this.state
|
||||
|
||||
const queryActions = {
|
||||
|
@ -492,6 +557,9 @@ class CellEditorOverlay extends Component {
|
|||
(!!query.measurement && !!query.database && !!query.fields.length) ||
|
||||
!!query.rawText
|
||||
|
||||
const visualizationColors =
|
||||
cellWorkingType === 'gauge' ? gaugeColors : singleStatColors
|
||||
|
||||
return (
|
||||
<div
|
||||
className={OVERLAY_TECHNOLOGY}
|
||||
|
@ -508,7 +576,7 @@ class CellEditorOverlay extends Component {
|
|||
>
|
||||
<Visualization
|
||||
axes={axes}
|
||||
colors={colors}
|
||||
colors={visualizationColors}
|
||||
type={cellWorkingType}
|
||||
name={cellWorkingName}
|
||||
timeRange={timeRange}
|
||||
|
@ -533,14 +601,16 @@ class CellEditorOverlay extends Component {
|
|||
{isDisplayOptionsTabActive
|
||||
? <DisplayOptions
|
||||
axes={axes}
|
||||
colors={colors}
|
||||
gaugeColors={gaugeColors}
|
||||
singleStatColors={singleStatColors}
|
||||
onChooseColor={this.handleChooseColor}
|
||||
onValidateColorValue={this.handleValidateColorValue}
|
||||
onUpdateColorValue={this.handleUpdateColorValue}
|
||||
onAddThreshold={this.handleAddThreshold}
|
||||
onAddGaugeThreshold={this.handleAddGaugeThreshold}
|
||||
onAddSingleStatThreshold={this.handleAddSingleStatThreshold}
|
||||
onDeleteThreshold={this.handleDeleteThreshold}
|
||||
onToggleSingleStatText={this.handleToggleSingleStatText}
|
||||
colorSingleStatText={colorSingleStatText}
|
||||
onToggleSingleStatType={this.handleToggleSingleStatType}
|
||||
singleStatType={singleStatType}
|
||||
onSetBase={this.handleSetBase}
|
||||
onSetLabel={this.handleSetLabel}
|
||||
onSetScale={this.handleSetScale}
|
||||
|
|
|
@ -35,7 +35,8 @@ class DisplayOptions extends Component {
|
|||
|
||||
renderOptions = () => {
|
||||
const {
|
||||
colors,
|
||||
gaugeColors,
|
||||
singleStatColors,
|
||||
onSetBase,
|
||||
onSetScale,
|
||||
onSetLabel,
|
||||
|
@ -43,13 +44,14 @@ class DisplayOptions extends Component {
|
|||
onSetPrefixSuffix,
|
||||
onSetYAxisBoundMin,
|
||||
onSetYAxisBoundMax,
|
||||
onAddThreshold,
|
||||
onAddGaugeThreshold,
|
||||
onAddSingleStatThreshold,
|
||||
onDeleteThreshold,
|
||||
onChooseColor,
|
||||
onValidateColorValue,
|
||||
onUpdateColorValue,
|
||||
colorSingleStatText,
|
||||
onToggleSingleStatText,
|
||||
singleStatType,
|
||||
onToggleSingleStatType,
|
||||
onSetSuffix,
|
||||
} = this.props
|
||||
const {axes, axes: {y: {suffix}}} = this.state
|
||||
|
@ -58,27 +60,27 @@ class DisplayOptions extends Component {
|
|||
case 'gauge':
|
||||
return (
|
||||
<GaugeOptions
|
||||
colors={colors}
|
||||
colors={gaugeColors}
|
||||
onChooseColor={onChooseColor}
|
||||
onValidateColorValue={onValidateColorValue}
|
||||
onUpdateColorValue={onUpdateColorValue}
|
||||
onAddThreshold={onAddThreshold}
|
||||
onAddThreshold={onAddGaugeThreshold}
|
||||
onDeleteThreshold={onDeleteThreshold}
|
||||
/>
|
||||
)
|
||||
case 'single-stat':
|
||||
return (
|
||||
<SingleStatOptions
|
||||
colors={colors}
|
||||
colors={singleStatColors}
|
||||
suffix={suffix}
|
||||
onSetSuffix={onSetSuffix}
|
||||
onChooseColor={onChooseColor}
|
||||
onValidateColorValue={onValidateColorValue}
|
||||
onUpdateColorValue={onUpdateColorValue}
|
||||
onAddThreshold={onAddThreshold}
|
||||
onAddThreshold={onAddSingleStatThreshold}
|
||||
onDeleteThreshold={onDeleteThreshold}
|
||||
colorSingleStatText={colorSingleStatText}
|
||||
onToggleSingleStatText={onToggleSingleStatText}
|
||||
singleStatType={singleStatType}
|
||||
onToggleSingleStatType={onToggleSingleStatType}
|
||||
/>
|
||||
)
|
||||
default:
|
||||
|
@ -111,10 +113,11 @@ class DisplayOptions extends Component {
|
|||
)
|
||||
}
|
||||
}
|
||||
const {arrayOf, bool, func, shape, string} = PropTypes
|
||||
const {arrayOf, func, number, shape, string} = PropTypes
|
||||
|
||||
DisplayOptions.propTypes = {
|
||||
onAddThreshold: func.isRequired,
|
||||
onAddGaugeThreshold: func.isRequired,
|
||||
onAddSingleStatThreshold: func.isRequired,
|
||||
onDeleteThreshold: func.isRequired,
|
||||
onChooseColor: func.isRequired,
|
||||
onValidateColorValue: func.isRequired,
|
||||
|
@ -129,18 +132,27 @@ DisplayOptions.propTypes = {
|
|||
onSetLabel: func.isRequired,
|
||||
onSetBase: func.isRequired,
|
||||
axes: shape({}).isRequired,
|
||||
colors: arrayOf(
|
||||
gaugeColors: arrayOf(
|
||||
shape({
|
||||
type: string.isRequired,
|
||||
hex: string.isRequired,
|
||||
id: string.isRequired,
|
||||
name: string.isRequired,
|
||||
value: string.isRequired,
|
||||
value: number.isRequired,
|
||||
}).isRequired
|
||||
),
|
||||
singleStatColors: arrayOf(
|
||||
shape({
|
||||
type: string.isRequired,
|
||||
hex: string.isRequired,
|
||||
id: string.isRequired,
|
||||
name: string.isRequired,
|
||||
value: number.isRequired,
|
||||
}).isRequired
|
||||
),
|
||||
queryConfigs: arrayOf(shape()).isRequired,
|
||||
colorSingleStatText: bool.isRequired,
|
||||
onToggleSingleStatText: func.isRequired,
|
||||
singleStatType: string.isRequired,
|
||||
onToggleSingleStatType: func.isRequired,
|
||||
}
|
||||
|
||||
export default DisplayOptions
|
||||
|
|
|
@ -19,7 +19,7 @@ const GaugeOptions = ({
|
|||
}) => {
|
||||
const disableMaxColor = colors.length > MIN_THRESHOLDS
|
||||
const disableAddThreshold = colors.length > MAX_THRESHOLDS
|
||||
const sortedColors = _.sortBy(colors, color => Number(color.value))
|
||||
const sortedColors = _.sortBy(colors, color => color.value)
|
||||
|
||||
return (
|
||||
<FancyScrollbar
|
||||
|
@ -58,7 +58,7 @@ const GaugeOptions = ({
|
|||
)
|
||||
}
|
||||
|
||||
const {arrayOf, func, shape, string} = PropTypes
|
||||
const {arrayOf, func, number, shape, string} = PropTypes
|
||||
|
||||
GaugeOptions.propTypes = {
|
||||
colors: arrayOf(
|
||||
|
@ -67,7 +67,7 @@ GaugeOptions.propTypes = {
|
|||
hex: string.isRequired,
|
||||
id: string.isRequired,
|
||||
name: string.isRequired,
|
||||
value: string.isRequired,
|
||||
value: number.isRequired,
|
||||
}).isRequired
|
||||
),
|
||||
onAddThreshold: func.isRequired,
|
||||
|
|
|
@ -3,9 +3,20 @@ import _ from 'lodash'
|
|||
|
||||
import FancyScrollbar from 'shared/components/FancyScrollbar'
|
||||
import Threshold from 'src/dashboards/components/Threshold'
|
||||
import ColorDropdown from 'shared/components/ColorDropdown'
|
||||
|
||||
import {MAX_THRESHOLDS} from 'src/dashboards/constants/gaugeColors'
|
||||
import {
|
||||
GAUGE_COLORS,
|
||||
MAX_THRESHOLDS,
|
||||
SINGLE_STAT_BASE,
|
||||
SINGLE_STAT_TEXT,
|
||||
SINGLE_STAT_BG,
|
||||
} from 'src/dashboards/constants/gaugeColors'
|
||||
|
||||
const formatColor = color => {
|
||||
const {hex, name} = color
|
||||
return {hex, name}
|
||||
}
|
||||
const SingleStatOptions = ({
|
||||
suffix,
|
||||
onSetSuffix,
|
||||
|
@ -15,12 +26,12 @@ const SingleStatOptions = ({
|
|||
onChooseColor,
|
||||
onValidateColorValue,
|
||||
onUpdateColorValue,
|
||||
colorSingleStatText,
|
||||
onToggleSingleStatText,
|
||||
singleStatType,
|
||||
onToggleSingleStatType,
|
||||
}) => {
|
||||
const disableAddThreshold = colors.length > MAX_THRESHOLDS
|
||||
|
||||
const sortedColors = _.sortBy(colors, color => Number(color.value))
|
||||
const sortedColors = _.sortBy(colors, color => color.value)
|
||||
|
||||
return (
|
||||
<FancyScrollbar
|
||||
|
@ -37,16 +48,27 @@ const SingleStatOptions = ({
|
|||
>
|
||||
<span className="icon plus" /> Add Threshold
|
||||
</button>
|
||||
{sortedColors.map(color =>
|
||||
<Threshold
|
||||
visualizationType="single-stat"
|
||||
threshold={color}
|
||||
key={color.id}
|
||||
onChooseColor={onChooseColor}
|
||||
onValidateColorValue={onValidateColorValue}
|
||||
onUpdateColorValue={onUpdateColorValue}
|
||||
onDeleteThreshold={onDeleteThreshold}
|
||||
/>
|
||||
{sortedColors.map(
|
||||
color =>
|
||||
color.id === SINGLE_STAT_BASE
|
||||
? <div className="gauge-controls--section" key={color.id}>
|
||||
<div className="gauge-controls--label">Base Color</div>
|
||||
<ColorDropdown
|
||||
colors={GAUGE_COLORS}
|
||||
selected={formatColor(color)}
|
||||
onChoose={onChooseColor(color)}
|
||||
stretchToFit={true}
|
||||
/>
|
||||
</div>
|
||||
: <Threshold
|
||||
visualizationType="single-stat"
|
||||
threshold={color}
|
||||
key={color.id}
|
||||
onChooseColor={onChooseColor}
|
||||
onValidateColorValue={onValidateColorValue}
|
||||
onUpdateColorValue={onUpdateColorValue}
|
||||
onDeleteThreshold={onDeleteThreshold}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="single-stat-controls">
|
||||
|
@ -54,14 +76,18 @@ const SingleStatOptions = ({
|
|||
<label>Coloring</label>
|
||||
<ul className="nav nav-tablist nav-tablist-sm">
|
||||
<li
|
||||
className={colorSingleStatText ? null : 'active'}
|
||||
onClick={onToggleSingleStatText}
|
||||
className={`${singleStatType === SINGLE_STAT_BG
|
||||
? 'active'
|
||||
: ''}`}
|
||||
onClick={onToggleSingleStatType(SINGLE_STAT_BG)}
|
||||
>
|
||||
Background
|
||||
</li>
|
||||
<li
|
||||
className={colorSingleStatText ? 'active' : null}
|
||||
onClick={onToggleSingleStatText}
|
||||
className={`${singleStatType === SINGLE_STAT_TEXT
|
||||
? 'active'
|
||||
: ''}`}
|
||||
onClick={onToggleSingleStatType(SINGLE_STAT_TEXT)}
|
||||
>
|
||||
Text
|
||||
</li>
|
||||
|
@ -83,7 +109,7 @@ const SingleStatOptions = ({
|
|||
)
|
||||
}
|
||||
|
||||
const {arrayOf, bool, func, shape, string} = PropTypes
|
||||
const {arrayOf, func, number, shape, string} = PropTypes
|
||||
|
||||
SingleStatOptions.defaultProps = {
|
||||
colors: [],
|
||||
|
@ -96,7 +122,7 @@ SingleStatOptions.propTypes = {
|
|||
hex: string.isRequired,
|
||||
id: string.isRequired,
|
||||
name: string.isRequired,
|
||||
value: string.isRequired,
|
||||
value: number.isRequired,
|
||||
}).isRequired
|
||||
),
|
||||
onAddThreshold: func.isRequired,
|
||||
|
@ -104,8 +130,8 @@ SingleStatOptions.propTypes = {
|
|||
onChooseColor: func.isRequired,
|
||||
onValidateColorValue: func.isRequired,
|
||||
onUpdateColorValue: func.isRequired,
|
||||
colorSingleStatText: bool.isRequired,
|
||||
onToggleSingleStatText: func.isRequired,
|
||||
singleStatType: string.isRequired,
|
||||
onToggleSingleStatType: func.isRequired,
|
||||
onSetSuffix: func.isRequired,
|
||||
suffix: string.isRequired,
|
||||
}
|
||||
|
|
|
@ -16,14 +16,15 @@ class Threshold extends Component {
|
|||
|
||||
handleChangeWorkingValue = e => {
|
||||
const {threshold, onValidateColorValue, onUpdateColorValue} = this.props
|
||||
const targetValue = Number(e.target.value)
|
||||
|
||||
const valid = onValidateColorValue(threshold, e)
|
||||
const valid = onValidateColorValue(threshold, targetValue)
|
||||
|
||||
if (valid) {
|
||||
onUpdateColorValue(threshold, e.target.value)
|
||||
onUpdateColorValue(threshold, targetValue)
|
||||
}
|
||||
|
||||
this.setState({valid, workingValue: e.target.value})
|
||||
this.setState({valid, workingValue: targetValue})
|
||||
}
|
||||
|
||||
handleBlur = () => {
|
||||
|
@ -98,7 +99,7 @@ class Threshold extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
const {bool, func, shape, string} = PropTypes
|
||||
const {bool, func, number, shape, string} = PropTypes
|
||||
|
||||
Threshold.propTypes = {
|
||||
visualizationType: string.isRequired,
|
||||
|
@ -107,7 +108,7 @@ Threshold.propTypes = {
|
|||
hex: string.isRequired,
|
||||
id: string.isRequired,
|
||||
name: string.isRequired,
|
||||
value: string.isRequired,
|
||||
value: number.isRequired,
|
||||
}).isRequired,
|
||||
disableMaxColor: bool,
|
||||
onChooseColor: func.isRequired,
|
||||
|
|
|
@ -3,6 +3,8 @@ import RefreshingGraph from 'shared/components/RefreshingGraph'
|
|||
import buildQueries from 'utils/buildQueriesForGraphs'
|
||||
import VisualizationName from 'src/dashboards/components/VisualizationName'
|
||||
|
||||
import {stringifyColorValues} from 'src/dashboards/constants/gaugeColors'
|
||||
|
||||
const DashVisualization = (
|
||||
{
|
||||
axes,
|
||||
|
@ -23,7 +25,7 @@ const DashVisualization = (
|
|||
<VisualizationName defaultName={name} onCellRename={onCellRename} />
|
||||
<div className="graph-container">
|
||||
<RefreshingGraph
|
||||
colors={colors}
|
||||
colors={stringifyColorValues(colors)}
|
||||
axes={axes}
|
||||
type={type}
|
||||
queries={buildQueries(proxy, queryConfigs, timeRange)}
|
||||
|
@ -66,8 +68,8 @@ DashVisualization.propTypes = {
|
|||
hex: string.isRequired,
|
||||
id: string.isRequired,
|
||||
name: string.isRequired,
|
||||
value: string.isRequired,
|
||||
}).isRequired
|
||||
value: number.isRequired,
|
||||
})
|
||||
),
|
||||
}
|
||||
|
||||
|
|
|
@ -4,13 +4,14 @@ export const MAX_THRESHOLDS = 5
|
|||
export const MIN_THRESHOLDS = 2
|
||||
|
||||
export const COLOR_TYPE_MIN = 'min'
|
||||
export const DEFAULT_VALUE_MIN = '0'
|
||||
export const DEFAULT_VALUE_MIN = 0
|
||||
export const COLOR_TYPE_MAX = 'max'
|
||||
export const DEFAULT_VALUE_MAX = '100'
|
||||
export const DEFAULT_VALUE_MAX = 100
|
||||
export const COLOR_TYPE_THRESHOLD = 'threshold'
|
||||
|
||||
export const SINGLE_STAT_TEXT = 'text'
|
||||
export const SINGLE_STAT_BG = 'background'
|
||||
export const SINGLE_STAT_BASE = 'base'
|
||||
|
||||
export const GAUGE_COLORS = [
|
||||
{
|
||||
|
@ -81,9 +82,13 @@ export const GAUGE_COLORS = [
|
|||
hex: '#545667',
|
||||
name: 'graphite',
|
||||
},
|
||||
{
|
||||
hex: '#ffffff',
|
||||
name: 'white',
|
||||
},
|
||||
]
|
||||
|
||||
export const DEFAULT_COLORS = [
|
||||
export const DEFAULT_GAUGE_COLORS = [
|
||||
{
|
||||
type: COLOR_TYPE_MIN,
|
||||
hex: GAUGE_COLORS[11].hex,
|
||||
|
@ -100,27 +105,73 @@ export const DEFAULT_COLORS = [
|
|||
},
|
||||
]
|
||||
|
||||
export const validateColors = (colors, type, colorSingleStatText) => {
|
||||
if (type === 'single-stat') {
|
||||
// Single stat colors should all have type of 'text' or 'background'
|
||||
const colorType = colorSingleStatText ? SINGLE_STAT_TEXT : SINGLE_STAT_BG
|
||||
return colors ? colors.map(color => ({...color, type: colorType})) : null
|
||||
}
|
||||
export const DEFAULT_SINGLESTAT_COLORS = [
|
||||
{
|
||||
type: SINGLE_STAT_TEXT,
|
||||
hex: GAUGE_COLORS[11].hex,
|
||||
id: SINGLE_STAT_BASE,
|
||||
name: GAUGE_COLORS[11].name,
|
||||
value: 0,
|
||||
},
|
||||
]
|
||||
|
||||
export const validateSingleStatColors = (colors, type) => {
|
||||
if (!colors || colors.length === 0) {
|
||||
return DEFAULT_COLORS
|
||||
}
|
||||
if (type === 'gauge') {
|
||||
// Gauge colors should have a type of min, any number of thresholds, and a max
|
||||
const formatttedColors = _.sortBy(colors, color =>
|
||||
Number(color.value)
|
||||
).map(c => ({
|
||||
...c,
|
||||
type: COLOR_TYPE_THRESHOLD,
|
||||
}))
|
||||
formatttedColors[0].type = COLOR_TYPE_MIN
|
||||
formatttedColors[formatttedColors.length - 1].type = COLOR_TYPE_MAX
|
||||
return formatttedColors
|
||||
return DEFAULT_SINGLESTAT_COLORS
|
||||
}
|
||||
|
||||
return colors.length >= MIN_THRESHOLDS ? colors : DEFAULT_COLORS
|
||||
let containsBaseColor = false
|
||||
|
||||
const formattedColors = colors.map(color => {
|
||||
if (color.id === SINGLE_STAT_BASE) {
|
||||
// Check for existance of base color
|
||||
containsBaseColor = true
|
||||
return {...color, value: Number(color.value), type}
|
||||
}
|
||||
// Single stat colors should all have type of 'text' or 'background'
|
||||
return {...color, value: Number(color.value), type}
|
||||
})
|
||||
|
||||
const formattedColorsWithBase = [
|
||||
...formattedColors,
|
||||
DEFAULT_SINGLESTAT_COLORS[0],
|
||||
]
|
||||
|
||||
return containsBaseColor ? formattedColors : formattedColorsWithBase
|
||||
}
|
||||
|
||||
export const getSingleStatType = colors => {
|
||||
const type = _.get(colors, ['0', 'type'], false)
|
||||
|
||||
if (type) {
|
||||
if (_.includes([SINGLE_STAT_TEXT, SINGLE_STAT_BG], type)) {
|
||||
return type
|
||||
}
|
||||
}
|
||||
|
||||
return SINGLE_STAT_TEXT
|
||||
}
|
||||
|
||||
export const validateGaugeColors = colors => {
|
||||
if (!colors || colors.length < MIN_THRESHOLDS) {
|
||||
return DEFAULT_GAUGE_COLORS
|
||||
}
|
||||
|
||||
// Gauge colors should have a type of min, any number of thresholds, and a max
|
||||
const formattedColors = _.sortBy(colors, color =>
|
||||
Number(color.value)
|
||||
).map(color => ({
|
||||
...color,
|
||||
value: Number(color.value),
|
||||
type: COLOR_TYPE_THRESHOLD,
|
||||
}))
|
||||
|
||||
formattedColors[0].type = COLOR_TYPE_MIN
|
||||
formattedColors[formattedColors.length - 1].type = COLOR_TYPE_MAX
|
||||
|
||||
return formattedColors
|
||||
}
|
||||
|
||||
export const stringifyColorValues = colors => {
|
||||
return colors.map(color => ({...color, value: `${color.value}`}))
|
||||
}
|
||||
|
|
|
@ -33,11 +33,12 @@ class ColorDropdown extends Component {
|
|||
|
||||
render() {
|
||||
const {visible} = this.state
|
||||
const {colors, selected, disabled} = this.props
|
||||
const {colors, selected, disabled, stretchToFit} = this.props
|
||||
|
||||
const dropdownClassNames = visible
|
||||
? 'color-dropdown open'
|
||||
: 'color-dropdown'
|
||||
const dropdownClassNames = classnames('color-dropdown', {
|
||||
open: visible,
|
||||
'color-dropdown--stretch': stretchToFit,
|
||||
})
|
||||
const toggleClassNames = classnames(
|
||||
'btn btn-sm btn-default color-dropdown--toggle',
|
||||
{active: visible, 'color-dropdown__disabled': disabled}
|
||||
|
@ -103,6 +104,7 @@ ColorDropdown.propTypes = {
|
|||
name: string.isRequired,
|
||||
})
|
||||
).isRequired,
|
||||
stretchToFit: bool,
|
||||
disabled: bool,
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,10 @@ import React, {PropTypes, PureComponent} from 'react'
|
|||
import lastValues from 'shared/parsing/lastValues'
|
||||
import Gauge from 'shared/components/Gauge'
|
||||
|
||||
import {DEFAULT_COLORS} from 'src/dashboards/constants/gaugeColors'
|
||||
import {
|
||||
DEFAULT_GAUGE_COLORS,
|
||||
stringifyColorValues,
|
||||
} from 'src/dashboards/constants/gaugeColors'
|
||||
import {DASHBOARD_LAYOUT_ROW_HEIGHT} from 'shared/constants'
|
||||
|
||||
class GaugeChart extends PureComponent {
|
||||
|
@ -60,7 +63,7 @@ class GaugeChart extends PureComponent {
|
|||
const {arrayOf, bool, number, shape, string} = PropTypes
|
||||
|
||||
GaugeChart.defaultProps = {
|
||||
colors: DEFAULT_COLORS,
|
||||
colors: stringifyColorValues(DEFAULT_GAUGE_COLORS),
|
||||
}
|
||||
|
||||
GaugeChart.propTypes = {
|
||||
|
|
|
@ -40,6 +40,7 @@ class LineGraph extends Component {
|
|||
axes,
|
||||
cell,
|
||||
title,
|
||||
colors,
|
||||
onZoom,
|
||||
queries,
|
||||
timeRange,
|
||||
|
@ -83,6 +84,14 @@ class LineGraph extends Component {
|
|||
? SINGLE_STAT_LINE_COLORS
|
||||
: overrideLineColors
|
||||
|
||||
let prefix
|
||||
let suffix
|
||||
|
||||
if (axes) {
|
||||
prefix = axes.y.prefix
|
||||
suffix = axes.y.suffix
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="dygraph graph--hasYLabel" style={{height: '100%'}}>
|
||||
{isRefreshing ? <GraphLoadingDots /> : null}
|
||||
|
@ -106,7 +115,14 @@ class LineGraph extends Component {
|
|||
options={options}
|
||||
/>
|
||||
{showSingleStat
|
||||
? <SingleStat data={data} cellHeight={cellHeight} />
|
||||
? <SingleStat
|
||||
prefix={prefix}
|
||||
suffix={suffix}
|
||||
data={data}
|
||||
lineGraph={true}
|
||||
colors={colors}
|
||||
cellHeight={cellHeight}
|
||||
/>
|
||||
: null}
|
||||
</div>
|
||||
)
|
||||
|
@ -170,6 +186,15 @@ LineGraph.propTypes = {
|
|||
resizeCoords: shape(),
|
||||
queries: arrayOf(shape({}).isRequired).isRequired,
|
||||
data: arrayOf(shape({}).isRequired).isRequired,
|
||||
colors: arrayOf(
|
||||
shape({
|
||||
type: string.isRequired,
|
||||
hex: string.isRequired,
|
||||
id: string.isRequired,
|
||||
name: string.isRequired,
|
||||
value: string.isRequired,
|
||||
}).isRequired
|
||||
),
|
||||
}
|
||||
|
||||
export default LineGraph
|
||||
|
|
|
@ -76,6 +76,7 @@ const RefreshingGraph = ({
|
|||
return (
|
||||
<RefreshingLineGraph
|
||||
axes={axes}
|
||||
colors={colors}
|
||||
onZoom={onZoom}
|
||||
queries={queries}
|
||||
key={manualRefresh}
|
||||
|
|
|
@ -1,18 +1,22 @@
|
|||
import React, {PropTypes, PureComponent} from 'react'
|
||||
import _ from 'lodash'
|
||||
import classnames from 'classnames'
|
||||
import lastValues from 'shared/parsing/lastValues'
|
||||
|
||||
import {SMALL_CELL_HEIGHT} from 'shared/graphs/helpers'
|
||||
import {SINGLE_STAT_TEXT} from 'src/dashboards/constants/gaugeColors'
|
||||
import {isBackgroundLight} from 'shared/constants/colorOperations'
|
||||
|
||||
const darkText = '#292933'
|
||||
const lightText = '#ffffff'
|
||||
import {generateSingleStatHexs} from 'shared/constants/colorOperations'
|
||||
|
||||
class SingleStat extends PureComponent {
|
||||
render() {
|
||||
const {data, cellHeight, isFetchingInitially, colors, suffix} = this.props
|
||||
const {
|
||||
data,
|
||||
cellHeight,
|
||||
isFetchingInitially,
|
||||
colors,
|
||||
prefix,
|
||||
suffix,
|
||||
lineGraph,
|
||||
} = this.props
|
||||
|
||||
// If data for this graph is being fetched for the first time, show a graph-wide spinner.
|
||||
if (isFetchingInitially) {
|
||||
|
@ -24,37 +28,20 @@ class SingleStat extends PureComponent {
|
|||
}
|
||||
|
||||
const lastValue = lastValues(data)[1]
|
||||
|
||||
const precision = 100.0
|
||||
const roundedValue = Math.round(+lastValue * precision) / precision
|
||||
let bgColor = null
|
||||
let textColor = null
|
||||
let className = 'single-stat'
|
||||
const colorizeText = !!colors.find(color => color.type === SINGLE_STAT_TEXT)
|
||||
|
||||
if (colors && colors.length > 0) {
|
||||
className = 'single-stat single-stat--colored'
|
||||
const sortedColors = _.sortBy(colors, color => Number(color.value))
|
||||
const nearestCrossedThreshold = sortedColors
|
||||
.filter(color => lastValue > color.value)
|
||||
.pop()
|
||||
|
||||
const colorizeText = _.some(colors, {type: SINGLE_STAT_TEXT})
|
||||
|
||||
if (colorizeText) {
|
||||
textColor = nearestCrossedThreshold
|
||||
? nearestCrossedThreshold.hex
|
||||
: '#292933'
|
||||
} else {
|
||||
bgColor = nearestCrossedThreshold
|
||||
? nearestCrossedThreshold.hex
|
||||
: '#292933'
|
||||
textColor = isBackgroundLight(bgColor) ? darkText : lightText
|
||||
}
|
||||
}
|
||||
const {bgColor, textColor} = generateSingleStatHexs(
|
||||
colors,
|
||||
lineGraph,
|
||||
colorizeText,
|
||||
lastValue
|
||||
)
|
||||
|
||||
return (
|
||||
<div
|
||||
className={className}
|
||||
className="single-stat"
|
||||
style={{backgroundColor: bgColor, color: textColor}}
|
||||
>
|
||||
<span
|
||||
|
@ -62,8 +49,10 @@ class SingleStat extends PureComponent {
|
|||
'single-stat--small': cellHeight === SMALL_CELL_HEIGHT,
|
||||
})}
|
||||
>
|
||||
{prefix}
|
||||
{roundedValue}
|
||||
{suffix}
|
||||
{lineGraph && <div className="single-stat--shadow" />}
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
|
@ -85,7 +74,9 @@ SingleStat.propTypes = {
|
|||
value: string.isRequired,
|
||||
}).isRequired
|
||||
),
|
||||
prefix: string,
|
||||
suffix: string,
|
||||
lineGraph: bool,
|
||||
}
|
||||
|
||||
export default SingleStat
|
||||
|
|
|
@ -1,3 +1,9 @@
|
|||
import _ from 'lodash'
|
||||
import {
|
||||
GAUGE_COLORS,
|
||||
SINGLE_STAT_BASE,
|
||||
} from 'src/dashboards/constants/gaugeColors'
|
||||
|
||||
const hexToRgb = hex => {
|
||||
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
|
||||
return result
|
||||
|
@ -16,9 +22,93 @@ const averageRgbValues = valuesObject => {
|
|||
|
||||
const trueNeutralGrey = 128
|
||||
|
||||
export const isBackgroundLight = backgroundColor => {
|
||||
const averageBackground = averageRgbValues(hexToRgb(backgroundColor))
|
||||
const isLight = averageBackground > trueNeutralGrey
|
||||
const getLegibleTextColor = bgColorHex => {
|
||||
const averageBackground = averageRgbValues(hexToRgb(bgColorHex))
|
||||
const isBackgroundLight = averageBackground > trueNeutralGrey
|
||||
|
||||
return isLight
|
||||
const darkText = '#292933'
|
||||
const lightText = '#ffffff'
|
||||
|
||||
return isBackgroundLight ? darkText : lightText
|
||||
}
|
||||
|
||||
const findNearestCrossedThreshold = (colors, lastValue) => {
|
||||
const sortedColors = _.sortBy(colors, color => Number(color.value))
|
||||
const nearestCrossedThreshold = sortedColors
|
||||
.filter(color => lastValue > color.value)
|
||||
.pop()
|
||||
|
||||
return nearestCrossedThreshold
|
||||
}
|
||||
|
||||
export const generateSingleStatHexs = (
|
||||
colors,
|
||||
containsLineGraph,
|
||||
colorizeText,
|
||||
lastValue
|
||||
) => {
|
||||
const defaultColoring = {bgColor: null, textColor: GAUGE_COLORS[11].hex}
|
||||
|
||||
if (!colors.length || !lastValue) {
|
||||
return defaultColoring
|
||||
}
|
||||
|
||||
// baseColor is expected in all cases
|
||||
const baseColor = colors.find(color => (color.id = SINGLE_STAT_BASE)) || {
|
||||
hex: defaultColoring.textColor,
|
||||
}
|
||||
|
||||
// If the single stat is above a line graph never have a background color
|
||||
if (containsLineGraph) {
|
||||
return baseColor
|
||||
? {bgColor: null, textColor: baseColor.hex}
|
||||
: defaultColoring
|
||||
}
|
||||
|
||||
// When there is only a base color and it's applied to the text
|
||||
if (colorizeText && colors.length === 1) {
|
||||
return baseColor
|
||||
? {bgColor: null, textColor: baseColor.hex}
|
||||
: defaultColoring
|
||||
}
|
||||
|
||||
// When there's multiple colors and they're applied to the text
|
||||
if (colorizeText && colors.length > 1) {
|
||||
const nearestCrossedThreshold = findNearestCrossedThreshold(
|
||||
colors,
|
||||
lastValue
|
||||
)
|
||||
const bgColor = null
|
||||
const textColor = nearestCrossedThreshold.hex
|
||||
|
||||
return {bgColor, textColor}
|
||||
}
|
||||
|
||||
// When there is only a base color and it's applued to the background
|
||||
if (colors.length === 1) {
|
||||
const bgColor = baseColor.hex
|
||||
const textColor = getLegibleTextColor(bgColor)
|
||||
|
||||
return {bgColor, textColor}
|
||||
}
|
||||
|
||||
// When there are multiple colors and they're applied to the background
|
||||
if (colors.length > 1) {
|
||||
const nearestCrossedThreshold = findNearestCrossedThreshold(
|
||||
colors,
|
||||
lastValue
|
||||
)
|
||||
|
||||
const bgColor = nearestCrossedThreshold
|
||||
? nearestCrossedThreshold.hex
|
||||
: baseColor.hex
|
||||
const textColor = getLegibleTextColor(bgColor)
|
||||
|
||||
return {bgColor, textColor}
|
||||
}
|
||||
|
||||
// If all else fails, use safe default
|
||||
const bgColor = null
|
||||
const textColor = baseColor.hex
|
||||
return {bgColor, textColor}
|
||||
}
|
||||
|
|
|
@ -242,8 +242,16 @@ button.btn.btn-primary.btn-sm.gauge-controls--add-threshold {
|
|||
|
||||
.gauge-controls--input {
|
||||
flex: 1 0 0;
|
||||
margin: 0 4px;
|
||||
margin: 0 0 0 4px;
|
||||
}
|
||||
.gauge-controls--section .color-dropdown {
|
||||
margin-left: 4px;
|
||||
}
|
||||
.gauge-controls--section .color-dropdown.color-dropdown--stretch {
|
||||
width: auto;
|
||||
flex: 1 0 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Cell Editor Overlay - Single-Stat Controls
|
||||
|
|
|
@ -11,6 +11,10 @@ $color-dropdown--circle: 14px;
|
|||
position: relative;
|
||||
}
|
||||
|
||||
.color-dropdown.color-dropdown--stretch {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.color-dropdown--toggle {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
|
|
|
@ -80,6 +80,7 @@
|
|||
height: calc(100% - 2px);
|
||||
pointer-events: none;
|
||||
border-radius: 3px;
|
||||
transition: background-color 0.25s ease, color 0.25s ease;
|
||||
@include no-user-select();
|
||||
color: $c-laser;
|
||||
|
||||
|
@ -92,15 +93,13 @@
|
|||
height: 100% !important;
|
||||
}
|
||||
}
|
||||
.single-stat.single-stat--colored {
|
||||
transition: background-color 0.25s ease, color 0.25s ease;
|
||||
}
|
||||
.single-stat--value {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%,-50%);
|
||||
width: calc(100% - 32px);
|
||||
width: auto;
|
||||
max-width: calc(100% - 32px);
|
||||
text-align: center;
|
||||
font-size: 54px;
|
||||
line-height: 54px;
|
||||
|
@ -115,15 +114,18 @@
|
|||
}
|
||||
}
|
||||
.single-stat--shadow {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
.single-stat--shadow:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 110%;
|
||||
width: 90%;
|
||||
height: 0;
|
||||
transform: translate(-50%,-50%);
|
||||
box-shadow: fade-out($g2-kevlar, 0.3) 0 0 50px 30px;
|
||||
|
|
Loading…
Reference in New Issue