mirror of
https://github.com/Foundry376/Mailspring.git
synced 2025-09-17 18:14:53 +08:00
feat(scheduler): better time picker
fix(scheduler): time picker fixed
This commit is contained in:
parent
199a900ac1
commit
6bc42a10dc
4 changed files with 148 additions and 18 deletions
|
@ -17,9 +17,6 @@
|
|||
},
|
||||
"icon": "./icon.png",
|
||||
"isOptional": true,
|
||||
"dependencies": {
|
||||
"moment-round": "^1.0"
|
||||
},
|
||||
"windowTypes": {
|
||||
"default": true,
|
||||
"composer": true,
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
color: @text-color;
|
||||
}
|
||||
.row {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
padding: 10px 0;
|
||||
|
@ -60,6 +61,7 @@
|
|||
}
|
||||
}
|
||||
.row.time {
|
||||
z-index: 10; // So the time pickers show over
|
||||
.time-picker {
|
||||
text-align: center;
|
||||
}
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
import React from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import moment from 'moment'
|
||||
require('moment-round') // overrides moment
|
||||
import classnames from 'classnames'
|
||||
|
||||
const INTERVAL = [30, 'minutes']
|
||||
|
||||
export default class TimePicker extends React.Component {
|
||||
static displayName = "TimePicker";
|
||||
|
||||
|
@ -29,26 +32,54 @@ export default class TimePicker extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this._fixTimeOptionScroll()
|
||||
}
|
||||
|
||||
componentWillReceiveProps(newProps) {
|
||||
this.setState({rawText: this._valToTimeString(newProps.value)})
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
if (this._gotoScrollStartOnUpdate) {
|
||||
this._fixTimeOptionScroll()
|
||||
}
|
||||
}
|
||||
|
||||
_valToTimeString(value) {
|
||||
return moment(value).format("LT")
|
||||
}
|
||||
|
||||
_onKeyDown = (event) => {
|
||||
if (event.key === "ArrowUp") {
|
||||
// TODO: When `renderTimeOptions` is implemented
|
||||
event.preventDefault()
|
||||
this._onArrow(event.key)
|
||||
} else if (event.key === "ArrowDown") {
|
||||
// TODO: When `renderTimeOptions` is implemented
|
||||
event.preventDefault()
|
||||
this._onArrow(event.key)
|
||||
} else if (event.key === "Enter") {
|
||||
this.context.parentTabGroup.shiftFocus(1);
|
||||
}
|
||||
}
|
||||
|
||||
_onArrow(key) {
|
||||
let newT = moment(this.props.value);
|
||||
newT = newT.round.apply(newT, INTERVAL);
|
||||
if (key === "ArrowUp") {
|
||||
newT = newT.subtract.apply(newT, INTERVAL);
|
||||
} else if (key === "ArrowDown") {
|
||||
newT = newT.add.apply(newT, INTERVAL);
|
||||
}
|
||||
if (moment(this.props.value).day() !== newT.day()) {
|
||||
return
|
||||
}
|
||||
this._gotoScrollStartOnUpdate = true
|
||||
this.props.onChange(newT);
|
||||
}
|
||||
|
||||
_onFocus = () => {
|
||||
this.setState({focused: true});
|
||||
this._gotoScrollStartOnUpdate = true
|
||||
const el = ReactDOM.findDOMNode(this.refs.input);
|
||||
el.setSelectionRange(0, el.value.length)
|
||||
}
|
||||
|
@ -83,26 +114,86 @@ export default class TimePicker extends React.Component {
|
|||
if (simpleDigitMatch && simpleDigitMatch.length > 0) {
|
||||
const hr = parseInt(simpleDigitMatch[1], 10);
|
||||
if (hr <= 7) {
|
||||
// If you're going to punch in "2" into the time field, you
|
||||
// probably mean 2pm, not 2am.
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// TODO
|
||||
_renderTimeOptions() {
|
||||
// TODO: When you select a time a dropdown will additionally show
|
||||
// letting you pick from preset times. The `relativeTo` prop will give
|
||||
// you relative times
|
||||
const opts = []
|
||||
if (this.state.focused) {
|
||||
return (
|
||||
<div className="time-options">{opts}</div>
|
||||
)
|
||||
_fixTimeOptionScroll() {
|
||||
this._gotoScrollStartOnUpdate = false
|
||||
const el = ReactDOM.findDOMNode(this);
|
||||
const scrollTo = el.querySelector(".scroll-start");
|
||||
const scrollWrap = el.querySelector(".time-options");
|
||||
if (scrollTo && scrollWrap) {
|
||||
scrollWrap.scrollTop = scrollTo.offsetTop
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
_onSelectOption(val) {
|
||||
this.props.onChange(val)
|
||||
}
|
||||
|
||||
_renderTimeOptions() {
|
||||
if (!this.state.focused) {
|
||||
return false
|
||||
}
|
||||
|
||||
const enteredMoment = moment(this.props.value);
|
||||
|
||||
const roundedMoment = moment(enteredMoment);
|
||||
roundedMoment.ceil.apply(roundedMoment, INTERVAL);
|
||||
|
||||
const firstVisibleMoment = moment(roundedMoment);
|
||||
firstVisibleMoment.add.apply(firstVisibleMoment, INTERVAL);
|
||||
|
||||
let startVal = moment(this.props.value).startOf('day').valueOf();
|
||||
startVal = Math.max(startVal, (this.props.relativeTo || 0));
|
||||
|
||||
const startMoment = moment(startVal)
|
||||
if (this.props.relativeTo) {
|
||||
startMoment.ceil.apply(startMoment, INTERVAL).add.apply(startMoment, INTERVAL)
|
||||
}
|
||||
const endMoment = moment(startVal).endOf('day');
|
||||
const opts = []
|
||||
|
||||
const relStart = moment(this.props.relativeTo);
|
||||
const timeIter = moment(startMoment)
|
||||
while (timeIter.isSameOrBefore(endMoment)) {
|
||||
const val = timeIter.valueOf();
|
||||
const className = classnames({
|
||||
option: true,
|
||||
selected: timeIter.isSame(enteredMoment),
|
||||
"scroll-start": timeIter.isSame(firstVisibleMoment),
|
||||
})
|
||||
|
||||
let relTxt = false
|
||||
if (this.props.relativeTo) {
|
||||
relTxt = (
|
||||
<span className="rel-text">
|
||||
{`(${timeIter.diff(relStart, 'hours', true)}hr)`}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
opts.push(
|
||||
<div className={className} key={val}
|
||||
onMouseDown={() => this._onSelectOption(val)}
|
||||
>
|
||||
{timeIter.format("LT")}{relTxt}
|
||||
</div>
|
||||
)
|
||||
timeIter.add.apply(timeIter, INTERVAL)
|
||||
}
|
||||
|
||||
const className = classnames({
|
||||
"time-options": true,
|
||||
"relative-to": this.props.relativeTo,
|
||||
})
|
||||
|
||||
return (
|
||||
<div className={className}>{opts}</div>
|
||||
)
|
||||
}
|
||||
|
||||
render() {
|
||||
|
|
|
@ -3,10 +3,50 @@
|
|||
.time-picker-wrap {
|
||||
display: inline-block;
|
||||
width: 5.5em;
|
||||
position: relative;
|
||||
input {
|
||||
width: 5.5em;
|
||||
&.invalid {
|
||||
background: fade(@color-error, 10%);
|
||||
}
|
||||
}
|
||||
|
||||
.time-options {
|
||||
max-height: 158px;
|
||||
overflow: auto;
|
||||
border: 1px solid #eee;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
background: @background-primary;
|
||||
border-top: 0;
|
||||
border-radius: 0 0 @border-radius-base @border-radius-base;
|
||||
box-shadow: 0 0 3px rgba(0,0,0,0.1);
|
||||
|
||||
&.relative-to {
|
||||
width: 120px;
|
||||
text-align: left;
|
||||
|
||||
.option {
|
||||
padding-left: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.option {
|
||||
line-height: 1.75;
|
||||
&.selected {
|
||||
color: @text-color;
|
||||
&:hover {
|
||||
background: transparent;
|
||||
}
|
||||
}
|
||||
&.focused, &:hover {
|
||||
background: rgba(0,0,0,0.05);
|
||||
}
|
||||
.rel-text {
|
||||
font-size: 0.8em;
|
||||
margin-left: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue