]> cat aescling's git repositories - mastodon.git/blob - app/javascript/mastodon/features/ui/index.js
Add missing import (patch by @MightyPork)
[mastodon.git] / app / javascript / mastodon / features / ui / index.js
1 import React from 'react';
2 import NotificationsContainer from './containers/notifications_container';
3 import PropTypes from 'prop-types';
4 import LoadingBarContainer from './containers/loading_bar_container';
5 import TabsBar from './components/tabs_bar';
6 import ModalContainer from './containers/modal_container';
7 import { connect } from 'react-redux';
8 import { Redirect, withRouter } from 'react-router-dom';
9 import { isMobile } from '../../is_mobile';
10 import { debounce } from 'lodash';
11 import { uploadCompose } from '../../actions/compose';
12 import { refreshHomeTimeline } from '../../actions/timelines';
13 import { refreshNotifications } from '../../actions/notifications';
14 import { clearStatusesHeight } from '../../actions/statuses';
15 import { WrappedSwitch, WrappedRoute } from './util/react_router_helpers';
16 import UploadArea from './components/upload_area';
17 import ColumnsAreaContainer from './containers/columns_area_container';
18 import classNames from 'classnames';
19 import {
20 Compose,
21 Status,
22 GettingStarted,
23 PublicTimeline,
24 CommunityTimeline,
25 AccountTimeline,
26 AccountGallery,
27 HomeTimeline,
28 Followers,
29 Following,
30 Reblogs,
31 Favourites,
32 HashtagTimeline,
33 Notifications,
34 FollowRequests,
35 GenericNotFound,
36 FavouritedStatuses,
37 Blocks,
38 Mutes,
39 } from './util/async-components';
40
41 // Dummy import, to make sure that <Status /> ends up in the application bundle.
42 // Without this it ends up in ~8 very commonly used bundles.
43 import '../../../glitch/components/status';
44
45 const mapStateToProps = state => ({
46 systemFontUi: state.getIn(['meta', 'system_font_ui']),
47 layout: state.getIn(['local_settings', 'layout']),
48 isWide: state.getIn(['local_settings', 'stretch']),
49 navbarUnder: state.getIn(['local_settings', 'navbar_under']),
50 isComposing: state.getIn(['compose', 'is_composing']),
51 });
52
53 @connect(mapStateToProps)
54 @withRouter
55 export default class UI extends React.PureComponent {
56
57 static contextTypes = {
58 router: PropTypes.object.isRequired,
59 }
60
61 static propTypes = {
62 dispatch: PropTypes.func.isRequired,
63 children: PropTypes.node,
64 layout: PropTypes.string,
65 isWide: PropTypes.bool,
66 systemFontUi: PropTypes.bool,
67 navbarUnder: PropTypes.bool,
68 isComposing: PropTypes.bool,
69 location: PropTypes.object,
70 };
71
72 state = {
73 width: window.innerWidth,
74 draggingOver: false,
75 };
76
77 handleResize = debounce(() => {
78 // The cached heights are no longer accurate, invalidate
79 this.props.dispatch(clearStatusesHeight());
80
81 this.setState({ width: window.innerWidth });
82 }, 500, {
83 trailing: true,
84 });
85
86 handleDragEnter = (e) => {
87 e.preventDefault();
88
89 if (!this.dragTargets) {
90 this.dragTargets = [];
91 }
92
93 if (this.dragTargets.indexOf(e.target) === -1) {
94 this.dragTargets.push(e.target);
95 }
96
97 if (e.dataTransfer && e.dataTransfer.types.includes('Files')) {
98 this.setState({ draggingOver: true });
99 }
100 }
101
102 handleDragOver = (e) => {
103 e.preventDefault();
104 e.stopPropagation();
105
106 try {
107 e.dataTransfer.dropEffect = 'copy';
108 } catch (err) {
109
110 }
111
112 return false;
113 }
114
115 handleDrop = (e) => {
116 e.preventDefault();
117
118 this.setState({ draggingOver: false });
119
120 if (e.dataTransfer && e.dataTransfer.files.length === 1) {
121 this.props.dispatch(uploadCompose(e.dataTransfer.files));
122 }
123 }
124
125 handleDragLeave = (e) => {
126 e.preventDefault();
127 e.stopPropagation();
128
129 this.dragTargets = this.dragTargets.filter(el => el !== e.target && this.node.contains(el));
130
131 if (this.dragTargets.length > 0) {
132 return;
133 }
134
135 this.setState({ draggingOver: false });
136 }
137
138 closeUploadModal = () => {
139 this.setState({ draggingOver: false });
140 }
141
142 handleServiceWorkerPostMessage = ({ data }) => {
143 if (data.type === 'navigate') {
144 this.context.router.history.push(data.path);
145 } else {
146 console.warn('Unknown message type:', data.type);
147 }
148 }
149
150 componentWillMount () {
151 window.addEventListener('resize', this.handleResize, { passive: true });
152 document.addEventListener('dragenter', this.handleDragEnter, false);
153 document.addEventListener('dragover', this.handleDragOver, false);
154 document.addEventListener('drop', this.handleDrop, false);
155 document.addEventListener('dragleave', this.handleDragLeave, false);
156 document.addEventListener('dragend', this.handleDragEnd, false);
157
158 if ('serviceWorker' in navigator) {
159 navigator.serviceWorker.addEventListener('message', this.handleServiceWorkerPostMessage);
160 }
161
162 this.props.dispatch(refreshHomeTimeline());
163 this.props.dispatch(refreshNotifications());
164 }
165
166 shouldComponentUpdate (nextProps) {
167 if (nextProps.isComposing !== this.props.isComposing) {
168 // Avoid expensive update just to toggle a class
169 this.node.classList.toggle('is-composing', nextProps.isComposing);
170 this.node.classList.toggle('navbar-under', nextProps.navbarUnder);
171
172 return false;
173 }
174
175 // Why isn't this working?!?
176 // return super.shouldComponentUpdate(nextProps, nextState);
177 return true;
178 }
179
180 componentDidUpdate (prevProps) {
181 if (![this.props.location.pathname, '/'].includes(prevProps.location.pathname)) {
182 this.columnsAreaNode.handleChildrenContentChange();
183 }
184 }
185
186 componentWillUnmount () {
187 window.removeEventListener('resize', this.handleResize);
188 document.removeEventListener('dragenter', this.handleDragEnter);
189 document.removeEventListener('dragover', this.handleDragOver);
190 document.removeEventListener('drop', this.handleDrop);
191 document.removeEventListener('dragleave', this.handleDragLeave);
192 document.removeEventListener('dragend', this.handleDragEnd);
193 }
194
195 setRef = (c) => {
196 this.node = c;
197 }
198
199 setColumnsAreaRef = (c) => {
200 this.columnsAreaNode = c.getWrappedInstance().getWrappedInstance();
201 }
202
203 render () {
204 const { width, draggingOver } = this.state;
205 const { children, layout, isWide, navbarUnder } = this.props;
206
207 const columnsClass = layout => {
208 switch (layout) {
209 case 'single':
210 return 'single-column';
211 case 'multiple':
212 return 'multi-columns';
213 default:
214 return 'auto-columns';
215 }
216 };
217
218 const className = classNames('ui', columnsClass(layout), {
219 'wide': isWide,
220 'system-font': this.props.systemFontUi,
221 'navbar-under': navbarUnder,
222 });
223
224 return (
225 <div className={className} ref={this.setRef}>
226 {navbarUnder ? null : (<TabsBar />)}
227 <ColumnsAreaContainer ref={this.setColumnsAreaRef} singleColumn={isMobile(width, layout)}>
228 <WrappedSwitch>
229 <Redirect from='/' to='/getting-started' exact />
230 <WrappedRoute path='/getting-started' component={GettingStarted} content={children} />
231 <WrappedRoute path='/timelines/home' component={HomeTimeline} content={children} />
232 <WrappedRoute path='/timelines/public' exact component={PublicTimeline} content={children} />
233 <WrappedRoute path='/timelines/public/local' component={CommunityTimeline} content={children} />
234 <WrappedRoute path='/timelines/tag/:id' component={HashtagTimeline} content={children} />
235
236 <WrappedRoute path='/notifications' component={Notifications} content={children} />
237 <WrappedRoute path='/favourites' component={FavouritedStatuses} content={children} />
238
239 <WrappedRoute path='/statuses/new' component={Compose} content={children} />
240 <WrappedRoute path='/statuses/:statusId' exact component={Status} content={children} />
241 <WrappedRoute path='/statuses/:statusId/reblogs' component={Reblogs} content={children} />
242 <WrappedRoute path='/statuses/:statusId/favourites' component={Favourites} content={children} />
243
244 <WrappedRoute path='/accounts/:accountId' exact component={AccountTimeline} content={children} />
245 <WrappedRoute path='/accounts/:accountId/followers' component={Followers} content={children} />
246 <WrappedRoute path='/accounts/:accountId/following' component={Following} content={children} />
247 <WrappedRoute path='/accounts/:accountId/media' component={AccountGallery} content={children} />
248
249 <WrappedRoute path='/follow_requests' component={FollowRequests} content={children} />
250 <WrappedRoute path='/blocks' component={Blocks} content={children} />
251 <WrappedRoute path='/mutes' component={Mutes} content={children} />
252
253 <WrappedRoute component={GenericNotFound} content={children} />
254 </WrappedSwitch>
255 </ColumnsAreaContainer>
256 <NotificationsContainer />
257 {navbarUnder ? (<TabsBar />) : null}
258 <LoadingBarContainer className='loading-bar' />
259 <ModalContainer />
260 <UploadArea active={draggingOver} onClose={this.closeUploadModal} />
261 </div>
262 );
263 }
264
265 }
This page took 0.117153 seconds and 4 git commands to generate.