Rewrite clock
parent
b907fb9822
commit
26cef5fd1d
|
@ -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>
|
|
@ -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
|
|
@ -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
|
|
@ -1,149 +1,75 @@
|
|||
css = require './style'
|
||||
Hours = require './hours'
|
||||
Minutes = require './minutes'
|
||||
|
||||
module.exports = React.createClass
|
||||
|
||||
# -- States & Properties
|
||||
propTypes:
|
||||
className : React.PropTypes.string
|
||||
mode : React.PropTypes.oneOf(['hours', 'minutes'])
|
||||
|
||||
getDefaultProps: ->
|
||||
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
|
||||
|
|
|
@ -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
|
|
@ -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)
|
||||
|
|
|
@ -2,4 +2,7 @@ Clock = require '../../components/clock'
|
|||
|
||||
module.exports = React.createClass
|
||||
render: ->
|
||||
<Clock />
|
||||
<div>
|
||||
<Clock mode="hours" />
|
||||
<Clock mode="minutes" />
|
||||
</div>
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
Loading…
Reference in New Issue