Rewrite clock

old
Javi Velasco 2015-08-23 03:09:08 +02:00
parent b907fb9822
commit 26cef5fd1d
8 changed files with 301 additions and 214 deletions

View File

@ -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: ->
<div ref="root" className={"#{@props.className} #{css.face}"} style={@_faceStyle()}>
{ for i, k in @props.numbers
<span className={css.number}
key={i} style={@_numberStyle(@props.radius - @props.spacing, k + 1)}>
{i}
</span> }
</div>

View File

@ -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
<div className={css.hand} style={style}>
<div ref='knob' className={css.knob} onMouseDown={@_onKnobMouseDown}></div>
</div>
# -- 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

View File

@ -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
<div>
<Face
className={css.outerSphere}
numbers={if @props.format == '24hr' then PM_HOURS else AM_HOURS}
spacing={@props.spacing}
radius={@props.radius} />
{
if @props.format == '24hr'
<Face
className={css.innerSphere}
numbers={AM_HOURS}
spacing={@props.spacing}
radius={innerRadius} />
}
<Hand
degrees={0}
length={handLength}
onHandMouseMove={@_onHandMouseMove}
onHandChange={@_onHandChange}
origin={@props.center}
step={STEP} />
</div>
# -- Private constants
AM_HOURS = [12].concat([1..11])
PM_HOURS = ['00'].concat([13..23])
STEP = 360/12

View File

@ -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)"
<div ref="root" className={className} onMouseDown={@onClockMouseDown}>
{# Main Clock }
<div ref="clock" className={css.clock}>
{ <span className={css.hour} key="hour-#{i}">{i}</span> for i in [13..23] }
<span className={css.hour} key="hour-00">00</span>
</div>
{# Inner Clock }
<div ref="innerClock" className={css.innerClock}>
{ <span className={css.innerHour} key="hour-#{i}">{i}</span> for i in [1..12] }
</div>
{# Support area holders }
<div ref="clockHolder" className={css.clockHolder}></div>
<div ref="innerClockHolder" className={css.innerClockHolder}></div>
{# Clock hand }
<div ref="hand" style={handStyle} className={css.hand}>
<div className={css.knob} onMouseDown={@onKnobMouseDown}></div>
console.log @props
<div className={css.root}>
<div ref="wrapper" className={css.wrapper} style={height: @state.radius * 2} >
{
if @props.mode == 'minutes'
<Minutes
center={@state.center}
onChange={@onMinuteChange}
radius={@state.radius}
spacing={@state.radius * 0.16} />
else if @props.mode == 'hours'
<Hours
center={@state.center}
onChange={@onHourChange}
radius={@state.radius}
spacing={@state.radius * 0.16} />
}
</div>
</div>
_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

View File

@ -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: ->
<div>
<Face
className={css.outerSphere}
numbers={MINUTES}
spacing={@props.spacing}
radius={@props.radius} />
<Hand
degrees={0}
length={@props.radius - @props.spacing}
onHandChange={@_onHandChange}
origin={@props.center}
step={STEP} />
</div>
# -- Private constants
MINUTES = (('0' + i).slice(-2) for i in [0..55] by 5)
STEP = 360/60

View File

@ -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)

View File

@ -2,4 +2,7 @@ Clock = require '../../components/clock'
module.exports = React.createClass
render: ->
<Clock />
<div>
<Clock mode="hours" />
<Clock mode="minutes" />
</div>

View File

@ -21,8 +21,6 @@ Test = React.createClass
# -- Render
render: ->
<app data-toolbox={true}>
<h1>React-Toolbox <small>New way for create</small></h1>
<Clock />
</app>