From 26cef5fd1d934127355fb372de7563ea7aa7becf Mon Sep 17 00:00:00 2001 From: Javi Velasco Date: Sun, 23 Aug 2015 03:09:08 +0200 Subject: [PATCH] Rewrite clock --- components/clock/face.cjsx | 29 ++++++ components/clock/hand.cjsx | 87 ++++++++++++++++ components/clock/hours.cjsx | 64 ++++++++++++ components/clock/index.cjsx | 184 ++++++++++------------------------ components/clock/minutes.cjsx | 33 ++++++ components/clock/style.styl | 111 ++++++-------------- spec/components/clock.cjsx | 5 +- spec/index.cjsx | 2 - 8 files changed, 301 insertions(+), 214 deletions(-) create mode 100644 components/clock/face.cjsx create mode 100644 components/clock/hand.cjsx create mode 100644 components/clock/hours.cjsx create mode 100644 components/clock/minutes.cjsx diff --git a/components/clock/face.cjsx b/components/clock/face.cjsx new file mode 100644 index 00000000..cf857f9f --- /dev/null +++ b/components/clock/face.cjsx @@ -0,0 +1,29 @@ +css = require './style' + +module.exports = React.createClass + + # -- States & Properties + getDefaultProps: -> + className : '' + numbers : [] + radius : 0 + + # -- Internal methods + _numberStyle: (radius, num) -> + position : 'absolute' + left : (radius + radius * Math.sin(360 * (Math.PI/180) / 12 * (num - 1)) + @props.spacing) + top : (radius - radius * Math.cos(360 * (Math.PI/180) / 12 * (num - 1)) + @props.spacing) + + _faceStyle: -> + height : @props.radius * 2 + width : @props.radius * 2 + + # -- Render + render: -> +
+ { for i, k in @props.numbers + + {i} + } +
diff --git a/components/clock/hand.cjsx b/components/clock/hand.cjsx new file mode 100644 index 00000000..0de42735 --- /dev/null +++ b/components/clock/hand.cjsx @@ -0,0 +1,87 @@ +prefixer = require "../prefixer" +css = require './style' + +module.exports = React.createClass + + # -- States & Properties + propTypes: + onHandChange : React.PropTypes.func + + getDefaultProps: -> + angle : 0 + length : 0 + origin : {} + + getInitialState: -> + angle : @props.angle + knobWidth : 0 + radius : 0 + + # -- Lifecycle + componentDidMount: -> + @setState + knobWidth : @refs.knob.getDOMNode().offsetWidth + + componentWillUpdate: (nextProps, nextState) -> + @props.onHandChange(nextState.angle) + + # -- Event handlers + _onKnobMouseDown: -> + _addEventsToDocument(@_getMouseEventMap()) + + _getMouseEventMap: -> + mousemove : @onMouseMove + mouseup : @onMouseUp + + onMouseMove: (event) -> + position = _getMousePosition(event) + @props.onHandMouseMove(@_getPositionRadius(position)) if @props.onHandMouseMove + newDegrees = @_trimAngleToValue(@_positionToAngle(position)) + newDegrees = if newDegrees == 360 then 0 else newDegrees + @setState(angle: newDegrees) if @state.angle != newDegrees + + # -- Internal methods + _getPositionRadius: (position) -> + x = @props.origin.x - position.x + y = @props.origin.y - position.y + Math.sqrt(x * x + y * y) + + _trimAngleToValue: (angle) -> + @props.step * Math.round(angle/@props.step) + + _positionToAngle: (position) -> + _angle360(@props.origin.x, @props.origin.y, position.x, position.y) + + onMouseUp: -> + @_end(@_getMouseEventMap()) + + _end: (events) -> + _removeEventsFromDocument(events) + + # -- Render + render: -> + style = prefixer.transform("rotate(#{@state.angle}deg)") + style.height = @props.length - @state.knobWidth/2 + +
+
+
+ +# -- Static Helper functions +_addEventsToDocument = (events) -> + document.addEventListener(key, events[key], false) for key of events + +_removeEventsFromDocument = (events) -> + document.removeEventListener(key, events[key], false) for key of events + +_getMousePosition = (event) -> + x: event.pageX + y: event.pageY + +_angle360 = (cx, cy, ex, ey) -> + theta = _angle(cx, cy, ex, ey) + if (theta < 0) then 360 + theta else theta + +_angle = (cx, cy, ex, ey) -> + theta = Math.atan2(ey - cy, ex - cx) + Math.PI/2 + theta * 180 / Math.PI diff --git a/components/clock/hours.cjsx b/components/clock/hours.cjsx new file mode 100644 index 00000000..68fa3927 --- /dev/null +++ b/components/clock/hours.cjsx @@ -0,0 +1,64 @@ +css = require './style' +Face = require './face' +Hand = require './hand' + +module.exports = React.createClass + + # -- States & Properties + propTypes: + format : React.PropTypes.oneOf(['24hr', 'ampm']) + onChange : React.PropTypes.func + + getDefaultProps: -> + format : '24hr' + onChange : null + + getInitialState: -> + am : false + + # -- Events + _onHandMouseMove: (radius) -> + if @props.format == '24hr' + currentAm = radius < @props.radius - @props.spacing * 2 + @setState am: currentAm if @state.am != currentAm + + _onHandChange: (value) -> + if @props.format == '24hr' + values = if @state.am then AM_HOURS else PM_HOURS + else + values = AM_HOURS + @props.onChange(parseInt(values[value/STEP])) if @props.onChange + + # -- Render + render: -> + innerRadius = @props.radius - @props.spacing * 2 + handRadius = if @props.format == '24hr' && @state.am then innerRadius else @props.radius + handLength = handRadius - @props.spacing + +
+ + { + if @props.format == '24hr' + + } + +
+ +# -- Private constants +AM_HOURS = [12].concat([1..11]) +PM_HOURS = ['00'].concat([13..23]) +STEP = 360/12 diff --git a/components/clock/index.cjsx b/components/clock/index.cjsx index 01d9ee80..f18da88e 100644 --- a/components/clock/index.cjsx +++ b/components/clock/index.cjsx @@ -1,149 +1,75 @@ -css = require './style' +css = require './style' +Hours = require './hours' +Minutes = require './minutes' module.exports = React.createClass # -- States & Properties propTypes: - className : React.PropTypes.string + className : React.PropTypes.string + mode : React.PropTypes.oneOf(['hours', 'minutes']) getDefaultProps: -> - className : '' + className : '' + mode : 'hours' getInitialState: -> - clockCenter : undefined - clockCenter : undefined - clockInnerMaxRadius : undefined - clockInnerMinRadius : undefined - clockMaxRadius : undefined - clockMinRadius : undefined - handInner : false - pressed : false - handAngle : 0 + radius : 0 # -- Lifecycle componentDidMount: -> - @setState - clockCenter : @_getClockCenter() - clockMaxRadius : @_getRefRadius('root') - clockMinRadius : @_getRefRadius('clockHolder') - clockInnerMaxRadius : @_getRefRadius('clockHolder') - clockInnerMinRadius : @_getRefRadius('innerClockHolder') + window.addEventListener('resize', @handleResize) + @setState radius: @_getRadius() - # -- Position Functions - _getClockCenter: -> - bounds = @refs.root.getDOMNode().getBoundingClientRect() - return { + componentWillUpdate: -> + center = @_getCenter() + if @state.center?.x != center.x && @state.center?.y != center.y + @setState center: center + + componentWillUnmount: -> + window.removeEventListener('resize', @handleResize) + + # -- Events handlers + onHourChange: (hour) -> + console.log "Hour changed to #{hour}" + + onMinuteChange: (minute) -> + console.log "Minute changed to #{minute}" + + # -- Helper methods + _getRadius: -> + @refs.wrapper.getDOMNode().getBoundingClientRect().width/2 + + handleResize: -> + @setState + center: @_getCenter() + radius: @_getRadius() + + _getCenter: -> + bounds = @getDOMNode().getBoundingClientRect() + { x: bounds.left + (bounds.right - bounds.left)/2 y: bounds.top + (bounds.bottom - bounds.top) /2 } - _getRefRadius: (ref) -> - bounds = @refs[ref].getDOMNode().getBoundingClientRect() - (bounds.right - bounds.left)/2 - - _isInsideClockArea: (position) -> - @state.clockMinRadius < @_getPositionRadius(position) < @state.clockMaxRadius - - _isInsideClockInnerArea: (position) -> - @state.clockInnerMinRadius < @_getPositionRadius(position) < @state.clockInnerMaxRadius - - _getPositionRadius: (position) -> - x = @state.clockCenter.x - position.x - y = @state.clockCenter.y - position.y - Math.sqrt(x * x + y * y) - - # -- Helper Functions - _positionToAngle: (position) -> - _angle360(@state.clockCenter.x, @state.clockCenter.y, position.x, position.y) - - _trimAngleToValue: (angle) -> - step = 360/12 - step * Math.round(angle/step) - - _getMouseEventMap: -> - mousemove: @onMouseMove - mouseup: @onMouseUp - - _moveHandToPosition: (position) -> - trimAngle = @_trimAngleToValue(@_positionToAngle(position)) - if @_isInsideClockInnerArea(position) - @setState - handAngle: trimAngle - handInner: true - else if @_isInsideClockArea(position) - @setState - handAngle: trimAngle - handInner: false - - _end: (events) -> - _removeEventsFromDocument(events) - - # -- Event handlers - onClockMouseDown: (event) -> - position = _getMousePosition(event) - @_moveHandToPosition(position) - _addEventsToDocument(@_getMouseEventMap()) - @setState pressed: true - - onKnobMouseDown: (event) -> - _addEventsToDocument(@_getMouseEventMap()) - @setState pressed: true - - onMouseMove: (event) -> - position = _getMousePosition(event) - @_moveHandToPosition(position) - - onMouseUp: -> - @_end(@_getMouseEventMap()) - @setState pressed: false - + # -- Render render: -> - className = @props.className - className += css.root - className += " hand-inner" if @state.handInner - className += " pressed" if @state.pressed - handStyle = transform: "rotate(#{@state.handAngle}deg)" - -
- {# Main Clock } -
- { {i} for i in [13..23] } - 00 -
- - {# Inner Clock } -
- { {i} for i in [1..12] } -
- - {# Support area holders } -
-
- - {# Clock hand } -
-
+ console.log @props +
+
+ { + if @props.mode == 'minutes' + + else if @props.mode == 'hours' + + }
- -_getMousePosition = (event) -> - x: event.pageX - y: event.pageY - -_angle = (cx, cy, ex, ey) -> - dy = ey - cy; - dx = ex - cx; - theta = Math.atan2(dy, dx) + Math.PI/2 - theta = theta * 180 / Math.PI - return theta - -_angle360 = (cx, cy, ex, ey) -> - theta = _angle(cx, cy, ex, ey) - theta = 360 + theta if (theta < 0) - return theta - -_addEventsToDocument = (events) -> - document.addEventListener(key, events[key], false) for key of events - -_removeEventsFromDocument = (events) -> - document.removeEventListener(key, events[key], false) for key of events diff --git a/components/clock/minutes.cjsx b/components/clock/minutes.cjsx new file mode 100644 index 00000000..1e0675c2 --- /dev/null +++ b/components/clock/minutes.cjsx @@ -0,0 +1,33 @@ +css = require './style' +Face = require './face' +Hand = require './hand' + +module.exports = React.createClass + + # -- States & Properties + propTypes: + onChange : React.PropTypes.func + + # -- Events + _onHandChange: (value) -> + @props.onChange(parseInt(value/STEP)) if @props.onChange + + # -- Render + render: -> +
+ + +
+ +# -- Private constants +MINUTES = (('0' + i).slice(-2) for i in [0..55] by 5) +STEP = 360/60 diff --git a/components/clock/style.styl b/components/clock/style.styl index 593d8f1d..5c4f6f12 100644 --- a/components/clock/style.styl +++ b/components/clock/style.styl @@ -1,54 +1,45 @@ -rootSize = 250px -innerPadding = 12px -knobSize = 28px -hourSize = 28px -handWidth = 2px -handDotSize = 8px -clockSize = rootSize - knobSize - innerPadding - 5px -clockRadius = clockSize / 2 -clockHolderSize = clockSize - knobSize - innerPadding -innerClockSize = clockHolderSize - knobSize - innerPadding -clockInnerRadius = innerClockSize / 2 -innerClockHolderSize = innerClockSize - knobSize - innerPadding - -centeredCircle(circleSize) - border-radius : 50% - height : circleSize - left : 50% - margin-left : -(circleSize/2) - margin-top : -(circleSize/2) - position : absolute - top : 50% - width : circleSize +handWidth = 4px +handDotSize = 6px +knobSize = 34px :local(.root) - border-radius : 50% - background-color : #ccc - height : rootSize - margin-left : 30px - padding : (hourSize/2) - position : relative - width : rootSize + border: 1px solid pink + padding: 20px -:local(.clock) - centeredCircle clockSize +:local(.wrapper) + position: relative -:local(.clockHolder) - centeredCircle clockHolderSize +:local(.face) + border-radius: 50% + position: relative -:local(.innerClock) - centeredCircle innerClockSize +:local(.number) + width: 20px + text-align: center + pointer-events: none + height: 20px + margin-left: -10px + margin-top: -10px -:local(.innerClockHolder) - centeredCircle innerClockHolderSize +:local(.outerSphere), :local(.innerSphere) + position: absolute + top: 50% + left: 50% + transform: translateX(-50%) translateY(-50%) + +:local(.outerSphere) + background: gray + +:local(.innerSphere) + background: yellow :local(.hand) background-color : magenta bottom : 50% display : block - height : clockRadius - (knobSize/2) left : 50% margin-left : -(handWidth/2) + opacity : .6 position : absolute transform-origin : 50% 100% width : handWidth @@ -65,9 +56,6 @@ centeredCircle(circleSize) position : absolute width : handDotSize -:local(.root).hand-inner :local(.hand) - height : clockRadius - (knobSize/2) - knobSize - innerPadding - :local(.knob) background-color : magenta border-radius : 50% @@ -78,44 +66,3 @@ centeredCircle(circleSize) position : absolute top : -(knobSize) width : knobSize - -:local(.root).pressed :local(.knob) - background-color : pink - -:local(.hour) - background-color : gray - border-radius : 50% - color : #444 - height : hourSize - line-height : hourSize - margin-left : -(hourSize/2) - margin-top : -(hourSize/2) - pointer-events : none - position : absolute - text-align : center - width : hourSize - -:local(.innerHour) - background-color : yellowgreen - border-radius : 50% - color : purple - height : hourSize - line-height : hourSize - margin-left : -(hourSize/2) - margin-top : -(hourSize/2) - pointer-events : none - position : absolute - text-align : center - width : hourSize - -:local(.clock) - for num in 1 2 3 4 5 6 7 8 9 10 11 12 - :local(.hour):nth-child({num}) - left : clockRadius + clockRadius * sin(360deg / 12 * num) - top : clockRadius - clockRadius * cos(360deg / 12 * num) - -:local(.innerClock) - for num in 1 2 3 4 5 6 7 8 9 10 11 12 - :local(.innerHour):nth-child({num}) - left : clockInnerRadius + clockInnerRadius * sin(360deg / 12 * num) - top : clockInnerRadius - clockInnerRadius * cos(360deg / 12 * num) diff --git a/spec/components/clock.cjsx b/spec/components/clock.cjsx index 16388ff5..a4d1d4c2 100644 --- a/spec/components/clock.cjsx +++ b/spec/components/clock.cjsx @@ -2,4 +2,7 @@ Clock = require '../../components/clock' module.exports = React.createClass render: -> - +
+ + +
diff --git a/spec/index.cjsx b/spec/index.cjsx index 3935ae4c..cf1ddb7f 100644 --- a/spec/index.cjsx +++ b/spec/index.cjsx @@ -21,8 +21,6 @@ Test = React.createClass # -- Render render: -> -

React-Toolbox New way for create

-