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';
38 } from './util/async-components';
40 // Dummy import, to make sure that <Status /> ends up in the application bundle.
41 // Without this it ends up in ~8 very commonly used bundles.
42 import '../../../glitch/components/status';
44 const mapStateToProps
= state
=> ({
45 systemFontUi: state
.getIn(['meta', 'system_font_ui']),
46 layout: state
.getIn(['local_settings', 'layout']),
47 isWide: state
.getIn(['local_settings', 'stretch']),
48 navbarUnder: state
.getIn(['local_settings', 'navbar_under']),
49 isComposing: state
.getIn(['compose', 'is_composing']),
52 @connect(mapStateToProps
)
54 export default class UI
extends React
.PureComponent
{
56 static contextTypes
= {
57 router: PropTypes
.object
.isRequired
,
61 dispatch: PropTypes
.func
.isRequired
,
62 children: PropTypes
.node
,
63 layout: PropTypes
.string
,
64 isWide: PropTypes
.bool
,
65 systemFontUi: PropTypes
.bool
,
66 navbarUnder: PropTypes
.bool
,
67 isComposing: PropTypes
.bool
,
68 location: PropTypes
.object
,
72 width: window
.innerWidth
,
76 handleResize
= debounce(() => {
77 // The cached heights are no longer accurate, invalidate
78 this.props
.dispatch(clearStatusesHeight());
80 this.setState({ width: window
.innerWidth
});
85 handleDragEnter
= (e
) => {
88 if (!this.dragTargets
) {
89 this.dragTargets
= [];
92 if (this.dragTargets
.indexOf(e
.target
) === -1) {
93 this.dragTargets
.push(e
.target
);
96 if (e
.dataTransfer
&& e
.dataTransfer
.types
.includes('Files')) {
97 this.setState({ draggingOver: true });
101 handleDragOver
= (e
) => {
106 e
.dataTransfer
.dropEffect
= 'copy';
114 handleDrop
= (e
) => {
117 this.setState({ draggingOver: false });
119 if (e
.dataTransfer
&& e
.dataTransfer
.files
.length
=== 1) {
120 this.props
.dispatch(uploadCompose(e
.dataTransfer
.files
));
124 handleDragLeave
= (e
) => {
128 this.dragTargets
= this.dragTargets
.filter(el
=> el
!== e
.target
&& this.node
.contains(el
));
130 if (this.dragTargets
.length
> 0) {
134 this.setState({ draggingOver: false });
137 closeUploadModal
= () => {
138 this.setState({ draggingOver: false });
141 handleServiceWorkerPostMessage
= ({ data
}) => {
142 if (data
.type
=== 'navigate') {
143 this.context
.router
.history
.push(data
.path
);
145 console
.warn('Unknown message type:', data
.type
);
149 componentWillMount () {
150 window
.addEventListener('resize', this.handleResize
, { passive: true });
151 document
.addEventListener('dragenter', this.handleDragEnter
, false);
152 document
.addEventListener('dragover', this.handleDragOver
, false);
153 document
.addEventListener('drop', this.handleDrop
, false);
154 document
.addEventListener('dragleave', this.handleDragLeave
, false);
155 document
.addEventListener('dragend', this.handleDragEnd
, false);
157 if ('serviceWorker' in navigator
) {
158 navigator
.serviceWorker
.addEventListener('message', this.handleServiceWorkerPostMessage
);
161 this.props
.dispatch(refreshHomeTimeline());
162 this.props
.dispatch(refreshNotifications());
165 shouldComponentUpdate (nextProps
) {
166 if (nextProps
.isComposing
!== this.props
.isComposing
) {
167 // Avoid expensive update just to toggle a class
168 this.node
.classList
.toggle('is-composing', nextProps
.isComposing
);
169 this.node
.classList
.toggle('navbar-under', nextProps
.navbarUnder
);
174 // Why isn't this working?!?
175 // return super.shouldComponentUpdate(nextProps, nextState);
179 componentDidUpdate (prevProps
) {
180 if (![this.props
.location
.pathname
, '/'].includes(prevProps
.location
.pathname
)) {
181 this.columnsAreaNode
.handleChildrenContentChange();
185 componentWillUnmount () {
186 window
.removeEventListener('resize', this.handleResize
);
187 document
.removeEventListener('dragenter', this.handleDragEnter
);
188 document
.removeEventListener('dragover', this.handleDragOver
);
189 document
.removeEventListener('drop', this.handleDrop
);
190 document
.removeEventListener('dragleave', this.handleDragLeave
);
191 document
.removeEventListener('dragend', this.handleDragEnd
);
198 setColumnsAreaRef
= (c
) => {
199 this.columnsAreaNode
= c
.getWrappedInstance().getWrappedInstance();
203 const { width
, draggingOver
} = this.state
;
204 const { children
, layout
, isWide
, navbarUnder
} = this.props
;
206 const columnsClass
= layout
=> {
209 return 'single-column';
211 return 'multi-columns';
213 return 'auto-columns';
217 const className
= classNames('ui', columnsClass(layout
), {
219 'system-font': this.props
.systemFontUi
,
220 'navbar-under': navbarUnder
,
224 <div className
={className
} ref
={this.setRef
}>
225 {navbarUnder
? null : (<TabsBar
/>)}
226 <ColumnsAreaContainer ref
={this.setColumnsAreaRef
} singleColumn
={isMobile(width
, layout
)}>
228 <Redirect
from='/' to
='/getting-started' exact
/>
229 <WrappedRoute path
='/getting-started' component
={GettingStarted
} content
={children
} />
230 <WrappedRoute path
='/timelines/home' component
={HomeTimeline
} content
={children
} />
231 <WrappedRoute path
='/timelines/public' exact component
={PublicTimeline
} content
={children
} />
232 <WrappedRoute path
='/timelines/public/local' component
={CommunityTimeline
} content
={children
} />
233 <WrappedRoute path
='/timelines/tag/:id' component
={HashtagTimeline
} content
={children
} />
235 <WrappedRoute path
='/notifications' component
={Notifications
} content
={children
} />
236 <WrappedRoute path
='/favourites' component
={FavouritedStatuses
} content
={children
} />
238 <WrappedRoute path
='/statuses/new' component
={Compose
} content
={children
} />
239 <WrappedRoute path
='/statuses/:statusId' exact component
={Status
} content
={children
} />
240 <WrappedRoute path
='/statuses/:statusId/reblogs' component
={Reblogs
} content
={children
} />
241 <WrappedRoute path
='/statuses/:statusId/favourites' component
={Favourites
} content
={children
} />
243 <WrappedRoute path
='/accounts/:accountId' exact component
={AccountTimeline
} content
={children
} />
244 <WrappedRoute path
='/accounts/:accountId/followers' component
={Followers
} content
={children
} />
245 <WrappedRoute path
='/accounts/:accountId/following' component
={Following
} content
={children
} />
246 <WrappedRoute path
='/accounts/:accountId/media' component
={AccountGallery
} content
={children
} />
248 <WrappedRoute path
='/follow_requests' component
={FollowRequests
} content
={children
} />
249 <WrappedRoute path
='/blocks' component
={Blocks
} content
={children
} />
250 <WrappedRoute path
='/mutes' component
={Mutes
} content
={children
} />
252 <WrappedRoute component
={GenericNotFound
} content
={children
} />
254 </ColumnsAreaContainer
>
255 <NotificationsContainer
/>
256 {navbarUnder
? (<TabsBar
/>) : null}
257 <LoadingBarContainer className
='loading-bar' />
259 <UploadArea active
={draggingOver
} onClose
={this.closeUploadModal
} />