💄(send-later): Lots of styling of send-later

This commit is contained in:
Ben Gotow 2016-02-23 13:52:08 -08:00
parent 481dc28585
commit 39265572bc
10 changed files with 188 additions and 184 deletions

View file

@ -41,7 +41,9 @@ class InitialPackagesPage extends React.Component
<div>
{@state.packages.map (item) =>
<div className="initial-package" key={item.name}>
<img src="nylas://#{item.name}/#{item.icon}" style={width:50} />
<div className="icon-container">
<img src="nylas://#{item.name}/#{item.icon}" style={width:27, alignContent: 'center', objectFit: 'scale-down'} />
</div>
<div className="install-container">
<InstallButton package={item} />
</div>

View file

@ -394,8 +394,17 @@
border:1px solid #e1e1e1;
text-align:left;
cursor: default;
img {
.icon-container {
float:left;
width: 52px;
height: 52px;
border-radius: 6px;
background: linear-gradient(to bottom, @background-primary 0%, @background-secondary 100%);
box-shadow: 0 0.5px 0 rgba(0,0,0,0.15), 0 -0.5px 0 rgba(0,0,0,0.15), 0.5px 0 0 rgba(0,0,0,0.15), -0.5px 0 0 rgba(0,0,0,0.15), 0 0.5px 1px rgba(0, 0, 0, 0.15);
flex-shrink: 0;
text-align: center;
line-height: 50px;
margin: 12px;
margin-left: 5px;
margin-right: 20px;

View file

@ -1,8 +1,9 @@
/** @babel */
import _ from 'underscore'
import Rx from 'rx-lite'
import React, {Component, PropTypes} from 'react'
import {DateUtils} from 'nylas-exports'
import {Popover} from 'nylas-component-kit'
import {DateUtils, Message, DatabaseStore} from 'nylas-exports'
import {Popover, RetinaImg, Menu} from 'nylas-component-kit'
import SendLaterActions from './send-later-actions'
import SendLaterStore from './send-later-store'
import {DATE_FORMAT_SHORT, DATE_FORMAT_LONG} from './send-later-constants'
@ -10,6 +11,7 @@ import {DATE_FORMAT_SHORT, DATE_FORMAT_LONG} from './send-later-constants'
const SendLaterOptions = {
'In 1 hour': DateUtils.in1Hour,
'In 2 hours': DateUtils.in2Hours,
'Later Today': DateUtils.laterToday,
'Tomorrow Morning': DateUtils.tomorrow,
'Tomorrow Evening': DateUtils.tomorrowEvening,
@ -27,128 +29,133 @@ class SendLaterPopover extends Component {
constructor(props) {
super(props)
this.state = {
inputSendDate: null,
isScheduled: SendLaterStore.isScheduled(this.props.draftClientId),
inputDate: null,
scheduledDate: null,
}
}
componentDidMount() {
this.unsubscribe = SendLaterStore.listen(this.onScheduledMessagesChanged)
this._subscription = Rx.Observable.fromQuery(
DatabaseStore.findBy(Message, {clientId: this.props.draftClientId})
).subscribe((draft)=> {
const scheduledDate = SendLaterStore.getScheduledDateForMessage(draft);
if (scheduledDate !== this.state.scheduledDate) {
this.setState({scheduledDate});
}
});
}
componentWillUnmount() {
this.unsubscribe()
this._subscription.dispose();
this.unsubscribe();
}
onSendLater = (momentDate)=> {
const utcDate = momentDate.utc()
const formatted = DateUtils.format(utcDate)
SendLaterActions.sendLater(this.props.draftClientId, formatted)
onSelectMenuOption = (optionKey)=> {
const date = SendLaterOptions[optionKey]();
const formatted = DateUtils.format(date.utc())
this.setState({isScheduled: null, inputSendDate: null})
SendLaterActions.sendLater(this.props.draftClientId, formatted)
this.setState({scheduledDate: 'saving', inputDate: null})
this.refs.popover.close()
};
onCancelSendLater = ()=> {
SendLaterActions.cancelSendLater(this.props.draftClientId)
this.setState({inputSendDate: null})
this.setState({inputDate: null})
this.refs.popover.close()
};
onScheduledMessagesChanged = ()=> {
const isScheduled = SendLaterStore.isScheduled(this.props.draftClientId)
if (isScheduled !== this.state.isScheduled) {
this.setState({isScheduled});
renderCustomTimeSection() {
const updateInputDateValue = _.debounce((value)=> {
this.setState({inputDate: DateUtils.fromString(value)})
}, 250);
let dateInterpretation = false;
if (this.state.inputDate) {
dateInterpretation = (<em>
{DateUtils.format(this.state.inputDate, DATE_FORMAT_LONG)}
</em>);
}
};
onInputChange = (event)=> {
this.updateInputSendDateValue(event.target.value)
};
return (
<div key="custom" className="custom-time-section">
<input
tabIndex={1}
type="text"
placeholder="Or type a time"
onChange={event=> updateInputDateValue(event.target.value)}/>
{dateInterpretation}
</div>
)
}
getButtonLabel = (isScheduled)=> {
return isScheduled ? '✅ Scheduled' : 'Send Later';
};
renderMenuOption(optionKey) {
const date = SendLaterOptions[optionKey]();
const formatted = DateUtils.format(date, DATE_FORMAT_SHORT);
return (
<div className="send-later-option">{optionKey}<em>{formatted}</em></div>
);
}
updateInputSendDateValue = _.debounce((dateValue)=> {
const inputSendDate = DateUtils.fromString(dateValue)
this.setState({inputSendDate})
}, 250);
renderButton() {
const {scheduledDate} = this.state;
renderItems() {
return Object.keys(SendLaterOptions).map((label)=> {
const date = SendLaterOptions[label]()
const formatted = DateUtils.format(date, DATE_FORMAT_SHORT)
if (scheduledDate === 'saving') {
return (
<div
key={label}
onMouseDown={this.onSendLater.bind(this, date)}
className="send-later-option">
{label}
<em className="item-date-value">{formatted}</em>
</div>
<RetinaImg
name="inline-loading-spinner.gif"
mode={RetinaImg.Mode.ContentDark}
style={{width: 14, height: 14}}/>
);
})
}
}
renderEmptyInput() {
let className = 'btn btn-toolbar btn-send-later';
let dateInterpretation = false;
if (scheduledDate) {
className += ' scheduled';
const momentDate = DateUtils.fromString(scheduledDate);
if (momentDate) {
dateInterpretation = <span className="at">Sending in {momentDate.fromNow(true)}</span>;
}
}
return (
<div className="send-later-section">
<label>At a specific time</label>
<input
type="text"
placeholder="e.g. Next Monday at 1pm"
onChange={this.onInputChange}/>
</div>
)
}
renderLabeledInput(inputSendDate) {
const formatted = DateUtils.format(inputSendDate, DATE_FORMAT_LONG)
return (
<div className="send-later-section">
<label>At a specific time</label>
<input
type="text"
placeholder="e.g. Next Monday at 1pm"
onChange={this.onInputChange}/>
<em className="input-date-value">{formatted}</em>
<button
className="btn btn-send-later"
onClick={this.onSendLater.bind(this, inputSendDate)}>Schedule Email</button>
</div>
)
<button className={className}>
<RetinaImg name="icon-composer-sendlater.png" mode={RetinaImg.Mode.ContentIsMask}/>
{dateInterpretation}
<RetinaImg name="composer-caret.png" style={{marginLeft: 6}} mode={RetinaImg.Mode.ContentIsMask}/>
</button>
);
}
render() {
const {isScheduled, inputSendDate} = this.state
const buttonLabel = isScheduled != null ? this.getButtonLabel(isScheduled) : 'Scheduling...';
const button = (
<button className="btn btn-primary send-later-button">{buttonLabel}</button>
)
const input = inputSendDate ? this.renderLabeledInput(inputSendDate) : this.renderEmptyInput();
const footerComponents = [
<div key="divider" className="divider" />,
this.renderCustomTimeSection(),
];
if (this.state.scheduledDate) {
footerComponents.push(<div key="divider-unschedule" className="divider" />)
footerComponents.push(
<div className="cancel-section" key="cancel-section">
<button className="btn" onClick={this.onCancelSendLater}>
Unschedule Send
</button>
</div>
)
}
return (
<Popover
ref="popover"
style={{order: -103}}
className="send-later"
buttonComponent={button}>
<div className="send-later-container">
{this.renderItems()}
<div className="divider" />
{input}
{isScheduled ?
<div className="divider" />
: void 0}
{isScheduled ?
<div className="send-later-section">
<button className="btn btn-send-later" onClick={this.onCancelSendLater}>
Unschedule Send
</button>
</div>
: void 0}
</div>
buttonComponent={this.renderButton()}>
<Menu items={ Object.keys(SendLaterOptions) }
itemKey={ (item)=> item }
itemContent={this.renderMenuOption}
footerComponents={footerComponents}
onSelect={this.onSelectMenuOption}
/>
</Popover>
);
}

View file

@ -9,61 +9,36 @@ class SendLaterStore extends NylasStore {
constructor(pluginId = PLUGIN_ID) {
super()
this.pluginId = pluginId
this.scheduledMessages = new Map()
this.pluginId = pluginId;
}
activate() {
this.setupQuerySubscription()
this.unsubscribers = [
SendLaterActions.sendLater.listen(this.onSendLater),
SendLaterActions.cancelSendLater.listen(this.onCancelSendLater),
]
];
}
setupQuerySubscription() {
const query = DatabaseStore.findAll(
Message, [Message.attributes.pluginMetadata.contains(this.pluginId)]
)
this.queryDisposable = Rx.Observable.fromQuery(query).subscribe(this.onScheduledMessagesChanged)
}
getScheduledMessage = (messageClientId)=> {
return this.scheduledMessages.get(messageClientId)
};
isScheduled = (messageClientId)=> {
const message = this.getScheduledMessage(messageClientId)
if (message && message.metadataForPluginId(this.pluginId).sendLaterDate) {
return true
}
return false
getScheduledDateForMessage = (message)=> {
const metadata = message.metadataForPluginId(this.pluginId) || {};
return metadata.sendLaterDate || null;
};
setMetadata = (draftClientId, metadata)=> {
return (
DatabaseStore.modelify(Message, [draftClientId])
.then((messages)=> {
const {accountId} = messages[0]
return NylasAPI.authPlugin(this.pluginId, PLUGIN_NAME, accountId)
.then(()=> {
Actions.setMetadata(messages, this.pluginId, metadata)
})
.catch((error)=> {
console.error(error)
NylasEnv.showErrorDialog(error.message)
})
const {accountId} = messages[0];
return NylasAPI.authPlugin(this.pluginId, PLUGIN_NAME, accountId);
})
)
};
onScheduledMessagesChanged = (messages)=> {
this.scheduledMessages.clear()
messages.forEach((message)=> {
this.scheduledMessages.set(message.clientId, message);
})
this.trigger()
.then(()=> {
Actions.setMetadata(messages, this.pluginId, metadata);
})
.catch((error)=> {
NylasEnv.reportError(error);
NylasEnv.showErrorDialog(error.message);
})
);
};
onSendLater = (draftClientId, sendLaterDate)=> {
@ -75,7 +50,6 @@ class SendLaterStore extends NylasStore {
};
deactivate = ()=> {
this.queryDisposable.dispose()
this.unsubscribers.forEach(unsub => unsub())
};
}

View file

@ -1,60 +1,61 @@
@import "ui-variables";
.send-later {
em {
font-size: 0.9em;
opacity: 0.6;
}
.send-later-container {
display: flex;
flex-direction: column;
padding: 10px 0;
.menu {
width: 250px;
.divider {
border-top: 1px solid @border-color-divider;
margin: 10px 0;
width: 90%;
align-self: center;
.header-container {
display: none;
}
.send-later-section {
padding: 0 10px;
display: flex;
flex-direction: column;
label {
margin-bottom: 3px;
}
input {
border: 1px solid @input-border;
}
.input-date-value {
font-size: 0.9em;
margin: 5px 0;
}
.btn-send-later {
width: 100%;
}
.content-container {
overflow:hidden;
border-top-right-radius: @border-radius-base;
border-top-left-radius: @border-radius-base;
}
.send-later-option {
cursor: default;
width: 100%;
padding: 1px 10px;
.item-date-value {
.item {
em {
display: none;
float: right;
font-size: 0.9em;
padding-right: @padding-base-horizontal;
}
&:hover {
background-color: @background-secondary;
.item-date-value {
em {
display: inline-block;
}
}
}
}
.divider {
border-top: 1px solid @border-color-divider;
}
.custom-time-section {
padding: @padding-base-vertical @padding-base-horizontal;
}
.cancel-section {
padding: @padding-base-vertical @padding-base-horizontal;
padding-bottom: @padding-base-vertical * 1.2;
.btn {
width: 100%;
}
}
}
.btn-send-later {
&.scheduled {
img { background-color: @component-active-color; margin-right: 5px; }
color: @component-active-color;
.at {
margin-left: 3px;
}
}
}
.send-later-status {
display: flex;
align-items: center;

View file

@ -45,6 +45,10 @@ const DateUtils = {
return DateUtils.minutesFromNow(60);
},
in2Hours() {
return DateUtils.minutesFromNow(120);
},
laterToday(now = moment()) {
return now.add(3, 'hours').oclock();
},

View file

@ -11,24 +11,31 @@
border-bottom: 1px solid @base-border-color;
padding: 3px;
position: relative;
input.search {
border: 1px solid darken(@background-secondary, 10%);
border-radius: 3px;
padding-left: 0;
background-color: white;
background-repeat: no-repeat;
background-image: url("../static/images/search/searchloupe@2x.png");
background-size: 15px 15px;
background-position: 7px 4px;
text-indent: 31px;
box-shadow: inset 0 1px 0 rgba(0,0,0,0.05), 0 1px 0 rgba(0,0,0,0.05)
}
}
.footer-container {
position: relative;
}
.header-container,
.footer-container {
input[type=text] {
border: 1px solid darken(@background-secondary, 10%);
border-radius: 3px;
background-color: white;
box-shadow: inset 0 1px 0 rgba(0,0,0,0.05), 0 1px 0 rgba(0,0,0,0.05);
&.search {
padding-left: 0;
background-repeat: no-repeat;
background-image: url("../static/images/search/searchloupe@2x.png");
background-size: 15px 15px;
background-position: 7px 4px;
text-indent: 31px;
}
}
}
.content-container {
background: @background-secondary;
width: 100%;

View file

@ -6,7 +6,7 @@
}
.popover {
background-color: @background-primary;
background-color: @background-secondary;
border-radius: @border-radius-base;
box-shadow: 0 0.5px 0 rgba(0, 0, 0, 0.15), 0 -0.5px 0 rgba(0, 0, 0, 0.15), 0.5px 0 0 rgba(0, 0, 0, 0.15), -0.5px 0 0 rgba(0, 0, 0, 0.15), 0 4px 7px rgba(0,0,0,0.15);
.menu {
@ -34,7 +34,7 @@
.popover-pointer {
-webkit-mask-image: url('images/tooltip/tooltip-bg-pointer@2x.png');
background-color: @background-primary;
background-color: @background-secondary;
}
.popover-pointer.shadow {
-webkit-mask-image: url('images/tooltip/tooltip-bg-pointer-shadow@2x.png');

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB