diff --git a/web/src/scripts/components/input/NumberInput.jsx b/web/src/scripts/components/input/NumberInput.jsx index 0c34ac78..e63ea815 100644 --- a/web/src/scripts/components/input/NumberInput.jsx +++ b/web/src/scripts/components/input/NumberInput.jsx @@ -16,11 +16,13 @@ */ import _ from 'lodash' -import React, { Component, } from 'react' +import React, { Component, PropTypes } from 'react' import ReactDOM from 'react-dom' import { StylesEnhancer } from 'react-styles-provider' import pureRender from 'pure-render-decorator' +import { getLockedValue } from '../../utils/NumberUtils' + const stylesCreator = ({input}, {type, width, disabled}) => ({ input: { ...(type === 'platform' ? input.platform : input.regular), @@ -36,10 +38,13 @@ const stylesCreator = ({input}, {type, width, disabled}) => ({ export default class NumberInput extends Component { static propTypes = { - onChange: React.PropTypes.func.isRequired, - onSubmit: React.PropTypes.func, - value: React.PropTypes.number.isRequired, - disabled: React.PropTypes.bool, + onChange: PropTypes.func.isRequired, + onSubmit: PropTypes.func, + value: PropTypes.number.isRequired, + min: PropTypes.number, + max: PropTypes.number, + step: PropTypes.number, + disabled: PropTypes.bool, } static defaultProps = { @@ -89,23 +94,28 @@ export default class NumberInput extends Component { } onKeyDown = (e) => { + const {step, max, min} = this.props let stopPropagation = false let incrementBy = 0 switch (e.keyCode) { + // Tab case 9: ; break + // Enter case 13: stopPropagation = true this.props.onSubmit(e.target.value) break + // Up arrow case 38: - incrementBy = 1 + incrementBy = step || 1 stopPropagation = true break + // Down arrow case 40: - incrementBy = -1 + incrementBy = step ? step * -1 : -1 stopPropagation = true break default: @@ -142,6 +152,7 @@ export default class NumberInput extends Component { } value = this.roundInput(value) + value = getLockedValue(value, min, max, step) this.props.onChange(value) diff --git a/web/src/scripts/components/input/SliderInput.jsx b/web/src/scripts/components/input/SliderInput.jsx index 0059a43b..4216f64e 100644 --- a/web/src/scripts/components/input/SliderInput.jsx +++ b/web/src/scripts/components/input/SliderInput.jsx @@ -16,10 +16,12 @@ */ import _ from 'lodash' -import React, { Component, } from 'react' +import React, { Component, PropTypes } from 'react' import { StylesEnhancer } from 'react-styles-provider' import pureRender from 'pure-render-decorator' +import { getLockedValue } from '../../utils/NumberUtils' + let SLIDER_REF = 'slider' const stylesCreator = ({input, colors}, {type, width, height, trackHeight, disabled, knobWidth}) => { @@ -86,17 +88,19 @@ const stylesCreator = ({input, colors}, {type, width, height, trackHeight, disab export default class SliderInput extends Component { static propTypes = { - value: React.PropTypes.number, - min: React.PropTypes.number, - max: React.PropTypes.number, - onChange: React.PropTypes.func, - disabled: React.PropTypes.bool, + value: PropTypes.number, + min: PropTypes.number, + max: PropTypes.number, + step: PropTypes.number, + onChange: PropTypes.func, + disabled: PropTypes.bool, } static defaultProps = { value: 0, min: 0, max: 100, + step: 1, height: 30, trackHeight: 3, knobWidth: 12, @@ -168,7 +172,7 @@ export default class SliderInput extends Component { calculateValueFromPosition(position, bounds) { const trackWidth = this.getTrackWidth(bounds) - const {min, max, knobWidth} = this.props + const {min, max, knobWidth, step} = this.props // Prevent division by 0 if (trackWidth === 0) { @@ -180,7 +184,7 @@ export default class SliderInput extends Component { const percent = (position - bounds.min) / trackWidth const range = max - min const value = (percent * range) + min - return Math.round(_.clamp(value, min, max)) + return getLockedValue(value, min, max, step) } getPercentValue() { diff --git a/web/src/scripts/components/inspector/Property.jsx b/web/src/scripts/components/inspector/Property.jsx index e4d12c1c..527a982d 100644 --- a/web/src/scripts/components/inspector/Property.jsx +++ b/web/src/scripts/components/inspector/Property.jsx @@ -58,7 +58,7 @@ export default class Property extends Component { const {name, value, type, editWith} = prop const inputProps = { - value, + ...prop, title: name, onChange: this.onChange, actions, diff --git a/web/src/scripts/components/inspector/PropertyNumberInput.jsx b/web/src/scripts/components/inspector/PropertyNumberInput.jsx index 55f1c80c..48384ae0 100644 --- a/web/src/scripts/components/inspector/PropertyNumberInput.jsx +++ b/web/src/scripts/components/inspector/PropertyNumberInput.jsx @@ -18,6 +18,7 @@ import React, { Component } from 'react' import { StylesEnhancer } from 'react-styles-provider' import pureRender from 'pure-render-decorator' +import _ from 'lodash' import PropertyField from './PropertyField' import PropertyDivider from './PropertyDivider' @@ -37,6 +38,8 @@ const stylesCreator = ({fonts}) => ({ } }) +const inputPropKeys = ['value', 'min', 'max', 'step', 'onChange', 'disabled'] + @StylesEnhancer(stylesCreator) @pureRender export default class PropertyNumberInput extends Component { @@ -44,10 +47,15 @@ export default class PropertyNumberInput extends Component { static defaultProps = { title: '', value: 0, + min: 0, + max: 100, + step: 1, } render() { - const {styles, title, value, onChange, actions, dividerType, disabled} = this.props + const {styles, title, actions, dividerType} = this.props + + const inputProps = _.pick(this.props, inputPropKeys) return (
diff --git a/web/src/scripts/containers/ComponentInspector.jsx b/web/src/scripts/containers/ComponentInspector.jsx index 444de7ca..afd88d84 100644 --- a/web/src/scripts/containers/ComponentInspector.jsx +++ b/web/src/scripts/containers/ComponentInspector.jsx @@ -25,6 +25,8 @@ import { StylesEnhancer } from 'react-styles-provider' import { elementTreeActions } from '../actions' import * as uiActions from '../actions/uiActions' +import * as tabActions from '../actions/tabActions' +import * as URIUtils from '../utils/URIUtils' import * as selectors from '../selectors' import { ComponentMenuItem, PaneHeader } from '../components' import ComponentProps from './ComponentProps' @@ -48,15 +50,18 @@ const mapStateToProps = (state) => createSelector( selectors.selectedElement, selectors.selectedComponent, selectors.focusedFileId, - (element, component, focusedFileId) => ({ + selectors.tabContainerId, + (element, component, focusedFileId, tabContainerId) => ({ component: component || element, focusedFileId, + tabContainerId, }) ) const mapDispatchToProps = (dispatch) => ({ elementTreeActions: bindActionCreators(elementTreeActions, dispatch), uiActions: bindActionCreators(uiActions, dispatch), + tabActions: bindActionCreators(tabActions, dispatch), }) @StylesEnhancer(stylesCreator, ({style}) => ({style})) @@ -69,6 +74,15 @@ class ComponentInspector extends Component { uiActions.setSidebarContext() } + onTitleClick = (component) => { + // TODO: if we're not in dev mode or with correct permissions, return instead + const {tabActions, tabContainerId} = this.props + const uri = URIUtils.componentIdToURI(component.id) + + this.onBack() + tabActions.addTab(tabContainerId, uri) + } + render() { const {style, styles, width, component, elementTreeActions} = this.props @@ -80,6 +94,7 @@ class ComponentInspector extends Component { /> {component && ( diff --git a/web/src/scripts/utils/NumberUtils.js b/web/src/scripts/utils/NumberUtils.js new file mode 100644 index 00000000..d9bc2de6 --- /dev/null +++ b/web/src/scripts/utils/NumberUtils.js @@ -0,0 +1,45 @@ +/** + * Copyright (C) 2015 Deco Software Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +export const getNearestValue = (val, low, high) => { + return Math.abs(val - low) < Math.abs(val - high) ? low : high +} + +export const getDecimalCount = (num) => { + const decSplitArr = num.toString().split('.') + return decSplitArr[1] ? decSplitArr[1].length : 0 +} + +export const conditionalClamp = (val, min, max) => { + if (min && val <= min) { + return min + } + if (max && val >= max) { + return max + } + return val +} + +export const getLockedValue = (val, min, max, step) => { + const stepCount = val / step + const nearestLower = Math.max(Math.floor(stepCount) * step, min) + const nearestUpper = Math.min(Math.ceil(stepCount) * step, max) + // If step=.1, value=.82, this gets us .8 + const newValue = getNearestValue(val, nearestLower, nearestUpper) + // Clip value to step's decimals, and then remove trailing 0s + return Number(conditionalClamp(newValue, min, max).toFixed(getDecimalCount(step))) +}