Making upload button into a smaller icon button
This commit is contained in:
parent
d42ed78aa4
commit
50d3083cbd
19 changed files with 85 additions and 229 deletions
|
@ -1,24 +0,0 @@
|
|||
import PureRenderMixin from 'react-addons-pure-render-mixin';
|
||||
|
||||
const CharacterCounter = React.createClass({
|
||||
|
||||
propTypes: {
|
||||
text: React.PropTypes.string.isRequired,
|
||||
max: React.PropTypes.number.isRequired
|
||||
},
|
||||
|
||||
mixins: [PureRenderMixin],
|
||||
|
||||
render () {
|
||||
const diff = this.props.max - this.props.text.length;
|
||||
|
||||
return (
|
||||
<span style={{ fontSize: '16px', cursor: 'default' }}>
|
||||
{diff}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
export default CharacterCounter;
|
|
@ -1,178 +0,0 @@
|
|||
import CharacterCounter from './character_counter';
|
||||
import Button from '../../../components/button';
|
||||
import PureRenderMixin from 'react-addons-pure-render-mixin';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import ReplyIndicator from './reply_indicator';
|
||||
import UploadButton from './upload_button';
|
||||
import Autosuggest from 'react-autosuggest';
|
||||
import AutosuggestAccountContainer from '../../compose/containers/autosuggest_account_container';
|
||||
import { debounce } from 'react-decoration';
|
||||
|
||||
const getTokenForSuggestions = (str, caretPosition) => {
|
||||
let word;
|
||||
|
||||
let left = str.slice(0, caretPosition).search(/\S+$/);
|
||||
let right = str.slice(caretPosition).search(/\s/);
|
||||
|
||||
if (right < 0) {
|
||||
word = str.slice(left);
|
||||
} else {
|
||||
word = str.slice(left, right + caretPosition);
|
||||
}
|
||||
|
||||
if (!word || word.trim().length < 2 || word[0] !== '@') {
|
||||
return null;
|
||||
}
|
||||
|
||||
word = word.trim().toLowerCase().slice(1);
|
||||
|
||||
if (word.length > 0) {
|
||||
return word;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const getSuggestionValue = suggestionId => suggestionId;
|
||||
const renderSuggestion = suggestionId => <AutosuggestAccountContainer id={suggestionId} />;
|
||||
|
||||
const textareaStyle = {
|
||||
display: 'block',
|
||||
boxSizing: 'border-box',
|
||||
width: '100%',
|
||||
height: '100px',
|
||||
resize: 'none',
|
||||
border: 'none',
|
||||
color: '#282c37',
|
||||
padding: '10px',
|
||||
fontFamily: 'Roboto',
|
||||
fontSize: '14px',
|
||||
margin: '0'
|
||||
};
|
||||
|
||||
const renderInputComponent = inputProps => (
|
||||
<textarea {...inputProps} placeholder='What is on your mind?' className='compose-form__textarea' style={textareaStyle} />
|
||||
);
|
||||
|
||||
const ComposeForm = React.createClass({
|
||||
|
||||
propTypes: {
|
||||
text: React.PropTypes.string.isRequired,
|
||||
suggestion_token: React.PropTypes.string,
|
||||
suggestions: React.PropTypes.array,
|
||||
is_submitting: React.PropTypes.bool,
|
||||
is_uploading: React.PropTypes.bool,
|
||||
in_reply_to: ImmutablePropTypes.map,
|
||||
onChange: React.PropTypes.func.isRequired,
|
||||
onSubmit: React.PropTypes.func.isRequired,
|
||||
onCancelReply: React.PropTypes.func.isRequired,
|
||||
onClearSuggestions: React.PropTypes.func.isRequired,
|
||||
onFetchSuggestions: React.PropTypes.func.isRequired,
|
||||
onSuggestionSelected: React.PropTypes.func.isRequired
|
||||
},
|
||||
|
||||
mixins: [PureRenderMixin],
|
||||
|
||||
handleChange (e) {
|
||||
if (typeof e.target.value === 'undefined' || typeof e.target.value === 'number') {
|
||||
return;
|
||||
}
|
||||
|
||||
this.props.onChange(e.target.value);
|
||||
},
|
||||
|
||||
handleKeyUp (e) {
|
||||
if (e.keyCode === 13 && e.ctrlKey) {
|
||||
this.props.onSubmit();
|
||||
}
|
||||
},
|
||||
|
||||
handleSubmit () {
|
||||
this.props.onSubmit();
|
||||
},
|
||||
|
||||
componentDidUpdate (prevProps) {
|
||||
if (prevProps.text !== this.props.text || prevProps.in_reply_to !== this.props.in_reply_to) {
|
||||
const textarea = this.autosuggest.input;
|
||||
|
||||
if (textarea) {
|
||||
textarea.focus();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
onSuggestionsClearRequested () {
|
||||
this.props.onClearSuggestions();
|
||||
},
|
||||
|
||||
@debounce(500)
|
||||
onSuggestionsFetchRequested ({ value }) {
|
||||
const textarea = this.autosuggest.input;
|
||||
|
||||
if (textarea) {
|
||||
const token = getTokenForSuggestions(value, textarea.selectionStart);
|
||||
|
||||
if (token !== null) {
|
||||
this.props.onFetchSuggestions(token);
|
||||
} else {
|
||||
this.props.onClearSuggestions();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
onSuggestionSelected (e, { suggestionValue }) {
|
||||
const textarea = this.autosuggest.input;
|
||||
|
||||
if (textarea) {
|
||||
this.props.onSuggestionSelected(textarea.selectionStart, suggestionValue);
|
||||
}
|
||||
},
|
||||
|
||||
setRef (c) {
|
||||
this.autosuggest = c;
|
||||
},
|
||||
|
||||
render () {
|
||||
let replyArea = '';
|
||||
const disabled = this.props.is_submitting || this.props.is_uploading;
|
||||
|
||||
if (this.props.in_reply_to) {
|
||||
replyArea = <ReplyIndicator status={this.props.in_reply_to} onCancel={this.props.onCancelReply} />;
|
||||
}
|
||||
|
||||
const inputProps = {
|
||||
placeholder: 'What is on your mind?',
|
||||
value: this.props.text,
|
||||
onKeyUp: this.handleKeyUp,
|
||||
onChange: this.handleChange,
|
||||
disabled: disabled
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ padding: '10px' }}>
|
||||
{replyArea}
|
||||
|
||||
<Autosuggest
|
||||
ref={this.setRef}
|
||||
suggestions={this.props.suggestions}
|
||||
focusFirstSuggestion={true}
|
||||
onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
|
||||
onSuggestionsClearRequested={this.onSuggestionsClearRequested}
|
||||
onSuggestionSelected={this.onSuggestionSelected}
|
||||
getSuggestionValue={getSuggestionValue}
|
||||
renderSuggestion={renderSuggestion}
|
||||
renderInputComponent={renderInputComponent}
|
||||
inputProps={inputProps}
|
||||
/>
|
||||
|
||||
<div style={{ marginTop: '10px', overflow: 'hidden' }}>
|
||||
<div style={{ float: 'right' }}><Button text='Publish' onClick={this.handleSubmit} disabled={disabled} /></div>
|
||||
<div style={{ float: 'right', marginRight: '16px', lineHeight: '36px' }}><CharacterCounter max={500} text={this.props.text} /></div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
export default ComposeForm;
|
|
@ -1,26 +0,0 @@
|
|||
import PureRenderMixin from 'react-addons-pure-render-mixin';
|
||||
|
||||
const style = {
|
||||
boxSizing: 'border-box',
|
||||
background: '#454b5e',
|
||||
padding: '0',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
overflowY: 'auto'
|
||||
};
|
||||
|
||||
const Drawer = React.createClass({
|
||||
|
||||
mixins: [PureRenderMixin],
|
||||
|
||||
render () {
|
||||
return (
|
||||
<div className='drawer' style={style}>
|
||||
{this.props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
export default Drawer;
|
|
@ -1,44 +0,0 @@
|
|||
import IconButton from '../../../components/icon_button';
|
||||
import PureRenderMixin from 'react-addons-pure-render-mixin';
|
||||
|
||||
const FollowForm = React.createClass({
|
||||
|
||||
contextTypes: {
|
||||
router: React.PropTypes.object
|
||||
},
|
||||
|
||||
propTypes: {
|
||||
text: React.PropTypes.string.isRequired,
|
||||
is_submitting: React.PropTypes.bool,
|
||||
onChange: React.PropTypes.func.isRequired,
|
||||
onSubmit: React.PropTypes.func.isRequired
|
||||
},
|
||||
|
||||
mixins: [PureRenderMixin],
|
||||
|
||||
handleChange (e) {
|
||||
this.props.onChange(e.target.value);
|
||||
},
|
||||
|
||||
handleKeyUp (e) {
|
||||
if (e.keyCode === 13) {
|
||||
this.handleSubmit();
|
||||
}
|
||||
},
|
||||
|
||||
handleSubmit () {
|
||||
this.props.onSubmit(this.context.router);
|
||||
},
|
||||
|
||||
render () {
|
||||
return (
|
||||
<div style={{ display: 'flex', lineHeight: '20px', padding: '10px', background: '#373b4a' }}>
|
||||
<input autoComplete='off' type='text' disabled={this.props.is_submitting} placeholder='username@domain' value={this.props.text} onKeyUp={this.handleKeyUp} onChange={this.handleChange} className='follow-form__input' style={{ flex: '1 1 auto', boxSizing: 'border-box', display: 'block', border: 'none', padding: '10px', fontFamily: 'Roboto', color: '#282c37', fontSize: '14px', margin: '0' }} />
|
||||
<div style={{ padding: '10px', paddingRight: '0' }}><IconButton title='Follow' size={20} icon='user-plus' onClick={this.handleSubmit} disabled={this.props.is_submitting} /></div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
export default FollowForm;
|
|
@ -1,30 +0,0 @@
|
|||
import PureRenderMixin from 'react-addons-pure-render-mixin';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import Avatar from '../../../components/avatar';
|
||||
import IconButton from '../../../components/icon_button';
|
||||
import DisplayName from '../../../components/display_name';
|
||||
import { Link } from 'react-router';
|
||||
|
||||
const NavigationBar = React.createClass({
|
||||
propTypes: {
|
||||
account: ImmutablePropTypes.map.isRequired
|
||||
},
|
||||
|
||||
mixins: [PureRenderMixin],
|
||||
|
||||
render () {
|
||||
return (
|
||||
<div style={{ padding: '10px', display: 'flex', cursor: 'default' }}>
|
||||
<Link to={`/accounts/${this.props.account.get('id')}`} style={{ textDecoration: 'none' }}><Avatar src={this.props.account.get('avatar')} size={40} /></Link>
|
||||
|
||||
<div style={{ flex: '1 1 auto', marginLeft: '8px', color: '#9baec8' }}>
|
||||
<strong style={{ fontWeight: '500', display: 'block', color: '#fff' }}>{this.props.account.get('acct')}</strong>
|
||||
<a href='/settings/profile' style={{ color: 'inherit', textDecoration: 'none' }}>Settings</a> · <Link to='/timelines/public' style={{ color: 'inherit', textDecoration: 'none' }}>Public timeline</Link> · <a href='/auth/sign_out' data-method='delete' style={{ color: 'inherit', textDecoration: 'none' }}>Logout</a>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
export default NavigationBar;
|
|
@ -1,57 +0,0 @@
|
|||
import PureRenderMixin from 'react-addons-pure-render-mixin';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import Avatar from '../../../components/avatar';
|
||||
import IconButton from '../../../components/icon_button';
|
||||
import DisplayName from '../../../components/display_name';
|
||||
import emojione from 'emojione';
|
||||
|
||||
emojione.imageType = 'png';
|
||||
emojione.sprites = false;
|
||||
emojione.imagePathPNG = '/emoji/';
|
||||
|
||||
const ReplyIndicator = React.createClass({
|
||||
|
||||
contextTypes: {
|
||||
router: React.PropTypes.object
|
||||
},
|
||||
|
||||
propTypes: {
|
||||
status: ImmutablePropTypes.map.isRequired,
|
||||
onCancel: React.PropTypes.func.isRequired
|
||||
},
|
||||
|
||||
mixins: [PureRenderMixin],
|
||||
|
||||
handleClick () {
|
||||
this.props.onCancel();
|
||||
},
|
||||
|
||||
handleAccountClick (e) {
|
||||
if (e.button === 0) {
|
||||
e.preventDefault();
|
||||
this.context.router.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`);
|
||||
}
|
||||
},
|
||||
|
||||
render () {
|
||||
let content = { __html: emojione.unicodeToImage(this.props.status.get('content')) };
|
||||
|
||||
return (
|
||||
<div style={{ background: '#9baec8', padding: '10px' }}>
|
||||
<div style={{ overflow: 'hidden', marginBottom: '5px' }}>
|
||||
<div style={{ float: 'right', lineHeight: '24px' }}><IconButton title='Cancel' icon='times' onClick={this.handleClick} /></div>
|
||||
|
||||
<a href={this.props.status.getIn(['account', 'url'])} onClick={this.handleAccountClick} className='reply-indicator__display-name' style={{ display: 'block', maxWidth: '100%', paddingRight: '25px', color: '#282c37', textDecoration: 'none', overflow: 'hidden', lineHeight: '24px' }}>
|
||||
<div style={{ float: 'left', marginRight: '5px' }}><Avatar size={24} src={this.props.status.getIn(['account', 'avatar'])} /></div>
|
||||
<DisplayName account={this.props.status.get('account')} />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div className='reply-indicator__content' dangerouslySetInnerHTML={content} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
export default ReplyIndicator;
|
|
@ -1,37 +0,0 @@
|
|||
import PureRenderMixin from 'react-addons-pure-render-mixin';
|
||||
import Button from '../../../components/button';
|
||||
|
||||
const UploadButton = React.createClass({
|
||||
|
||||
propTypes: {
|
||||
disabled: React.PropTypes.bool,
|
||||
onSelectFile: React.PropTypes.func.isRequired
|
||||
},
|
||||
|
||||
mixins: [PureRenderMixin],
|
||||
|
||||
handleChange (e) {
|
||||
if (e.target.files.length > 0) {
|
||||
this.props.onSelectFile(e.target.files);
|
||||
}
|
||||
},
|
||||
|
||||
handleClick () {
|
||||
this.refs.fileElement.click();
|
||||
},
|
||||
|
||||
render () {
|
||||
return (
|
||||
<div>
|
||||
<Button disabled={this.props.disabled} onClick={this.handleClick} block={true}>
|
||||
<i className='fa fa-fw fa-photo' /> Add media
|
||||
</Button>
|
||||
|
||||
<input ref='fileElement' type='file' multiple={false} onChange={this.handleChange} disabled={this.props.disabled} style={{ display: 'none' }} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
export default UploadButton;
|
|
@ -1,43 +0,0 @@
|
|||
import PureRenderMixin from 'react-addons-pure-render-mixin';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import UploadButton from './upload_button';
|
||||
import IconButton from '../../../components/icon_button';
|
||||
|
||||
const UploadForm = React.createClass({
|
||||
|
||||
propTypes: {
|
||||
media: ImmutablePropTypes.list.isRequired,
|
||||
is_uploading: React.PropTypes.bool,
|
||||
onSelectFile: React.PropTypes.func.isRequired,
|
||||
onRemoveFile: React.PropTypes.func.isRequired
|
||||
},
|
||||
|
||||
mixins: [PureRenderMixin],
|
||||
|
||||
render () {
|
||||
let uploads = this.props.media.map(function (attachment) {
|
||||
return (
|
||||
<div key={attachment.get('id')} style={{ borderRadius: '4px', marginBottom: '10px' }} className='transparent-background'>
|
||||
<div style={{ width: '100%', height: '100px', borderRadius: '4px', background: `url(${attachment.get('preview_url')}) no-repeat center`, backgroundSize: 'cover' }}>
|
||||
<IconButton icon='times' title='Undo' size={36} onClick={() => this.props.onRemoveFile(attachment.get('id'))} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}.bind(this));
|
||||
|
||||
const noMoreAllowed = (this.props.media.some(m => m.get('type') === 'video')) || (this.props.media.size > 3);
|
||||
|
||||
return (
|
||||
<div style={{ marginBottom: '20px', padding: '10px', paddingTop: '0' }}>
|
||||
<UploadButton onSelectFile={this.props.onSelectFile} disabled={this.props.is_uploading || noMoreAllowed } />
|
||||
|
||||
<div style={{ marginTop: '10px', overflow: 'hidden' }}>
|
||||
{uploads}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
export default UploadForm;
|
|
@ -1,58 +0,0 @@
|
|||
import { connect } from 'react-redux';
|
||||
import ComposeForm from '../components/compose_form';
|
||||
import {
|
||||
changeCompose,
|
||||
submitCompose,
|
||||
cancelReplyCompose,
|
||||
clearComposeSuggestions,
|
||||
fetchComposeSuggestions,
|
||||
selectComposeSuggestion
|
||||
} from '../../../actions/compose';
|
||||
import { makeGetStatus } from '../../../selectors';
|
||||
|
||||
const makeMapStateToProps = () => {
|
||||
const getStatus = makeGetStatus();
|
||||
|
||||
const mapStateToProps = function (state, props) {
|
||||
return {
|
||||
text: state.getIn(['compose', 'text']),
|
||||
suggestion_token: state.getIn(['compose', 'suggestion_token']),
|
||||
suggestions: state.getIn(['compose', 'suggestions']).toJS(),
|
||||
is_submitting: state.getIn(['compose', 'is_submitting']),
|
||||
is_uploading: state.getIn(['compose', 'is_uploading']),
|
||||
in_reply_to: getStatus(state, state.getIn(['compose', 'in_reply_to']))
|
||||
};
|
||||
};
|
||||
|
||||
return mapStateToProps;
|
||||
};
|
||||
|
||||
const mapDispatchToProps = function (dispatch) {
|
||||
return {
|
||||
onChange (text) {
|
||||
dispatch(changeCompose(text));
|
||||
},
|
||||
|
||||
onSubmit () {
|
||||
dispatch(submitCompose());
|
||||
},
|
||||
|
||||
onCancelReply () {
|
||||
dispatch(cancelReplyCompose());
|
||||
},
|
||||
|
||||
onClearSuggestions () {
|
||||
dispatch(clearComposeSuggestions());
|
||||
},
|
||||
|
||||
onFetchSuggestions (token) {
|
||||
dispatch(fetchComposeSuggestions(token));
|
||||
},
|
||||
|
||||
onSuggestionSelected (position, accountId) {
|
||||
dispatch(selectComposeSuggestion(position, accountId));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default connect(makeMapStateToProps, mapDispatchToProps)(ComposeForm);
|
|
@ -1,24 +0,0 @@
|
|||
import { connect } from 'react-redux';
|
||||
import FollowForm from '../components/follow_form';
|
||||
import { changeFollow, submitFollow } from '../../../actions/follow';
|
||||
|
||||
const mapStateToProps = function (state, props) {
|
||||
return {
|
||||
text: state.getIn(['follow', 'text']),
|
||||
is_submitting: state.getIn(['follow', 'is_submitting'])
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = function (dispatch) {
|
||||
return {
|
||||
onChange: function (text) {
|
||||
dispatch(changeFollow(text));
|
||||
},
|
||||
|
||||
onSubmit: function (router) {
|
||||
dispatch(submitFollow(router));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(FollowForm);
|
|
@ -1,8 +0,0 @@
|
|||
import { connect } from 'react-redux';
|
||||
import NavigationBar from '../components/navigation_bar';
|
||||
|
||||
const mapStateToProps = (state, props) => ({
|
||||
account: state.getIn(['accounts', state.getIn(['meta', 'me'])])
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps)(NavigationBar);
|
|
@ -1,25 +0,0 @@
|
|||
import { connect } from 'react-redux';
|
||||
import UploadForm from '../components/upload_form';
|
||||
import { uploadCompose, undoUploadCompose } from '../../../actions/compose';
|
||||
|
||||
const mapStateToProps = function (state, props) {
|
||||
return {
|
||||
media: state.getIn(['compose', 'media_attachments']),
|
||||
progress: state.getIn(['compose', 'progress']),
|
||||
is_uploading: state.getIn(['compose', 'is_uploading'])
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = function (dispatch) {
|
||||
return {
|
||||
onSelectFile: function (files) {
|
||||
dispatch(uploadCompose(files));
|
||||
},
|
||||
|
||||
onRemoveFile: function (media_id) {
|
||||
dispatch(undoUploadCompose(media_id));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(UploadForm);
|
Loading…
Add table
Add a link
Reference in a new issue