-
-
-
+ const uploads = media.map(attachment =>
+
+
+ {({ scale }) =>
+
+
+
+ }
+
- ));
+ );
return (
+
{uploads}
);
diff --git a/app/assets/javascripts/components/features/compose/components/upload_progress.jsx b/app/assets/javascripts/components/features/compose/components/upload_progress.jsx
new file mode 100644
index 000000000..86ffbf936
--- /dev/null
+++ b/app/assets/javascripts/components/features/compose/components/upload_progress.jsx
@@ -0,0 +1,44 @@
+import PureRenderMixin from 'react-addons-pure-render-mixin';
+import { Motion, spring } from 'react-motion';
+import { FormattedMessage } from 'react-intl';
+
+const UploadProgress = React.createClass({
+
+ propTypes: {
+ active: React.PropTypes.bool,
+ progress: React.PropTypes.number
+ },
+
+ mixins: [PureRenderMixin],
+
+ render () {
+ const { active, progress } = this.props;
+
+ if (!active) {
+ return null;
+ }
+
+ return (
+
+
+
+
+
+
+
+
+
+
+ {({ width }) =>
+
+ }
+
+
+
+
+ );
+ }
+
+});
+
+export default UploadProgress;
diff --git a/app/assets/javascripts/components/features/compose/containers/compose_form_container.jsx b/app/assets/javascripts/components/features/compose/containers/compose_form_container.jsx
index a67adbdd6..835b37516 100644
--- a/app/assets/javascripts/components/features/compose/containers/compose_form_container.jsx
+++ b/app/assets/javascripts/components/features/compose/containers/compose_form_container.jsx
@@ -30,7 +30,6 @@ const mapStateToProps = (state, props) => {
spoiler_text: state.getIn(['compose', 'spoiler_text']),
unlisted: state.getIn(['compose', 'unlisted'], ),
private: state.getIn(['compose', 'private']),
- fileDropDate: state.getIn(['compose', 'fileDropDate']),
focusDate: state.getIn(['compose', 'focusDate']),
preselectDate: state.getIn(['compose', 'preselectDate']),
is_submitting: state.getIn(['compose', 'is_submitting']),
diff --git a/app/assets/javascripts/components/features/compose/containers/upload_progress_container.jsx b/app/assets/javascripts/components/features/compose/containers/upload_progress_container.jsx
new file mode 100644
index 000000000..b0f1d4d19
--- /dev/null
+++ b/app/assets/javascripts/components/features/compose/containers/upload_progress_container.jsx
@@ -0,0 +1,9 @@
+import { connect } from 'react-redux';
+import UploadProgress from '../components/upload_progress';
+
+const mapStateToProps = (state, props) => ({
+ active: state.getIn(['compose', 'is_uploading']),
+ progress: state.getIn(['compose', 'progress'])
+});
+
+export default connect(mapStateToProps)(UploadProgress);
diff --git a/app/assets/javascripts/components/features/ui/components/upload_area.jsx b/app/assets/javascripts/components/features/ui/components/upload_area.jsx
new file mode 100644
index 000000000..70b687019
--- /dev/null
+++ b/app/assets/javascripts/components/features/ui/components/upload_area.jsx
@@ -0,0 +1,32 @@
+import PureRenderMixin from 'react-addons-pure-render-mixin';
+import { Motion, spring } from 'react-motion';
+import { FormattedMessage } from 'react-intl';
+
+const UploadArea = React.createClass({
+
+ propTypes: {
+ active: React.PropTypes.bool
+ },
+
+ mixins: [PureRenderMixin],
+
+ render () {
+ const { active } = this.props;
+
+ return (
+
+ {({ backgroundOpacity, backgroundScale }) =>
+
+ }
+
+ );
+ }
+
+});
+
+export default UploadArea;
diff --git a/app/assets/javascripts/components/features/ui/index.jsx b/app/assets/javascripts/components/features/ui/index.jsx
index 900d83dba..10e989b2a 100644
--- a/app/assets/javascripts/components/features/ui/index.jsx
+++ b/app/assets/javascripts/components/features/ui/index.jsx
@@ -13,6 +13,7 @@ import { debounce } from 'react-decoration';
import { uploadCompose } from '../../actions/compose';
import { refreshTimeline } from '../../actions/timelines';
import { refreshNotifications } from '../../actions/notifications';
+import UploadArea from './components/upload_area';
const UI = React.createClass({
@@ -23,7 +24,8 @@ const UI = React.createClass({
getInitialState () {
return {
- width: window.innerWidth
+ width: window.innerWidth,
+ draggingOver: false
};
},
@@ -41,7 +43,7 @@ const UI = React.createClass({
e.dataTransfer.dropEffect = 'copy';
if (e.dataTransfer.effectAllowed === 'all' || e.dataTransfer.effectAllowed === 'uninitialized') {
- //
+ this.setState({ draggingOver: true });
}
},
@@ -49,10 +51,15 @@ const UI = React.createClass({
e.preventDefault();
if (e.dataTransfer && e.dataTransfer.files.length === 1) {
+ this.setState({ draggingOver: false });
this.props.dispatch(uploadCompose(e.dataTransfer.files));
}
},
+ handleDragLeave () {
+ this.setState({ draggingOver: false });
+ },
+
componentWillMount () {
window.addEventListener('resize', this.handleResize, { passive: true });
window.addEventListener('dragover', this.handleDragOver);
@@ -69,12 +76,15 @@ const UI = React.createClass({
},
render () {
+ const { width, draggingOver } = this.state;
+ const { children } = this.props;
+
let mountedColumns;
- if (isMobile(this.state.width)) {
+ if (isMobile(width)) {
mountedColumns = (
- {this.props.children}
+ {children}
);
} else {
@@ -83,13 +93,13 @@ const UI = React.createClass({
- {this.props.children}
+ {children}
);
}
return (
-
+
{mountedColumns}
@@ -97,6 +107,7 @@ const UI = React.createClass({
+
);
}
diff --git a/app/assets/javascripts/components/reducers/compose.jsx b/app/assets/javascripts/components/reducers/compose.jsx
index b0001351f..a4bdd20cd 100644
--- a/app/assets/javascripts/components/reducers/compose.jsx
+++ b/app/assets/javascripts/components/reducers/compose.jsx
@@ -35,7 +35,6 @@ const initialState = Immutable.Map({
unlisted: false,
private: false,
text: '',
- fileDropDate: null,
focusDate: null,
preselectDate: null,
in_reply_to: null,
@@ -163,7 +162,6 @@ export default function compose(state = initialState, action) {
case COMPOSE_UPLOAD_REQUEST:
return state.withMutations(map => {
map.set('is_uploading', true);
- map.set('fileDropDate', new Date());
});
case COMPOSE_UPLOAD_SUCCESS:
return appendMedia(state, Immutable.fromJS(action.media));
diff --git a/app/assets/stylesheets/components.scss b/app/assets/stylesheets/components.scss
index 057c61f91..bda35425f 100644
--- a/app/assets/stylesheets/components.scss
+++ b/app/assets/stylesheets/components.scss
@@ -960,17 +960,11 @@ a.status__content__spoiler-link {
resize: none;
margin: 0;
color: $color1;
- padding: 7px;
+ padding: 10px;
font-family: inherit;
font-size: 14px;
resize: vertical;
-
- border: 3px dashed transparent;
- transition: border-color 0.3s ease;
-
- &.file-drop {
- border-color: darken($color5, 33%);
- }
+ border: 0;
}
.autosuggest-textarea__textarea {
@@ -1094,7 +1088,7 @@ button.active i.fa-retweet {
text-decoration: none;
&:hover {
- background: lighten($color1, 8%);
+ background: lighten($color1, 2%);
}
}
@@ -1431,3 +1425,116 @@ button.active i.fa-retweet {
font-weight: 500;
}
}
+
+.upload-area {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ visibility: hidden;
+ background: rgba($color8, 0.8);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ opacity: 0;
+ z-index: 2000;
+
+ * {
+ pointer-events: none;
+ }
+}
+
+.upload-area__drop {
+ width: 320px;
+ height: 160px;
+ display: flex;
+ box-sizing: border-box;
+ position: relative;
+ padding: 8px;
+}
+
+.upload-area__background {
+ position: absolute;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ z-index: -1;
+ border-radius: 4px;
+ background: $color1;
+ box-shadow: 0 0 5px rgba($color8, 0.2);
+}
+
+.upload-area__content {
+ flex: 1;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: $color2;
+ font-size: 18px;
+ font-weight: 500;
+ border: 2px dashed lighten($color1, 26%);
+ border-radius: 4px;
+}
+
+.upload-progress {
+ padding-bottom: 20px;
+ color: lighten($color1, 26%);
+ overflow: hidden;
+ display: flex;
+
+ .fa {
+ font-size: 34px;
+ margin-right: 10px;
+ }
+
+ span {
+ font-size: 12px;
+ text-transform: uppercase;
+ font-weight: 500;
+ display: block;
+ }
+}
+
+.upload-progress__backdrop {
+ width: 100%;
+ height: 6px;
+ border-radius: 6px;
+ background: lighten($color1, 26%);
+ position: relative;
+ margin-top: 5px;
+}
+
+.upload-progress__tracker {
+ position: absolute;
+ left: 0;
+ top: 0;
+ height: 6px;
+ background: $color2;
+ border-radius: 6px;
+}
+
+.emoji-button {
+ img {
+ filter: grayscale(100%);
+ opacity: 0.4;
+ display: block;
+ margin: 0;
+ width: 22px;
+ height: 22px;
+ margin-top: 2px;
+ }
+
+ &:hover {
+ img {
+ opacity: 1;
+ filter: none;
+ }
+ }
+}
+
+.dropdown--active .emoji-button img {
+ opacity: 1;
+ filter: none;
+}