Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
b907fb9
commit 26cef5f
Showing
8 changed files
with
298 additions
and
211 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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') | ||
|
||
# -- Position Functions | ||
_getClockCenter: -> | ||
bounds = @refs.root.getDOMNode().getBoundingClientRect() | ||
return { | ||
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 | ||
window.addEventListener('resize', @handleResize) | ||
@setState radius: @_getRadius() | ||
|
||
_isInsideClockInnerArea: (position) -> | ||
@state.clockInnerMinRadius < @_getPositionRadius(position) < @state.clockInnerMaxRadius | ||
componentWillUpdate: -> | ||
center = @_getCenter() | ||
if @state.center?.x != center.x && @state.center?.y != center.y | ||
@setState center: center | ||
|
||
_getPositionRadius: (position) -> | ||
x = @state.clockCenter.x - position.x | ||
y = @state.clockCenter.y - position.y | ||
Math.sqrt(x * x + y * y) | ||
componentWillUnmount: -> | ||
window.removeEventListener('resize', @handleResize) | ||
|
||
# -- Helper Functions | ||
_positionToAngle: (position) -> | ||
_angle360(@state.clockCenter.x, @state.clockCenter.y, position.x, position.y) | ||
# -- Events handlers | ||
onHourChange: (hour) -> | ||
console.log "Hour changed to #{hour}" | ||
|
||
_trimAngleToValue: (angle) -> | ||
step = 360/12 | ||
step * Math.round(angle/step) | ||
onMinuteChange: (minute) -> | ||
console.log "Minute changed to #{minute}" | ||
|
||
_getMouseEventMap: -> | ||
mousemove: @onMouseMove | ||
mouseup: @onMouseUp | ||
# -- Helper methods | ||
_getRadius: -> | ||
@refs.wrapper.getDOMNode().getBoundingClientRect().width/2 | ||
|
||
_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) | ||
handleResize: -> | ||
@setState | ||
center: @_getCenter() | ||
radius: @_getRadius() | ||
|
||
onMouseUp: -> | ||
@_end(@_getMouseEventMap()) | ||
@setState pressed: false | ||
_getCenter: -> | ||
bounds = @getDOMNode().getBoundingClientRect() | ||
{ | ||
x: bounds.left + (bounds.right - bounds.left)/2 | ||
y: bounds.top + (bounds.bottom - bounds.top) /2 | ||
} | ||
|
||
# -- 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 |
Oops, something went wrong.