about summary refs log tree commit diff
path: root/app/javascript/themes/glitch/features/ui/components
diff options
context:
space:
mode:
authorkibigo! <marrus-sh@users.noreply.github.com>2017-12-03 23:26:40 -0800
committerkibigo! <marrus-sh@users.noreply.github.com>2017-12-03 23:26:40 -0800
commitbc4fa6b198557a7f3989eb0865e2c77ac7451d29 (patch)
treea18543e1e0555e88b97cad60adc6d2abe0bffb00 /app/javascript/themes/glitch/features/ui/components
parentd216547382cf1f3419de31e1ee06272e816ea339 (diff)
Rename themes -> flavours ? ?
Diffstat (limited to 'app/javascript/themes/glitch/features/ui/components')
-rw-r--r--app/javascript/themes/glitch/features/ui/components/actions_modal.js74
-rw-r--r--app/javascript/themes/glitch/features/ui/components/boost_modal.js84
-rw-r--r--app/javascript/themes/glitch/features/ui/components/bundle.js102
-rw-r--r--app/javascript/themes/glitch/features/ui/components/bundle_column_error.js44
-rw-r--r--app/javascript/themes/glitch/features/ui/components/bundle_modal_error.js53
-rw-r--r--app/javascript/themes/glitch/features/ui/components/column.js74
-rw-r--r--app/javascript/themes/glitch/features/ui/components/column_header.js35
-rw-r--r--app/javascript/themes/glitch/features/ui/components/column_link.js39
-rw-r--r--app/javascript/themes/glitch/features/ui/components/column_loading.js30
-rw-r--r--app/javascript/themes/glitch/features/ui/components/column_subheading.js16
-rw-r--r--app/javascript/themes/glitch/features/ui/components/columns_area.js174
-rw-r--r--app/javascript/themes/glitch/features/ui/components/confirmation_modal.js53
-rw-r--r--app/javascript/themes/glitch/features/ui/components/doodle_modal.js614
-rw-r--r--app/javascript/themes/glitch/features/ui/components/drawer_loading.js11
-rw-r--r--app/javascript/themes/glitch/features/ui/components/embed_modal.js84
-rw-r--r--app/javascript/themes/glitch/features/ui/components/image_loader.js152
-rw-r--r--app/javascript/themes/glitch/features/ui/components/media_modal.js126
-rw-r--r--app/javascript/themes/glitch/features/ui/components/modal_loading.js20
-rw-r--r--app/javascript/themes/glitch/features/ui/components/modal_root.js131
-rw-r--r--app/javascript/themes/glitch/features/ui/components/mute_modal.js105
-rw-r--r--app/javascript/themes/glitch/features/ui/components/onboarding_modal.js323
-rw-r--r--app/javascript/themes/glitch/features/ui/components/report_modal.js105
-rw-r--r--app/javascript/themes/glitch/features/ui/components/tabs_bar.js84
-rw-r--r--app/javascript/themes/glitch/features/ui/components/upload_area.js52
-rw-r--r--app/javascript/themes/glitch/features/ui/components/video_modal.js33
25 files changed, 0 insertions, 2618 deletions
diff --git a/app/javascript/themes/glitch/features/ui/components/actions_modal.js b/app/javascript/themes/glitch/features/ui/components/actions_modal.js
deleted file mode 100644
index 7a2b78b63..000000000
--- a/app/javascript/themes/glitch/features/ui/components/actions_modal.js
+++ /dev/null
@@ -1,74 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import ImmutablePureComponent from 'react-immutable-pure-component';
-import StatusContent from 'themes/glitch/components/status_content';
-import Avatar from 'themes/glitch/components/avatar';
-import RelativeTimestamp from 'themes/glitch/components/relative_timestamp';
-import DisplayName from 'themes/glitch/components/display_name';
-import IconButton from 'themes/glitch/components/icon_button';
-import classNames from 'classnames';
-
-export default class ActionsModal extends ImmutablePureComponent {
-
-  static propTypes = {
-    status: ImmutablePropTypes.map,
-    actions: PropTypes.array,
-    onClick: PropTypes.func,
-  };
-
-  renderAction = (action, i) => {
-    if (action === null) {
-      return <li key={`sep-${i}`} className='dropdown-menu__separator' />;
-    }
-
-    const { icon = null, text, meta = null, active = false, href = '#' } = action;
-
-    return (
-      <li key={`${text}-${i}`}>
-        <a href={href} target='_blank' rel='noopener' onClick={this.props.onClick} data-index={i} className={classNames({ active })}>
-          {icon && <IconButton title={text} icon={icon} role='presentation' tabIndex='-1' />}
-          <div>
-            <div className={classNames({ 'actions-modal__item-label': !!meta })}>{text}</div>
-            <div>{meta}</div>
-          </div>
-        </a>
-      </li>
-    );
-  }
-
-  render () {
-    const status = this.props.status && (
-      <div className='status light'>
-        <div className='boost-modal__status-header'>
-          <div className='boost-modal__status-time'>
-            <a href={this.props.status.get('url')} className='status__relative-time' target='_blank' rel='noopener'>
-              <RelativeTimestamp timestamp={this.props.status.get('created_at')} />
-            </a>
-          </div>
-
-          <a href={this.props.status.getIn(['account', 'url'])} className='status__display-name'>
-            <div className='status__avatar'>
-              <Avatar account={this.props.status.get('account')} size={48} />
-            </div>
-
-            <DisplayName account={this.props.status.get('account')} />
-          </a>
-        </div>
-
-        <StatusContent status={this.props.status} />
-      </div>
-    );
-
-    return (
-      <div className='modal-root__modal actions-modal'>
-        {status}
-
-        <ul>
-          {this.props.actions.map(this.renderAction)}
-        </ul>
-      </div>
-    );
-  }
-
-}
diff --git a/app/javascript/themes/glitch/features/ui/components/boost_modal.js b/app/javascript/themes/glitch/features/ui/components/boost_modal.js
deleted file mode 100644
index 49781db10..000000000
--- a/app/javascript/themes/glitch/features/ui/components/boost_modal.js
+++ /dev/null
@@ -1,84 +0,0 @@
-import React from 'react';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import PropTypes from 'prop-types';
-import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
-import Button from 'themes/glitch/components/button';
-import StatusContent from 'themes/glitch/components/status_content';
-import Avatar from 'themes/glitch/components/avatar';
-import RelativeTimestamp from 'themes/glitch/components/relative_timestamp';
-import DisplayName from 'themes/glitch/components/display_name';
-import ImmutablePureComponent from 'react-immutable-pure-component';
-
-const messages = defineMessages({
-  reblog: { id: 'status.reblog', defaultMessage: 'Boost' },
-});
-
-@injectIntl
-export default class BoostModal extends ImmutablePureComponent {
-
-  static contextTypes = {
-    router: PropTypes.object,
-  };
-
-  static propTypes = {
-    status: ImmutablePropTypes.map.isRequired,
-    onReblog: PropTypes.func.isRequired,
-    onClose: PropTypes.func.isRequired,
-    intl: PropTypes.object.isRequired,
-  };
-
-  componentDidMount() {
-    this.button.focus();
-  }
-
-  handleReblog = () => {
-    this.props.onReblog(this.props.status);
-    this.props.onClose();
-  }
-
-  handleAccountClick = (e) => {
-    if (e.button === 0) {
-      e.preventDefault();
-      this.props.onClose();
-      this.context.router.history.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`);
-    }
-  }
-
-  setRef = (c) => {
-    this.button = c;
-  }
-
-  render () {
-    const { status, intl } = this.props;
-
-    return (
-      <div className='modal-root__modal boost-modal'>
-        <div className='boost-modal__container'>
-          <div className='status light'>
-            <div className='boost-modal__status-header'>
-              <div className='boost-modal__status-time'>
-                <a href={status.get('url')} className='status__relative-time' target='_blank' rel='noopener'><RelativeTimestamp timestamp={status.get('created_at')} /></a>
-              </div>
-
-              <a onClick={this.handleAccountClick} href={status.getIn(['account', 'url'])} className='status__display-name'>
-                <div className='status__avatar'>
-                  <Avatar account={status.get('account')} size={48} />
-                </div>
-
-                <DisplayName account={status.get('account')} />
-              </a>
-            </div>
-
-            <StatusContent status={status} />
-          </div>
-        </div>
-
-        <div className='boost-modal__action-bar'>
-          <div><FormattedMessage id='boost_modal.combo' defaultMessage='You can press {combo} to skip this next time' values={{ combo: <span>Shift + <i className='fa fa-retweet' /></span> }} /></div>
-          <Button text={intl.formatMessage(messages.reblog)} onClick={this.handleReblog} ref={this.setRef} />
-        </div>
-      </div>
-    );
-  }
-
-}
diff --git a/app/javascript/themes/glitch/features/ui/components/bundle.js b/app/javascript/themes/glitch/features/ui/components/bundle.js
deleted file mode 100644
index fc88e0c70..000000000
--- a/app/javascript/themes/glitch/features/ui/components/bundle.js
+++ /dev/null
@@ -1,102 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-
-const emptyComponent = () => null;
-const noop = () => { };
-
-class Bundle extends React.Component {
-
-  static propTypes = {
-    fetchComponent: PropTypes.func.isRequired,
-    loading: PropTypes.func,
-    error: PropTypes.func,
-    children: PropTypes.func.isRequired,
-    renderDelay: PropTypes.number,
-    onFetch: PropTypes.func,
-    onFetchSuccess: PropTypes.func,
-    onFetchFail: PropTypes.func,
-  }
-
-  static defaultProps = {
-    loading: emptyComponent,
-    error: emptyComponent,
-    renderDelay: 0,
-    onFetch: noop,
-    onFetchSuccess: noop,
-    onFetchFail: noop,
-  }
-
-  static cache = {}
-
-  state = {
-    mod: undefined,
-    forceRender: false,
-  }
-
-  componentWillMount() {
-    this.load(this.props);
-  }
-
-  componentWillReceiveProps(nextProps) {
-    if (nextProps.fetchComponent !== this.props.fetchComponent) {
-      this.load(nextProps);
-    }
-  }
-
-  componentWillUnmount () {
-    if (this.timeout) {
-      clearTimeout(this.timeout);
-    }
-  }
-
-  load = (props) => {
-    const { fetchComponent, onFetch, onFetchSuccess, onFetchFail, renderDelay } = props || this.props;
-
-    onFetch();
-
-    if (Bundle.cache[fetchComponent.name]) {
-      const mod = Bundle.cache[fetchComponent.name];
-
-      this.setState({ mod: mod.default });
-      onFetchSuccess();
-      return Promise.resolve();
-    }
-
-    this.setState({ mod: undefined });
-
-    if (renderDelay !== 0) {
-      this.timestamp = new Date();
-      this.timeout = setTimeout(() => this.setState({ forceRender: true }), renderDelay);
-    }
-
-    return fetchComponent()
-      .then((mod) => {
-        Bundle.cache[fetchComponent.name] = mod;
-        this.setState({ mod: mod.default });
-        onFetchSuccess();
-      })
-      .catch((error) => {
-        this.setState({ mod: null });
-        onFetchFail(error);
-      });
-  }
-
-  render() {
-    const { loading: Loading, error: Error, children, renderDelay } = this.props;
-    const { mod, forceRender } = this.state;
-    const elapsed = this.timestamp ? (new Date() - this.timestamp) : renderDelay;
-
-    if (mod === undefined) {
-      return (elapsed >= renderDelay || forceRender) ? <Loading /> : null;
-    }
-
-    if (mod === null) {
-      return <Error onRetry={this.load} />;
-    }
-
-    return children(mod);
-  }
-
-}
-
-export default Bundle;
diff --git a/app/javascript/themes/glitch/features/ui/components/bundle_column_error.js b/app/javascript/themes/glitch/features/ui/components/bundle_column_error.js
deleted file mode 100644
index daedc6299..000000000
--- a/app/javascript/themes/glitch/features/ui/components/bundle_column_error.js
+++ /dev/null
@@ -1,44 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import { defineMessages, injectIntl } from 'react-intl';
-
-import Column from './column';
-import ColumnHeader from './column_header';
-import ColumnBackButtonSlim from 'themes/glitch/components/column_back_button_slim';
-import IconButton from 'themes/glitch/components/icon_button';
-
-const messages = defineMessages({
-  title: { id: 'bundle_column_error.title', defaultMessage: 'Network error' },
-  body: { id: 'bundle_column_error.body', defaultMessage: 'Something went wrong while loading this component.' },
-  retry: { id: 'bundle_column_error.retry', defaultMessage: 'Try again' },
-});
-
-class BundleColumnError extends React.Component {
-
-  static propTypes = {
-    onRetry: PropTypes.func.isRequired,
-    intl: PropTypes.object.isRequired,
-  }
-
-  handleRetry = () => {
-    this.props.onRetry();
-  }
-
-  render () {
-    const { intl: { formatMessage } } = this.props;
-
-    return (
-      <Column>
-        <ColumnHeader icon='exclamation-circle' type={formatMessage(messages.title)} />
-        <ColumnBackButtonSlim />
-        <div className='error-column'>
-          <IconButton title={formatMessage(messages.retry)} icon='refresh' onClick={this.handleRetry} size={64} />
-          {formatMessage(messages.body)}
-        </div>
-      </Column>
-    );
-  }
-
-}
-
-export default injectIntl(BundleColumnError);
diff --git a/app/javascript/themes/glitch/features/ui/components/bundle_modal_error.js b/app/javascript/themes/glitch/features/ui/components/bundle_modal_error.js
deleted file mode 100644
index 8cca32ae9..000000000
--- a/app/javascript/themes/glitch/features/ui/components/bundle_modal_error.js
+++ /dev/null
@@ -1,53 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import { defineMessages, injectIntl } from 'react-intl';
-
-import IconButton from 'themes/glitch/components/icon_button';
-
-const messages = defineMessages({
-  error: { id: 'bundle_modal_error.message', defaultMessage: 'Something went wrong while loading this component.' },
-  retry: { id: 'bundle_modal_error.retry', defaultMessage: 'Try again' },
-  close: { id: 'bundle_modal_error.close', defaultMessage: 'Close' },
-});
-
-class BundleModalError extends React.Component {
-
-  static propTypes = {
-    onRetry: PropTypes.func.isRequired,
-    onClose: PropTypes.func.isRequired,
-    intl: PropTypes.object.isRequired,
-  }
-
-  handleRetry = () => {
-    this.props.onRetry();
-  }
-
-  render () {
-    const { onClose, intl: { formatMessage } } = this.props;
-
-    // Keep the markup in sync with <ModalLoading />
-    // (make sure they have the same dimensions)
-    return (
-      <div className='modal-root__modal error-modal'>
-        <div className='error-modal__body'>
-          <IconButton title={formatMessage(messages.retry)} icon='refresh' onClick={this.handleRetry} size={64} />
-          {formatMessage(messages.error)}
-        </div>
-
-        <div className='error-modal__footer'>
-          <div>
-            <button
-              onClick={onClose}
-              className='error-modal__nav onboarding-modal__skip'
-            >
-              {formatMessage(messages.close)}
-            </button>
-          </div>
-        </div>
-      </div>
-    );
-  }
-
-}
-
-export default injectIntl(BundleModalError);
diff --git a/app/javascript/themes/glitch/features/ui/components/column.js b/app/javascript/themes/glitch/features/ui/components/column.js
deleted file mode 100644
index 73a5bc15e..000000000
--- a/app/javascript/themes/glitch/features/ui/components/column.js
+++ /dev/null
@@ -1,74 +0,0 @@
-import React from 'react';
-import ColumnHeader from './column_header';
-import PropTypes from 'prop-types';
-import { debounce } from 'lodash';
-import { scrollTop } from 'themes/glitch/util/scroll';
-import { isMobile } from 'themes/glitch/util/is_mobile';
-
-export default class Column extends React.PureComponent {
-
-  static propTypes = {
-    heading: PropTypes.string,
-    icon: PropTypes.string,
-    children: PropTypes.node,
-    active: PropTypes.bool,
-    hideHeadingOnMobile: PropTypes.bool,
-    name: PropTypes.string,
-  };
-
-  handleHeaderClick = () => {
-    const scrollable = this.node.querySelector('.scrollable');
-
-    if (!scrollable) {
-      return;
-    }
-
-    this._interruptScrollAnimation = scrollTop(scrollable);
-  }
-
-  scrollTop () {
-    const scrollable = this.node.querySelector('.scrollable');
-
-    if (!scrollable) {
-      return;
-    }
-
-    this._interruptScrollAnimation = scrollTop(scrollable);
-  }
-
-
-  handleScroll = debounce(() => {
-    if (typeof this._interruptScrollAnimation !== 'undefined') {
-      this._interruptScrollAnimation();
-    }
-  }, 200)
-
-  setRef = (c) => {
-    this.node = c;
-  }
-
-  render () {
-    const { heading, icon, children, active, hideHeadingOnMobile, name } = this.props;
-
-    const showHeading = heading && (!hideHeadingOnMobile || (hideHeadingOnMobile && !isMobile(window.innerWidth)));
-
-    const columnHeaderId = showHeading && heading.replace(/ /g, '-');
-    const header = showHeading && (
-      <ColumnHeader icon={icon} active={active} type={heading} onClick={this.handleHeaderClick} columnHeaderId={columnHeaderId} />
-    );
-    return (
-      <div
-        ref={this.setRef}
-        role='region'
-        data-column={name}
-        aria-labelledby={columnHeaderId}
-        className='column'
-        onScroll={this.handleScroll}
-      >
-        {header}
-        {children}
-      </div>
-    );
-  }
-
-}
diff --git a/app/javascript/themes/glitch/features/ui/components/column_header.js b/app/javascript/themes/glitch/features/ui/components/column_header.js
deleted file mode 100644
index af195ea9c..000000000
--- a/app/javascript/themes/glitch/features/ui/components/column_header.js
+++ /dev/null
@@ -1,35 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-
-export default class ColumnHeader extends React.PureComponent {
-
-  static propTypes = {
-    icon: PropTypes.string,
-    type: PropTypes.string,
-    active: PropTypes.bool,
-    onClick: PropTypes.func,
-    columnHeaderId: PropTypes.string,
-  };
-
-  handleClick = () => {
-    this.props.onClick();
-  }
-
-  render () {
-    const { type, active, columnHeaderId } = this.props;
-
-    let icon = '';
-
-    if (this.props.icon) {
-      icon = <i className={`fa fa-fw fa-${this.props.icon} column-header__icon`} />;
-    }
-
-    return (
-      <div role='heading' tabIndex='0' className={`column-header ${active ? 'active' : ''}`} onClick={this.handleClick} id={columnHeaderId || null}>
-        {icon}
-        {type}
-      </div>
-    );
-  }
-
-}
diff --git a/app/javascript/themes/glitch/features/ui/components/column_link.js b/app/javascript/themes/glitch/features/ui/components/column_link.js
deleted file mode 100644
index b845d1895..000000000
--- a/app/javascript/themes/glitch/features/ui/components/column_link.js
+++ /dev/null
@@ -1,39 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import { Link } from 'react-router-dom';
-
-const ColumnLink = ({ icon, text, to, onClick, href, method }) => {
-  if (href) {
-    return (
-      <a href={href} className='column-link' data-method={method}>
-        <i className={`fa fa-fw fa-${icon} column-link__icon`} />
-        {text}
-      </a>
-    );
-  } else if (to) {
-    return (
-      <Link to={to} className='column-link'>
-        <i className={`fa fa-fw fa-${icon} column-link__icon`} />
-        {text}
-      </Link>
-    );
-  } else {
-    return (
-      <a onClick={onClick} className='column-link' role='button' tabIndex='0' data-method={method}>
-        <i className={`fa fa-fw fa-${icon} column-link__icon`} />
-        {text}
-      </a>
-    );
-  }
-};
-
-ColumnLink.propTypes = {
-  icon: PropTypes.string.isRequired,
-  text: PropTypes.string.isRequired,
-  to: PropTypes.string,
-  onClick: PropTypes.func,
-  href: PropTypes.string,
-  method: PropTypes.string,
-};
-
-export default ColumnLink;
diff --git a/app/javascript/themes/glitch/features/ui/components/column_loading.js b/app/javascript/themes/glitch/features/ui/components/column_loading.js
deleted file mode 100644
index 75f26218a..000000000
--- a/app/javascript/themes/glitch/features/ui/components/column_loading.js
+++ /dev/null
@@ -1,30 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-
-import Column from 'themes/glitch/components/column';
-import ColumnHeader from 'themes/glitch/components/column_header';
-import ImmutablePureComponent from 'react-immutable-pure-component';
-
-export default class ColumnLoading extends ImmutablePureComponent {
-
-  static propTypes = {
-    title: PropTypes.oneOfType([PropTypes.node, PropTypes.string]),
-    icon: PropTypes.string,
-  };
-
-  static defaultProps = {
-    title: '',
-    icon: '',
-  };
-
-  render() {
-    let { title, icon } = this.props;
-    return (
-      <Column>
-        <ColumnHeader icon={icon} title={title} multiColumn={false} focusable={false} />
-        <div className='scrollable' />
-      </Column>
-    );
-  }
-
-}
diff --git a/app/javascript/themes/glitch/features/ui/components/column_subheading.js b/app/javascript/themes/glitch/features/ui/components/column_subheading.js
deleted file mode 100644
index 8160c4aa3..000000000
--- a/app/javascript/themes/glitch/features/ui/components/column_subheading.js
+++ /dev/null
@@ -1,16 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-
-const ColumnSubheading = ({ text }) => {
-  return (
-    <div className='column-subheading'>
-      {text}
-    </div>
-  );
-};
-
-ColumnSubheading.propTypes = {
-  text: PropTypes.string.isRequired,
-};
-
-export default ColumnSubheading;
diff --git a/app/javascript/themes/glitch/features/ui/components/columns_area.js b/app/javascript/themes/glitch/features/ui/components/columns_area.js
deleted file mode 100644
index 452950363..000000000
--- a/app/javascript/themes/glitch/features/ui/components/columns_area.js
+++ /dev/null
@@ -1,174 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import { injectIntl } from 'react-intl';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import ImmutablePureComponent from 'react-immutable-pure-component';
-
-import ReactSwipeableViews from 'react-swipeable-views';
-import { links, getIndex, getLink } from './tabs_bar';
-
-import BundleContainer from '../containers/bundle_container';
-import ColumnLoading from './column_loading';
-import DrawerLoading from './drawer_loading';
-import BundleColumnError from './bundle_column_error';
-import { Compose, Notifications, HomeTimeline, CommunityTimeline, PublicTimeline, HashtagTimeline, DirectTimeline, FavouritedStatuses } from 'themes/glitch/util/async-components';
-
-import detectPassiveEvents from 'detect-passive-events';
-import { scrollRight } from 'themes/glitch/util/scroll';
-
-const componentMap = {
-  'COMPOSE': Compose,
-  'HOME': HomeTimeline,
-  'NOTIFICATIONS': Notifications,
-  'PUBLIC': PublicTimeline,
-  'COMMUNITY': CommunityTimeline,
-  'HASHTAG': HashtagTimeline,
-  'DIRECT': DirectTimeline,
-  'FAVOURITES': FavouritedStatuses,
-};
-
-@component => injectIntl(component, { withRef: true })
-export default class ColumnsArea extends ImmutablePureComponent {
-
-  static contextTypes = {
-    router: PropTypes.object.isRequired,
-  };
-
-  static propTypes = {
-    intl: PropTypes.object.isRequired,
-    columns: ImmutablePropTypes.list.isRequired,
-    singleColumn: PropTypes.bool,
-    children: PropTypes.node,
-  };
-
-  state = {
-    shouldAnimate: false,
-  }
-
-  componentWillReceiveProps() {
-    this.setState({ shouldAnimate: false });
-  }
-
-  componentDidMount() {
-    if (!this.props.singleColumn) {
-      this.node.addEventListener('wheel', this.handleWheel,  detectPassiveEvents.hasSupport ? { passive: true } : false);
-    }
-    this.lastIndex = getIndex(this.context.router.history.location.pathname);
-    this.setState({ shouldAnimate: true });
-  }
-
-  componentWillUpdate(nextProps) {
-    if (this.props.singleColumn !== nextProps.singleColumn && nextProps.singleColumn) {
-      this.node.removeEventListener('wheel', this.handleWheel);
-    }
-  }
-
-  componentDidUpdate(prevProps) {
-    if (this.props.singleColumn !== prevProps.singleColumn && !this.props.singleColumn) {
-      this.node.addEventListener('wheel', this.handleWheel,  detectPassiveEvents.hasSupport ? { passive: true } : false);
-    }
-    this.lastIndex = getIndex(this.context.router.history.location.pathname);
-    this.setState({ shouldAnimate: true });
-  }
-
-  componentWillUnmount () {
-    if (!this.props.singleColumn) {
-      this.node.removeEventListener('wheel', this.handleWheel);
-    }
-  }
-
-  handleChildrenContentChange() {
-    if (!this.props.singleColumn) {
-      this._interruptScrollAnimation = scrollRight(this.node, this.node.scrollWidth - window.innerWidth);
-    }
-  }
-
-  handleSwipe = (index) => {
-    this.pendingIndex = index;
-
-    const nextLinkTranslationId = links[index].props['data-preview-title-id'];
-    const currentLinkSelector = '.tabs-bar__link.active';
-    const nextLinkSelector = `.tabs-bar__link[data-preview-title-id="${nextLinkTranslationId}"]`;
-
-    // HACK: Remove the active class from the current link and set it to the next one
-    // React-router does this for us, but too late, feeling laggy.
-    document.querySelector(currentLinkSelector).classList.remove('active');
-    document.querySelector(nextLinkSelector).classList.add('active');
-  }
-
-  handleAnimationEnd = () => {
-    if (typeof this.pendingIndex === 'number') {
-      this.context.router.history.push(getLink(this.pendingIndex));
-      this.pendingIndex = null;
-    }
-  }
-
-  handleWheel = () => {
-    if (typeof this._interruptScrollAnimation !== 'function') {
-      return;
-    }
-
-    this._interruptScrollAnimation();
-  }
-
-  setRef = (node) => {
-    this.node = node;
-  }
-
-  renderView = (link, index) => {
-    const columnIndex = getIndex(this.context.router.history.location.pathname);
-    const title = this.props.intl.formatMessage({ id: link.props['data-preview-title-id'] });
-    const icon = link.props['data-preview-icon'];
-
-    const view = (index === columnIndex) ?
-      React.cloneElement(this.props.children) :
-      <ColumnLoading title={title} icon={icon} />;
-
-    return (
-      <div className='columns-area' key={index}>
-        {view}
-      </div>
-    );
-  }
-
-  renderLoading = columnId => () => {
-    return columnId === 'COMPOSE' ? <DrawerLoading /> : <ColumnLoading />;
-  }
-
-  renderError = (props) => {
-    return <BundleColumnError {...props} />;
-  }
-
-  render () {
-    const { columns, children, singleColumn } = this.props;
-    const { shouldAnimate } = this.state;
-
-    const columnIndex = getIndex(this.context.router.history.location.pathname);
-    this.pendingIndex = null;
-
-    if (singleColumn) {
-      return columnIndex !== -1 ? (
-        <ReactSwipeableViews index={columnIndex} onChangeIndex={this.handleSwipe} onTransitionEnd={this.handleAnimationEnd} animateTransitions={shouldAnimate} springConfig={{ duration: '400ms', delay: '0s', easeFunction: 'ease' }} style={{ height: '100%' }}>
-          {links.map(this.renderView)}
-        </ReactSwipeableViews>
-      ) : <div className='columns-area'>{children}</div>;
-    }
-
-    return (
-      <div className='columns-area' ref={this.setRef}>
-        {columns.map(column => {
-          const params = column.get('params', null) === null ? null : column.get('params').toJS();
-
-          return (
-            <BundleContainer key={column.get('uuid')} fetchComponent={componentMap[column.get('id')]} loading={this.renderLoading(column.get('id'))} error={this.renderError}>
-              {SpecificComponent => <SpecificComponent columnId={column.get('uuid')} params={params} multiColumn />}
-            </BundleContainer>
-          );
-        })}
-
-        {React.Children.map(children, child => React.cloneElement(child, { multiColumn: true }))}
-      </div>
-    );
-  }
-
-}
diff --git a/app/javascript/themes/glitch/features/ui/components/confirmation_modal.js b/app/javascript/themes/glitch/features/ui/components/confirmation_modal.js
deleted file mode 100644
index 3d568aec3..000000000
--- a/app/javascript/themes/glitch/features/ui/components/confirmation_modal.js
+++ /dev/null
@@ -1,53 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import { injectIntl, FormattedMessage } from 'react-intl';
-import Button from 'themes/glitch/components/button';
-
-@injectIntl
-export default class ConfirmationModal extends React.PureComponent {
-
-  static propTypes = {
-    message: PropTypes.node.isRequired,
-    confirm: PropTypes.string.isRequired,
-    onClose: PropTypes.func.isRequired,
-    onConfirm: PropTypes.func.isRequired,
-    intl: PropTypes.object.isRequired,
-  };
-
-  componentDidMount() {
-    this.button.focus();
-  }
-
-  handleClick = () => {
-    this.props.onClose();
-    this.props.onConfirm();
-  }
-
-  handleCancel = () => {
-    this.props.onClose();
-  }
-
-  setRef = (c) => {
-    this.button = c;
-  }
-
-  render () {
-    const { message, confirm } = this.props;
-
-    return (
-      <div className='modal-root__modal confirmation-modal'>
-        <div className='confirmation-modal__container'>
-          {message}
-        </div>
-
-        <div className='confirmation-modal__action-bar'>
-          <Button onClick={this.handleCancel} className='confirmation-modal__cancel-button'>
-            <FormattedMessage id='confirmation_modal.cancel' defaultMessage='Cancel' />
-          </Button>
-          <Button text={confirm} onClick={this.handleClick} ref={this.setRef} />
-        </div>
-      </div>
-    );
-  }
-
-}
diff --git a/app/javascript/themes/glitch/features/ui/components/doodle_modal.js b/app/javascript/themes/glitch/features/ui/components/doodle_modal.js
deleted file mode 100644
index 819656dbf..000000000
--- a/app/javascript/themes/glitch/features/ui/components/doodle_modal.js
+++ /dev/null
@@ -1,614 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import Button from 'themes/glitch/components/button';
-import ImmutablePureComponent from 'react-immutable-pure-component';
-import Atrament from 'atrament'; // the doodling library
-import { connect } from 'react-redux';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import { doodleSet, uploadCompose } from 'themes/glitch/actions/compose';
-import IconButton from 'themes/glitch/components/icon_button';
-import { debounce, mapValues } from 'lodash';
-import classNames from 'classnames';
-
-// palette nicked from MyPaint, CC0
-const palette = [
-  ['rgb(  0,    0,    0)', 'Black'],
-  ['rgb( 38,   38,   38)', 'Gray 15'],
-  ['rgb( 77,   77,   77)', 'Grey 30'],
-  ['rgb(128,  128,  128)', 'Grey 50'],
-  ['rgb(171,  171,  171)', 'Grey 67'],
-  ['rgb(217,  217,  217)', 'Grey 85'],
-  ['rgb(255,  255,  255)', 'White'],
-  ['rgb(128,    0,    0)', 'Maroon'],
-  ['rgb(209,    0,    0)', 'English-red'],
-  ['rgb(255,   54,   34)', 'Tomato'],
-  ['rgb(252,   60,    3)', 'Orange-red'],
-  ['rgb(255,  140,  105)', 'Salmon'],
-  ['rgb(252,  232,   32)', 'Cadium-yellow'],
-  ['rgb(243,  253,   37)', 'Lemon yellow'],
-  ['rgb(121,    5,   35)', 'Dark crimson'],
-  ['rgb(169,   32,   62)', 'Deep carmine'],
-  ['rgb(255,  140,    0)', 'Orange'],
-  ['rgb(255,  168,   18)', 'Dark tangerine'],
-  ['rgb(217,  144,   88)', 'Persian orange'],
-  ['rgb(194,  178,  128)', 'Sand'],
-  ['rgb(255,  229,  180)', 'Peach'],
-  ['rgb(100,   54,   46)', 'Bole'],
-  ['rgb(108,   41,   52)', 'Dark cordovan'],
-  ['rgb(163,   65,   44)', 'Chestnut'],
-  ['rgb(228,  136,  100)', 'Dark salmon'],
-  ['rgb(255,  195,  143)', 'Apricot'],
-  ['rgb(255,  219,  188)', 'Unbleached silk'],
-  ['rgb(242,  227,  198)', 'Straw'],
-  ['rgb( 53,   19,   13)', 'Bistre'],
-  ['rgb( 84,   42,   14)', 'Dark chocolate'],
-  ['rgb(102,   51,   43)', 'Burnt sienna'],
-  ['rgb(184,   66,    0)', 'Sienna'],
-  ['rgb(216,  153,   12)', 'Yellow ochre'],
-  ['rgb(210,  180,  140)', 'Tan'],
-  ['rgb(232,  204,  144)', 'Dark wheat'],
-  ['rgb(  0,   49,   83)', 'Prussian blue'],
-  ['rgb( 48,   69,  119)', 'Dark grey blue'],
-  ['rgb(  0,   71,  171)', 'Cobalt blue'],
-  ['rgb( 31,  117,  254)', 'Blue'],
-  ['rgb(120,  180,  255)', 'Bright french blue'],
-  ['rgb(171,  200,  255)', 'Bright steel blue'],
-  ['rgb(208,  231,  255)', 'Ice blue'],
-  ['rgb( 30,   51,   58)', 'Medium jungle green'],
-  ['rgb( 47,   79,   79)', 'Dark slate grey'],
-  ['rgb( 74,  104,   93)', 'Dark grullo green'],
-  ['rgb(  0,  128,  128)', 'Teal'],
-  ['rgb( 67,  170,  176)', 'Turquoise'],
-  ['rgb(109,  174,  199)', 'Cerulean frost'],
-  ['rgb(173,  217,  186)', 'Tiffany green'],
-  ['rgb( 22,   34,   29)', 'Gray-asparagus'],
-  ['rgb( 36,   48,   45)', 'Medium dark teal'],
-  ['rgb( 74,  104,   93)', 'Xanadu'],
-  ['rgb(119,  198,  121)', 'Mint'],
-  ['rgb(175,  205,  182)', 'Timberwolf'],
-  ['rgb(185,  245,  246)', 'Celeste'],
-  ['rgb(193,  255,  234)', 'Aquamarine'],
-  ['rgb( 29,   52,   35)', 'Cal Poly Pomona'],
-  ['rgb(  1,   68,   33)', 'Forest green'],
-  ['rgb( 42,  128,    0)', 'Napier green'],
-  ['rgb(128,  128,    0)', 'Olive'],
-  ['rgb( 65,  156,  105)', 'Sea green'],
-  ['rgb(189,  246,   29)', 'Green-yellow'],
-  ['rgb(231,  244,  134)', 'Bright chartreuse'],
-  ['rgb(138,   23,  137)', 'Purple'],
-  ['rgb( 78,   39,  138)', 'Violet'],
-  ['rgb(193,   75,  110)', 'Dark thulian pink'],
-  ['rgb(222,   49,   99)', 'Cerise'],
-  ['rgb(255,   20,  147)', 'Deep pink'],
-  ['rgb(255,  102,  204)', 'Rose pink'],
-  ['rgb(255,  203,  219)', 'Pink'],
-  ['rgb(255,  255,  255)', 'White'],
-  ['rgb(229,   17,    1)', 'RGB Red'],
-  ['rgb(  0,  255,    0)', 'RGB Green'],
-  ['rgb(  0,    0,  255)', 'RGB Blue'],
-  ['rgb(  0,  255,  255)', 'CMYK Cyan'],
-  ['rgb(255,    0,  255)', 'CMYK Magenta'],
-  ['rgb(255,  255,    0)', 'CMYK Yellow'],
-];
-
-// re-arrange to the right order for display
-let palReordered = [];
-for (let row = 0; row < 7; row++) {
-  for (let col = 0; col < 11; col++) {
-    palReordered.push(palette[col * 7 + row]);
-  }
-  palReordered.push(null); // null indicates a <br />
-}
-
-// Utility for converting base64 image to binary for upload
-// https://stackoverflow.com/questions/35940290/how-to-convert-base64-string-to-javascript-file-object-like-as-from-file-input-f
-function dataURLtoFile(dataurl, filename) {
-  let arr = dataurl.split(','), mime = arr[0].match(/:(.*?);/)[1],
-    bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n);
-  while(n--){
-    u8arr[n] = bstr.charCodeAt(n);
-  }
-  return new File([u8arr], filename, { type: mime });
-}
-
-const DOODLE_SIZES = {
-  normal: [500, 500, 'Square 500'],
-  tootbanner: [702, 330, 'Tootbanner'],
-  s640x480: [640, 480, '640×480 - 480p'],
-  s800x600: [800, 600, '800×600 - SVGA'],
-  s720x480: [720, 405, '720x405 - 16:9'],
-};
-
-
-const mapStateToProps = state => ({
-  options: state.getIn(['compose', 'doodle']),
-});
-
-const mapDispatchToProps = dispatch => ({
-  /** Set options in the redux store */
-  setOpt: (opts) => dispatch(doodleSet(opts)),
-  /** Submit doodle for upload */
-  submit: (file) => dispatch(uploadCompose([file])),
-});
-
-/**
- * Doodling dialog with drawing canvas
- *
- * Keyboard shortcuts:
- * - Delete: Clear screen, fill with background color
- * - Backspace, Ctrl+Z: Undo one step
- * - Ctrl held while drawing: Use background color
- * - Shift held while clicking screen: Use fill tool
- *
- * Palette:
- * - Left mouse button: pick foreground
- * - Ctrl + left mouse button: pick background
- * - Right mouse button: pick background
- */
-@connect(mapStateToProps, mapDispatchToProps)
-export default class DoodleModal extends ImmutablePureComponent {
-
-  static propTypes = {
-    options: ImmutablePropTypes.map,
-    onClose: PropTypes.func.isRequired,
-    setOpt: PropTypes.func.isRequired,
-    submit: PropTypes.func.isRequired,
-  };
-
-  //region Option getters/setters
-
-  /** Foreground color */
-  get fg () {
-    return this.props.options.get('fg');
-  }
-  set fg (value) {
-    this.props.setOpt({ fg: value });
-  }
-
-  /** Background color */
-  get bg () {
-    return this.props.options.get('bg');
-  }
-  set bg (value) {
-    this.props.setOpt({ bg: value });
-  }
-
-  /** Swap Fg and Bg for drawing */
-  get swapped () {
-    return this.props.options.get('swapped');
-  }
-  set swapped (value) {
-    this.props.setOpt({ swapped: value });
-  }
-
-  /** Mode - 'draw' or 'fill' */
-  get mode () {
-    return this.props.options.get('mode');
-  }
-  set mode (value) {
-    this.props.setOpt({ mode: value });
-  }
-
-  /** Base line weight */
-  get weight () {
-    return this.props.options.get('weight');
-  }
-  set weight (value) {
-    this.props.setOpt({ weight: value });
-  }
-
-  /** Drawing opacity */
-  get opacity () {
-    return this.props.options.get('opacity');
-  }
-  set opacity (value) {
-    this.props.setOpt({ opacity: value });
-  }
-
-  /** Adaptive stroke - change width with speed */
-  get adaptiveStroke () {
-    return this.props.options.get('adaptiveStroke');
-  }
-  set adaptiveStroke (value) {
-    this.props.setOpt({ adaptiveStroke: value });
-  }
-
-  /** Smoothing (for mouse drawing) */
-  get smoothing () {
-    return this.props.options.get('smoothing');
-  }
-  set smoothing (value) {
-    this.props.setOpt({ smoothing: value });
-  }
-
-  /** Size preset */
-  get size () {
-    return this.props.options.get('size');
-  }
-  set size (value) {
-    this.props.setOpt({ size: value });
-  }
-
-  //endregion
-
-  /** Key up handler */
-  handleKeyUp = (e) => {
-    if (e.target.nodeName === 'INPUT') return;
-
-    if (e.key === 'Delete') {
-      e.preventDefault();
-      this.handleClearBtn();
-      return;
-    }
-
-    if (e.key === 'Backspace' || (e.key === 'z' && (e.ctrlKey || e.metaKey))) {
-      e.preventDefault();
-      this.undo();
-    }
-
-    if (e.key === 'Control' || e.key === 'Meta') {
-      this.controlHeld = false;
-      this.swapped = false;
-    }
-
-    if (e.key === 'Shift') {
-      this.shiftHeld = false;
-      this.mode = 'draw';
-    }
-  };
-
-  /** Key down handler */
-  handleKeyDown = (e) => {
-    if (e.key === 'Control' || e.key === 'Meta') {
-      this.controlHeld = true;
-      this.swapped = true;
-    }
-
-    if (e.key === 'Shift') {
-      this.shiftHeld = true;
-      this.mode = 'fill';
-    }
-  };
-
-  /**
-   * Component installed in the DOM, do some initial set-up
-   */
-  componentDidMount () {
-    this.controlHeld = false;
-    this.shiftHeld = false;
-    this.swapped = false;
-    window.addEventListener('keyup', this.handleKeyUp, false);
-    window.addEventListener('keydown', this.handleKeyDown, false);
-  };
-
-  /**
-   * Tear component down
-   */
-  componentWillUnmount () {
-    window.removeEventListener('keyup', this.handleKeyUp, false);
-    window.removeEventListener('keydown', this.handleKeyDown, false);
-    if (this.sketcher) this.sketcher.destroy();
-  }
-
-  /**
-   * Set reference to the canvas element.
-   * This is called during component init
-   *
-   * @param elem - canvas element
-   */
-  setCanvasRef = (elem) => {
-    this.canvas = elem;
-    if (elem) {
-      elem.addEventListener('dirty', () => {
-        this.saveUndo();
-        this.sketcher._dirty = false;
-      });
-
-      elem.addEventListener('click', () => {
-        // sketcher bug - does not fire dirty on fill
-        if (this.mode === 'fill') {
-          this.saveUndo();
-        }
-      });
-
-      // prevent context menu
-      elem.addEventListener('contextmenu', (e) => {
-        e.preventDefault();
-      });
-
-      elem.addEventListener('mousedown', (e) => {
-        if (e.button === 2) {
-          this.swapped = true;
-        }
-      });
-
-      elem.addEventListener('mouseup', (e) => {
-        if (e.button === 2) {
-          this.swapped = this.controlHeld;
-        }
-      });
-
-      this.initSketcher(elem);
-      this.mode = 'draw'; // Reset mode - it's confusing if left at 'fill'
-    }
-  };
-
-  /**
-   * Set up the sketcher instance
-   *
-   * @param canvas - canvas element. Null if we're just resizing
-   */
-  initSketcher (canvas = null) {
-    const sizepreset = DOODLE_SIZES[this.size];
-
-    if (this.sketcher) this.sketcher.destroy();
-    this.sketcher = new Atrament(canvas || this.canvas, sizepreset[0], sizepreset[1]);
-
-    if (canvas) {
-      this.ctx = this.sketcher.context;
-      this.updateSketcherSettings();
-    }
-
-    this.clearScreen();
-  }
-
-  /**
-   * Done button handler
-   */
-  onDoneButton = () => {
-    const dataUrl = this.sketcher.toImage();
-    const file = dataURLtoFile(dataUrl, 'doodle.png');
-    this.props.submit(file);
-    this.props.onClose(); // close dialog
-  };
-
-  /**
-   * Cancel button handler
-   */
-  onCancelButton = () => {
-    if (this.undos.length > 1 && !confirm('Discard doodle? All changes will be lost!')) {
-      return;
-    }
-
-    this.props.onClose(); // close dialog
-  };
-
-  /**
-   * Update sketcher options based on state
-   */
-  updateSketcherSettings () {
-    if (!this.sketcher) return;
-
-    if (this.oldSize !== this.size) this.initSketcher();
-
-    this.sketcher.color = (this.swapped ? this.bg : this.fg);
-    this.sketcher.opacity = this.opacity;
-    this.sketcher.weight = this.weight;
-    this.sketcher.mode = this.mode;
-    this.sketcher.smoothing = this.smoothing;
-    this.sketcher.adaptiveStroke = this.adaptiveStroke;
-
-    this.oldSize = this.size;
-  }
-
-  /**
-   * Fill screen with background color
-   */
-  clearScreen = () => {
-    this.ctx.fillStyle = this.bg;
-    this.ctx.fillRect(-1, -1, this.canvas.width+2, this.canvas.height+2);
-    this.undos = [];
-
-    this.doSaveUndo();
-  };
-
-  /**
-   * Undo one step
-   */
-  undo = () => {
-    if (this.undos.length > 1) {
-      this.undos.pop();
-      const buf = this.undos.pop();
-
-      this.sketcher.clear();
-      this.ctx.putImageData(buf, 0, 0);
-      this.doSaveUndo();
-    }
-  };
-
-  /**
-   * Save canvas content into the undo buffer immediately
-   */
-  doSaveUndo = () => {
-    this.undos.push(this.ctx.getImageData(0, 0, this.canvas.width, this.canvas.height));
-  };
-
-  /**
-   * Called on each canvas change.
-   * Saves canvas content to the undo buffer after some period of inactivity.
-   */
-  saveUndo = debounce(() => {
-    this.doSaveUndo();
-  }, 100);
-
-  /**
-   * Palette left click.
-   * Selects Fg color (or Bg, if Control/Meta is held)
-   *
-   * @param e - event
-   */
-  onPaletteClick = (e) => {
-    const c = e.target.dataset.color;
-
-    if (this.controlHeld) {
-      this.bg = c;
-    } else {
-      this.fg = c;
-    }
-
-    e.target.blur();
-    e.preventDefault();
-  };
-
-  /**
-   * Palette right click.
-   * Selects Bg color
-   *
-   * @param e - event
-   */
-  onPaletteRClick = (e) => {
-    this.bg = e.target.dataset.color;
-    e.target.blur();
-    e.preventDefault();
-  };
-
-  /**
-   * Handle click on the Draw mode button
-   *
-   * @param e - event
-   */
-  setModeDraw = (e) => {
-    this.mode = 'draw';
-    e.target.blur();
-  };
-
-  /**
-   * Handle click on the Fill mode button
-   *
-   * @param e - event
-   */
-  setModeFill = (e) => {
-    this.mode = 'fill';
-    e.target.blur();
-  };
-
-  /**
-   * Handle click on Smooth checkbox
-   *
-   * @param e - event
-   */
-  tglSmooth = (e) => {
-    this.smoothing = !this.smoothing;
-    e.target.blur();
-  };
-
-  /**
-   * Handle click on Adaptive checkbox
-   *
-   * @param e - event
-   */
-  tglAdaptive = (e) => {
-    this.adaptiveStroke = !this.adaptiveStroke;
-    e.target.blur();
-  };
-
-  /**
-   * Handle change of the Weight input field
-   *
-   * @param e - event
-   */
-  setWeight = (e) => {
-    this.weight = +e.target.value || 1;
-  };
-
-  /**
-   * Set size - clalback from the select box
-   *
-   * @param e - event
-   */
-  changeSize = (e) => {
-    let newSize = e.target.value;
-    if (newSize === this.oldSize) return;
-
-    if (this.undos.length > 1 && !confirm('Change size? This will erase your drawing!')) {
-      return;
-    }
-
-    this.size = newSize;
-  };
-
-  handleClearBtn = () => {
-    if (this.undos.length > 1 && !confirm('Clear screen? This will erase your drawing!')) {
-      return;
-    }
-
-    this.clearScreen();
-  };
-
-  /**
-   * Render the component
-   */
-  render () {
-    this.updateSketcherSettings();
-
-    return (
-      <div className='modal-root__modal doodle-modal'>
-        <div className='doodle-modal__container'>
-          <canvas ref={this.setCanvasRef} />
-        </div>
-
-        <div className='doodle-modal__action-bar'>
-          <div className='doodle-toolbar'>
-            <Button text='Done' onClick={this.onDoneButton} />
-            <Button text='Cancel' onClick={this.onCancelButton} />
-          </div>
-          <div className='filler' />
-          <div className='doodle-toolbar with-inputs'>
-            <div>
-              <label htmlFor='dd_smoothing'>Smoothing</label>
-              <span className='val'>
-                <input type='checkbox' id='dd_smoothing' onChange={this.tglSmooth} checked={this.smoothing} />
-              </span>
-            </div>
-            <div>
-              <label htmlFor='dd_adaptive'>Adaptive</label>
-              <span className='val'>
-                <input type='checkbox' id='dd_adaptive' onChange={this.tglAdaptive} checked={this.adaptiveStroke} />
-              </span>
-            </div>
-            <div>
-              <label htmlFor='dd_weight'>Weight</label>
-              <span className='val'>
-                <input type='number' min={1} id='dd_weight' value={this.weight} onChange={this.setWeight} />
-              </span>
-            </div>
-            <div>
-              <select aria-label='Canvas size' onInput={this.changeSize} defaultValue={this.size}>
-                { Object.values(mapValues(DOODLE_SIZES, (val, k) =>
-                  <option key={k} value={k}>{val[2]}</option>
-                )) }
-              </select>
-            </div>
-          </div>
-          <div className='doodle-toolbar'>
-            <IconButton icon='pencil' title='Draw' label='Draw' onClick={this.setModeDraw} size={18} active={this.mode === 'draw'} inverted />
-            <IconButton icon='bath' title='Fill' label='Fill' onClick={this.setModeFill} size={18} active={this.mode === 'fill'} inverted />
-            <IconButton icon='undo' title='Undo' label='Undo' onClick={this.undo} size={18} inverted />
-            <IconButton icon='trash' title='Clear' label='Clear' onClick={this.handleClearBtn} size={18} inverted />
-          </div>
-          <div className='doodle-palette'>
-            {
-              palReordered.map((c, i) =>
-                c === null ?
-                  <br key={i} /> :
-                  <button
-                    key={i}
-                    style={{ backgroundColor: c[0] }}
-                    onClick={this.onPaletteClick}
-                    onContextMenu={this.onPaletteRClick}
-                    data-color={c[0]}
-                    title={c[1]}
-                    className={classNames({
-                      'foreground': this.fg === c[0],
-                      'background': this.bg === c[0],
-                    })}
-                  />
-              )
-            }
-          </div>
-        </div>
-      </div>
-    );
-  }
-
-}
diff --git a/app/javascript/themes/glitch/features/ui/components/drawer_loading.js b/app/javascript/themes/glitch/features/ui/components/drawer_loading.js
deleted file mode 100644
index 08b0d2347..000000000
--- a/app/javascript/themes/glitch/features/ui/components/drawer_loading.js
+++ /dev/null
@@ -1,11 +0,0 @@
-import React from 'react';
-
-const DrawerLoading = () => (
-  <div className='drawer'>
-    <div className='drawer__pager'>
-      <div className='drawer__inner' />
-    </div>
-  </div>
-);
-
-export default DrawerLoading;
diff --git a/app/javascript/themes/glitch/features/ui/components/embed_modal.js b/app/javascript/themes/glitch/features/ui/components/embed_modal.js
deleted file mode 100644
index 1afffb51b..000000000
--- a/app/javascript/themes/glitch/features/ui/components/embed_modal.js
+++ /dev/null
@@ -1,84 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import ImmutablePureComponent from 'react-immutable-pure-component';
-import { FormattedMessage, injectIntl } from 'react-intl';
-import axios from 'axios';
-
-@injectIntl
-export default class EmbedModal extends ImmutablePureComponent {
-
-  static propTypes = {
-    url: PropTypes.string.isRequired,
-    onClose: PropTypes.func.isRequired,
-    intl: PropTypes.object.isRequired,
-  }
-
-  state = {
-    loading: false,
-    oembed: null,
-  };
-
-  componentDidMount () {
-    const { url } = this.props;
-
-    this.setState({ loading: true });
-
-    axios.post('/api/web/embed', { url }).then(res => {
-      this.setState({ loading: false, oembed: res.data });
-
-      const iframeDocument = this.iframe.contentWindow.document;
-
-      iframeDocument.open();
-      iframeDocument.write(res.data.html);
-      iframeDocument.close();
-
-      iframeDocument.body.style.margin = 0;
-      this.iframe.width  = iframeDocument.body.scrollWidth;
-      this.iframe.height = iframeDocument.body.scrollHeight;
-    });
-  }
-
-  setIframeRef = c =>  {
-    this.iframe = c;
-  }
-
-  handleTextareaClick = (e) => {
-    e.target.select();
-  }
-
-  render () {
-    const { oembed } = this.state;
-
-    return (
-      <div className='modal-root__modal embed-modal'>
-        <h4><FormattedMessage id='status.embed' defaultMessage='Embed' /></h4>
-
-        <div className='embed-modal__container'>
-          <p className='hint'>
-            <FormattedMessage id='embed.instructions' defaultMessage='Embed this status on your website by copying the code below.' />
-          </p>
-
-          <input
-            type='text'
-            className='embed-modal__html'
-            readOnly
-            value={oembed && oembed.html || ''}
-            onClick={this.handleTextareaClick}
-          />
-
-          <p className='hint'>
-            <FormattedMessage id='embed.preview' defaultMessage='Here is what it will look like:' />
-          </p>
-
-          <iframe
-            className='embed-modal__iframe'
-            frameBorder='0'
-            ref={this.setIframeRef}
-            title='preview'
-          />
-        </div>
-      </div>
-    );
-  }
-
-}
diff --git a/app/javascript/themes/glitch/features/ui/components/image_loader.js b/app/javascript/themes/glitch/features/ui/components/image_loader.js
deleted file mode 100644
index aad594380..000000000
--- a/app/javascript/themes/glitch/features/ui/components/image_loader.js
+++ /dev/null
@@ -1,152 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import classNames from 'classnames';
-
-export default class ImageLoader extends React.PureComponent {
-
-  static propTypes = {
-    alt: PropTypes.string,
-    src: PropTypes.string.isRequired,
-    previewSrc: PropTypes.string.isRequired,
-    width: PropTypes.number,
-    height: PropTypes.number,
-  }
-
-  static defaultProps = {
-    alt: '',
-    width: null,
-    height: null,
-  };
-
-  state = {
-    loading: true,
-    error: false,
-  }
-
-  removers = [];
-
-  get canvasContext() {
-    if (!this.canvas) {
-      return null;
-    }
-    this._canvasContext = this._canvasContext || this.canvas.getContext('2d');
-    return this._canvasContext;
-  }
-
-  componentDidMount () {
-    this.loadImage(this.props);
-  }
-
-  componentWillReceiveProps (nextProps) {
-    if (this.props.src !== nextProps.src) {
-      this.loadImage(nextProps);
-    }
-  }
-
-  loadImage (props) {
-    this.removeEventListeners();
-    this.setState({ loading: true, error: false });
-    Promise.all([
-      this.loadPreviewCanvas(props),
-      this.hasSize() && this.loadOriginalImage(props),
-    ].filter(Boolean))
-      .then(() => {
-        this.setState({ loading: false, error: false });
-        this.clearPreviewCanvas();
-      })
-      .catch(() => this.setState({ loading: false, error: true }));
-  }
-
-  loadPreviewCanvas = ({ previewSrc, width, height }) => new Promise((resolve, reject) => {
-    const image = new Image();
-    const removeEventListeners = () => {
-      image.removeEventListener('error', handleError);
-      image.removeEventListener('load', handleLoad);
-    };
-    const handleError = () => {
-      removeEventListeners();
-      reject();
-    };
-    const handleLoad = () => {
-      removeEventListeners();
-      this.canvasContext.drawImage(image, 0, 0, width, height);
-      resolve();
-    };
-    image.addEventListener('error', handleError);
-    image.addEventListener('load', handleLoad);
-    image.src = previewSrc;
-    this.removers.push(removeEventListeners);
-  })
-
-  clearPreviewCanvas () {
-    const { width, height } = this.canvas;
-    this.canvasContext.clearRect(0, 0, width, height);
-  }
-
-  loadOriginalImage = ({ src }) => new Promise((resolve, reject) => {
-    const image = new Image();
-    const removeEventListeners = () => {
-      image.removeEventListener('error', handleError);
-      image.removeEventListener('load', handleLoad);
-    };
-    const handleError = () => {
-      removeEventListeners();
-      reject();
-    };
-    const handleLoad = () => {
-      removeEventListeners();
-      resolve();
-    };
-    image.addEventListener('error', handleError);
-    image.addEventListener('load', handleLoad);
-    image.src = src;
-    this.removers.push(removeEventListeners);
-  });
-
-  removeEventListeners () {
-    this.removers.forEach(listeners => listeners());
-    this.removers = [];
-  }
-
-  hasSize () {
-    const { width, height } = this.props;
-    return typeof width === 'number' && typeof height === 'number';
-  }
-
-  setCanvasRef = c => {
-    this.canvas = c;
-  }
-
-  render () {
-    const { alt, src, width, height } = this.props;
-    const { loading } = this.state;
-
-    const className = classNames('image-loader', {
-      'image-loader--loading': loading,
-      'image-loader--amorphous': !this.hasSize(),
-    });
-
-    return (
-      <div className={className}>
-        <canvas
-          className='image-loader__preview-canvas'
-          width={width}
-          height={height}
-          ref={this.setCanvasRef}
-          style={{ opacity: loading ? 1 : 0 }}
-        />
-
-        {!loading && (
-          <img
-            alt={alt}
-            className='image-loader__img'
-            src={src}
-            width={width}
-            height={height}
-          />
-        )}
-      </div>
-    );
-  }
-
-}
diff --git a/app/javascript/themes/glitch/features/ui/components/media_modal.js b/app/javascript/themes/glitch/features/ui/components/media_modal.js
deleted file mode 100644
index 1dad972b2..000000000
--- a/app/javascript/themes/glitch/features/ui/components/media_modal.js
+++ /dev/null
@@ -1,126 +0,0 @@
-import React from 'react';
-import ReactSwipeableViews from 'react-swipeable-views';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import PropTypes from 'prop-types';
-import ExtendedVideoPlayer from 'themes/glitch/components/extended_video_player';
-import { defineMessages, injectIntl } from 'react-intl';
-import IconButton from 'themes/glitch/components/icon_button';
-import ImmutablePureComponent from 'react-immutable-pure-component';
-import ImageLoader from './image_loader';
-
-const messages = defineMessages({
-  close: { id: 'lightbox.close', defaultMessage: 'Close' },
-  previous: { id: 'lightbox.previous', defaultMessage: 'Previous' },
-  next: { id: 'lightbox.next', defaultMessage: 'Next' },
-});
-
-@injectIntl
-export default class MediaModal extends ImmutablePureComponent {
-
-  static propTypes = {
-    media: ImmutablePropTypes.list.isRequired,
-    index: PropTypes.number.isRequired,
-    onClose: PropTypes.func.isRequired,
-    intl: PropTypes.object.isRequired,
-  };
-
-  state = {
-    index: null,
-  };
-
-  handleSwipe = (index) => {
-    this.setState({ index: index % this.props.media.size });
-  }
-
-  handleNextClick = () => {
-    this.setState({ index: (this.getIndex() + 1) % this.props.media.size });
-  }
-
-  handlePrevClick = () => {
-    this.setState({ index: (this.props.media.size + this.getIndex() - 1) % this.props.media.size });
-  }
-
-  handleChangeIndex = (e) => {
-    const index = Number(e.currentTarget.getAttribute('data-index'));
-    this.setState({ index: index % this.props.media.size });
-  }
-
-  handleKeyUp = (e) => {
-    switch(e.key) {
-    case 'ArrowLeft':
-      this.handlePrevClick();
-      break;
-    case 'ArrowRight':
-      this.handleNextClick();
-      break;
-    }
-  }
-
-  componentDidMount () {
-    window.addEventListener('keyup', this.handleKeyUp, false);
-  }
-
-  componentWillUnmount () {
-    window.removeEventListener('keyup', this.handleKeyUp);
-  }
-
-  getIndex () {
-    return this.state.index !== null ? this.state.index : this.props.index;
-  }
-
-  render () {
-    const { media, intl, onClose } = this.props;
-
-    const index = this.getIndex();
-    let pagination = [];
-
-    const leftNav  = media.size > 1 && <button tabIndex='0' className='modal-container__nav modal-container__nav--left' onClick={this.handlePrevClick} aria-label={intl.formatMessage(messages.previous)}><i className='fa fa-fw fa-chevron-left' /></button>;
-    const rightNav = media.size > 1 && <button tabIndex='0' className='modal-container__nav  modal-container__nav--right' onClick={this.handleNextClick} aria-label={intl.formatMessage(messages.next)}><i className='fa fa-fw fa-chevron-right' /></button>;
-
-    if (media.size > 1) {
-      pagination = media.map((item, i) => {
-        const classes = ['media-modal__button'];
-        if (i === index) {
-          classes.push('media-modal__button--active');
-        }
-        return (<li className='media-modal__page-dot' key={i}><button tabIndex='0' className={classes.join(' ')} onClick={this.handleChangeIndex} data-index={i}>{i + 1}</button></li>);
-      });
-    }
-
-    const content = media.map((image) => {
-      const width  = image.getIn(['meta', 'original', 'width']) || null;
-      const height = image.getIn(['meta', 'original', 'height']) || null;
-
-      if (image.get('type') === 'image') {
-        return <ImageLoader previewSrc={image.get('preview_url')} src={image.get('url')} width={width} height={height} alt={image.get('description')} key={image.get('preview_url')} />;
-      } else if (image.get('type') === 'gifv') {
-        return <ExtendedVideoPlayer src={image.get('url')} muted controls={false} width={width} height={height} key={image.get('preview_url')} alt={image.get('description')} />;
-      }
-
-      return null;
-    }).toArray();
-
-    const containerStyle = {
-      alignItems: 'center', // center vertically
-    };
-
-    return (
-      <div className='modal-root__modal media-modal'>
-        {leftNav}
-
-        <div className='media-modal__content'>
-          <IconButton className='media-modal__close' title={intl.formatMessage(messages.close)} icon='times' onClick={onClose} size={16} />
-          <ReactSwipeableViews containerStyle={containerStyle} onChangeIndex={this.handleSwipe} index={index}>
-            {content}
-          </ReactSwipeableViews>
-        </div>
-        <ul className='media-modal__pagination'>
-          {pagination}
-        </ul>
-
-        {rightNav}
-      </div>
-    );
-  }
-
-}
diff --git a/app/javascript/themes/glitch/features/ui/components/modal_loading.js b/app/javascript/themes/glitch/features/ui/components/modal_loading.js
deleted file mode 100644
index e14d20fbb..000000000
--- a/app/javascript/themes/glitch/features/ui/components/modal_loading.js
+++ /dev/null
@@ -1,20 +0,0 @@
-import React from 'react';
-
-import LoadingIndicator from 'themes/glitch/components/loading_indicator';
-
-// Keep the markup in sync with <BundleModalError />
-// (make sure they have the same dimensions)
-const ModalLoading = () => (
-  <div className='modal-root__modal error-modal'>
-    <div className='error-modal__body'>
-      <LoadingIndicator />
-    </div>
-    <div className='error-modal__footer'>
-      <div>
-        <button className='error-modal__nav onboarding-modal__skip' />
-      </div>
-    </div>
-  </div>
-);
-
-export default ModalLoading;
diff --git a/app/javascript/themes/glitch/features/ui/components/modal_root.js b/app/javascript/themes/glitch/features/ui/components/modal_root.js
deleted file mode 100644
index fbe794170..000000000
--- a/app/javascript/themes/glitch/features/ui/components/modal_root.js
+++ /dev/null
@@ -1,131 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import BundleContainer from '../containers/bundle_container';
-import BundleModalError from './bundle_modal_error';
-import ModalLoading from './modal_loading';
-import ActionsModal from './actions_modal';
-import MediaModal from './media_modal';
-import VideoModal from './video_modal';
-import BoostModal from './boost_modal';
-import DoodleModal from './doodle_modal';
-import ConfirmationModal from './confirmation_modal';
-import {
-  OnboardingModal,
-  MuteModal,
-  ReportModal,
-  SettingsModal,
-  EmbedModal,
-} from 'themes/glitch/util/async-components';
-
-const MODAL_COMPONENTS = {
-  'MEDIA': () => Promise.resolve({ default: MediaModal }),
-  'ONBOARDING': OnboardingModal,
-  'VIDEO': () => Promise.resolve({ default: VideoModal }),
-  'BOOST': () => Promise.resolve({ default: BoostModal }),
-  'DOODLE': () => Promise.resolve({ default: DoodleModal }),
-  'CONFIRM': () => Promise.resolve({ default: ConfirmationModal }),
-  'MUTE': MuteModal,
-  'REPORT': ReportModal,
-  'SETTINGS': SettingsModal,
-  'ACTIONS': () => Promise.resolve({ default: ActionsModal }),
-  'EMBED': EmbedModal,
-};
-
-export default class ModalRoot extends React.PureComponent {
-
-  static propTypes = {
-    type: PropTypes.string,
-    props: PropTypes.object,
-    onClose: PropTypes.func.isRequired,
-  };
-
-  state = {
-    revealed: false,
-  };
-
-  handleKeyUp = (e) => {
-    if ((e.key === 'Escape' || e.key === 'Esc' || e.keyCode === 27)
-         && !!this.props.type && !this.props.props.noEsc) {
-      this.props.onClose();
-    }
-  }
-
-  componentDidMount () {
-    window.addEventListener('keyup', this.handleKeyUp, false);
-  }
-
-  componentWillReceiveProps (nextProps) {
-    if (!!nextProps.type && !this.props.type) {
-      this.activeElement = document.activeElement;
-
-      this.getSiblings().forEach(sibling => sibling.setAttribute('inert', true));
-    } else if (!nextProps.type) {
-      this.setState({ revealed: false });
-    }
-  }
-
-  componentDidUpdate (prevProps) {
-    if (!this.props.type && !!prevProps.type) {
-      this.getSiblings().forEach(sibling => sibling.removeAttribute('inert'));
-      this.activeElement.focus();
-      this.activeElement = null;
-    }
-    if (this.props.type) {
-      requestAnimationFrame(() => {
-        this.setState({ revealed: true });
-      });
-    }
-  }
-
-  componentWillUnmount () {
-    window.removeEventListener('keyup', this.handleKeyUp);
-  }
-
-  getSiblings = () => {
-    return Array(...this.node.parentElement.childNodes).filter(node => node !== this.node);
-  }
-
-  setRef = ref => {
-    this.node = ref;
-  }
-
-  renderLoading = modalId => () => {
-    return ['MEDIA', 'VIDEO', 'BOOST', 'DOODLE', 'CONFIRM', 'ACTIONS'].indexOf(modalId) === -1 ? <ModalLoading /> : null;
-  }
-
-  renderError = (props) => {
-    const { onClose } = this.props;
-
-    return <BundleModalError {...props} onClose={onClose} />;
-  }
-
-  render () {
-    const { type, props, onClose } = this.props;
-    const { revealed } = this.state;
-    const visible = !!type;
-
-    if (!visible) {
-      return (
-        <div className='modal-root' ref={this.setRef} style={{ opacity: 0 }} />
-      );
-    }
-
-    return (
-      <div className='modal-root' ref={this.setRef} style={{ opacity: revealed ? 1 : 0 }}>
-        <div style={{ pointerEvents: visible ? 'auto' : 'none' }}>
-          <div role='presentation' className='modal-root__overlay' onClick={onClose} />
-          <div role='dialog' className='modal-root__container'>
-            {
-              visible ?
-                (<BundleContainer fetchComponent={MODAL_COMPONENTS[type]} loading={this.renderLoading(type)} error={this.renderError} renderDelay={200}>
-                  {(SpecificComponent) => <SpecificComponent {...props} onClose={onClose} />}
-                </BundleContainer>) :
-              null
-            }
-          </div>
-        </div>
-      </div>
-    );
-  }
-
-}
diff --git a/app/javascript/themes/glitch/features/ui/components/mute_modal.js b/app/javascript/themes/glitch/features/ui/components/mute_modal.js
deleted file mode 100644
index ffccdc84d..000000000
--- a/app/javascript/themes/glitch/features/ui/components/mute_modal.js
+++ /dev/null
@@ -1,105 +0,0 @@
-import React from 'react';
-import { connect } from 'react-redux';
-import PropTypes from 'prop-types';
-import { injectIntl, FormattedMessage } from 'react-intl';
-import Toggle from 'react-toggle';
-import Button from 'themes/glitch/components/button';
-import { closeModal } from 'themes/glitch/actions/modal';
-import { muteAccount } from 'themes/glitch/actions/accounts';
-import { toggleHideNotifications } from 'themes/glitch/actions/mutes';
-
-
-const mapStateToProps = state => {
-  return {
-    isSubmitting: state.getIn(['reports', 'new', 'isSubmitting']),
-    account: state.getIn(['mutes', 'new', 'account']),
-    notifications: state.getIn(['mutes', 'new', 'notifications']),
-  };
-};
-
-const mapDispatchToProps = dispatch => {
-  return {
-    onConfirm(account, notifications) {
-      dispatch(muteAccount(account.get('id'), notifications));
-    },
-
-    onClose() {
-      dispatch(closeModal());
-    },
-
-    onToggleNotifications() {
-      dispatch(toggleHideNotifications());
-    },
-  };
-};
-
-@connect(mapStateToProps, mapDispatchToProps)
-@injectIntl
-export default class MuteModal extends React.PureComponent {
-
-  static propTypes = {
-    isSubmitting: PropTypes.bool.isRequired,
-    account: PropTypes.object.isRequired,
-    notifications: PropTypes.bool.isRequired,
-    onClose: PropTypes.func.isRequired,
-    onConfirm: PropTypes.func.isRequired,
-    onToggleNotifications: PropTypes.func.isRequired,
-    intl: PropTypes.object.isRequired,
-  };
-
-  componentDidMount() {
-    this.button.focus();
-  }
-
-  handleClick = () => {
-    this.props.onClose();
-    this.props.onConfirm(this.props.account, this.props.notifications);
-  }
-
-  handleCancel = () => {
-    this.props.onClose();
-  }
-
-  setRef = (c) => {
-    this.button = c;
-  }
-
-  toggleNotifications = () => {
-    this.props.onToggleNotifications();
-  }
-
-  render () {
-    const { account, notifications } = this.props;
-
-    return (
-      <div className='modal-root__modal mute-modal'>
-        <div className='mute-modal__container'>
-          <p>
-            <FormattedMessage
-              id='confirmations.mute.message'
-              defaultMessage='Are you sure you want to mute {name}?'
-              values={{ name: <strong>@{account.get('acct')}</strong> }}
-            />
-          </p>
-          <div>
-            <label htmlFor='mute-modal__hide-notifications-checkbox'>
-              <FormattedMessage id='mute_modal.hide_notifications' defaultMessage='Hide notifications from this user?' />
-              {' '}
-              <Toggle id='mute-modal__hide-notifications-checkbox' checked={notifications} onChange={this.toggleNotifications} />
-            </label>
-          </div>
-        </div>
-
-        <div className='mute-modal__action-bar'>
-          <Button onClick={this.handleCancel} className='mute-modal__cancel-button'>
-            <FormattedMessage id='confirmation_modal.cancel' defaultMessage='Cancel' />
-          </Button>
-          <Button onClick={this.handleClick} ref={this.setRef}>
-            <FormattedMessage id='confirmations.mute.confirm' defaultMessage='Mute' />
-          </Button>
-        </div>
-      </div>
-    );
-  }
-
-}
diff --git a/app/javascript/themes/glitch/features/ui/components/onboarding_modal.js b/app/javascript/themes/glitch/features/ui/components/onboarding_modal.js
deleted file mode 100644
index 58875262e..000000000
--- a/app/javascript/themes/glitch/features/ui/components/onboarding_modal.js
+++ /dev/null
@@ -1,323 +0,0 @@
-import React from 'react';
-import { connect } from 'react-redux';
-import PropTypes from 'prop-types';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
-import ReactSwipeableViews from 'react-swipeable-views';
-import classNames from 'classnames';
-import Permalink from 'themes/glitch/components/permalink';
-import ComposeForm from 'themes/glitch/features/compose/components/compose_form';
-import Search from 'themes/glitch/features/compose/components/search';
-import NavigationBar from 'themes/glitch/features/compose/components/navigation_bar';
-import ColumnHeader from './column_header';
-import {
-  List as ImmutableList,
-  Map as ImmutableMap,
-} from 'immutable';
-import { me } from 'themes/glitch/util/initial_state';
-
-const noop = () => { };
-
-const messages = defineMessages({
-  home_title: { id: 'column.home', defaultMessage: 'Home' },
-  notifications_title: { id: 'column.notifications', defaultMessage: 'Notifications' },
-  local_title: { id: 'column.community', defaultMessage: 'Local timeline' },
-  federated_title: { id: 'column.public', defaultMessage: 'Federated timeline' },
-});
-
-const PageOne = ({ acct, domain }) => (
-  <div className='onboarding-modal__page onboarding-modal__page-one'>
-    <div style={{ flex: '0 0 auto' }}>
-      <div className='onboarding-modal__page-one__elephant-friend' />
-    </div>
-
-    <div>
-      <h1><FormattedMessage id='onboarding.page_one.welcome' defaultMessage='Welcome to {domain}!' values={{ domain }} /></h1>
-      <p><FormattedMessage id='onboarding.page_one.federation' defaultMessage='{domain} is an "instance" of Mastodon. Mastodon is a network of independent servers joining up to make one larger social network. We call these servers instances.' values={{ domain }} /></p>
-      <p><FormattedMessage id='onboarding.page_one.handle' defaultMessage='You are on {domain}, so your full handle is {handle}' values={{ domain, handle: <strong>@{acct}@{domain}</strong> }} /></p>
-    </div>
-  </div>
-);
-
-PageOne.propTypes = {
-  acct: PropTypes.string.isRequired,
-  domain: PropTypes.string.isRequired,
-};
-
-const PageTwo = ({ myAccount }) => (
-  <div className='onboarding-modal__page onboarding-modal__page-two'>
-    <div className='figure non-interactive'>
-      <div className='pseudo-drawer'>
-        <NavigationBar onClose={noop} account={myAccount} />
-      </div>
-      <ComposeForm
-        text='Awoo! #introductions'
-        suggestions={ImmutableList()}
-        mentionedDomains={[]}
-        spoiler={false}
-        onChange={noop}
-        onSubmit={noop}
-        onPaste={noop}
-        onPickEmoji={noop}
-        onChangeSpoilerText={noop}
-        onClearSuggestions={noop}
-        onFetchSuggestions={noop}
-        onSuggestionSelected={noop}
-        onPrivacyChange={noop}
-        showSearch
-        settings={ImmutableMap.of('side_arm', 'none')}
-      />
-    </div>
-
-    <p><FormattedMessage id='onboarding.page_two.compose' defaultMessage='Write posts from the compose column. You can upload images, change privacy settings, and add content warnings with the icons below.' /></p>
-  </div>
-);
-
-PageTwo.propTypes = {
-  myAccount: ImmutablePropTypes.map.isRequired,
-};
-
-const PageThree = ({ myAccount }) => (
-  <div className='onboarding-modal__page onboarding-modal__page-three'>
-    <div className='figure non-interactive'>
-      <Search
-        value=''
-        onChange={noop}
-        onSubmit={noop}
-        onClear={noop}
-        onShow={noop}
-      />
-
-      <div className='pseudo-drawer'>
-        <NavigationBar onClose={noop} account={myAccount} />
-      </div>
-    </div>
-
-    <p><FormattedMessage id='onboarding.page_three.search' defaultMessage='Use the search bar to find people and look at hashtags, such as {illustration} and {introductions}. To look for a person who is not on this instance, use their full handle.' values={{ illustration: <Permalink to='/timelines/tag/illustration' href='/tags/illustration'>#illustration</Permalink>, introductions: <Permalink to='/timelines/tag/introductions' href='/tags/introductions'>#introductions</Permalink> }} /></p>
-    <p><FormattedMessage id='onboarding.page_three.profile' defaultMessage='Edit your profile to change your avatar, bio, and display name. There, you will also find other preferences.' /></p>
-  </div>
-);
-
-PageThree.propTypes = {
-  myAccount: ImmutablePropTypes.map.isRequired,
-};
-
-const PageFour = ({ domain, intl }) => (
-  <div className='onboarding-modal__page onboarding-modal__page-four'>
-    <div className='onboarding-modal__page-four__columns'>
-      <div className='row'>
-        <div>
-          <div className='figure non-interactive'><ColumnHeader icon='home' type={intl.formatMessage(messages.home_title)} /></div>
-          <p><FormattedMessage id='onboarding.page_four.home' defaultMessage='The home timeline shows posts from people you follow.' /></p>
-        </div>
-
-        <div>
-          <div className='figure non-interactive'><ColumnHeader icon='bell' type={intl.formatMessage(messages.notifications_title)} /></div>
-          <p><FormattedMessage id='onboarding.page_four.notifications' defaultMessage='The notifications column shows when someone interacts with you.' /></p>
-        </div>
-      </div>
-
-      <div className='row'>
-        <div>
-          <div className='figure non-interactive' style={{ marginBottom: 0 }}><ColumnHeader icon='users' type={intl.formatMessage(messages.local_title)} /></div>
-        </div>
-
-        <div>
-          <div className='figure non-interactive' style={{ marginBottom: 0 }}><ColumnHeader icon='globe' type={intl.formatMessage(messages.federated_title)} /></div>
-        </div>
-      </div>
-
-      <p><FormattedMessage id='onboarding.page_five.public_timelines' defaultMessage='The local timeline shows public posts from everyone on {domain}. The federated timeline shows public posts from everyone who people on {domain} follow. These are the Public Timelines, a great way to discover new people.' values={{ domain }} /></p>
-    </div>
-  </div>
-);
-
-PageFour.propTypes = {
-  domain: PropTypes.string.isRequired,
-  intl: PropTypes.object.isRequired,
-};
-
-const PageSix = ({ admin, domain }) => {
-  let adminSection = '';
-
-  if (admin) {
-    adminSection = (
-      <p>
-        <FormattedMessage id='onboarding.page_six.admin' defaultMessage="Your instance's admin is {admin}." values={{ admin: <Permalink href={admin.get('url')} to={`/accounts/${admin.get('id')}`}>@{admin.get('acct')}</Permalink> }} />
-        <br />
-        <FormattedMessage id='onboarding.page_six.read_guidelines' defaultMessage="Please read {domain}'s {guidelines}!" values={{ domain, guidelines: <a href='/about/more' target='_blank'><FormattedMessage id='onboarding.page_six.guidelines' defaultMessage='community guidelines' /></a> }} />
-      </p>
-    );
-  }
-
-  return (
-    <div className='onboarding-modal__page onboarding-modal__page-six'>
-      <h1><FormattedMessage id='onboarding.page_six.almost_done' defaultMessage='Almost done...' /></h1>
-      {adminSection}
-      <p><FormattedMessage id='onboarding.page_six.github' defaultMessage='{domain} runs on Glitchsoc. Glitchsoc is a friendly {fork} of {Mastodon}. Glitchsoc is fully compatible with all Mastodon apps and instances. Glitchsoc is free open-source software. You can report bugs, request features, or contribute to the code on {github}.' values={{ domain, fork: <a href='https://en.wikipedia.org/wiki/Fork_(software_development)' target='_blank' rel='noopener'>fork</a>, Mastodon: <a href='https://github.com/tootsuite/mastodon' target='_blank' rel='noopener'>Mastodon</a>, github: <a href='https://github.com/glitch-soc/mastodon' target='_blank' rel='noopener'>GitHub</a> }} /></p>
-      <p><FormattedMessage id='onboarding.page_six.apps_available' defaultMessage='There are {apps} available for iOS, Android and other platforms.' values={{ domain, apps: <a href='https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/Apps.md' target='_blank' rel='noopener'><FormattedMessage id='onboarding.page_six.various_app' defaultMessage='mobile apps' /></a> }} /></p>
-      <p><em><FormattedMessage id='onboarding.page_six.appetoot' defaultMessage='Bon Appetoot!' /></em></p>
-    </div>
-  );
-};
-
-PageSix.propTypes = {
-  admin: ImmutablePropTypes.map,
-  domain: PropTypes.string.isRequired,
-};
-
-const mapStateToProps = state => ({
-  myAccount: state.getIn(['accounts', me]),
-  admin: state.getIn(['accounts', state.getIn(['meta', 'admin'])]),
-  domain: state.getIn(['meta', 'domain']),
-});
-
-@connect(mapStateToProps)
-@injectIntl
-export default class OnboardingModal extends React.PureComponent {
-
-  static propTypes = {
-    onClose: PropTypes.func.isRequired,
-    intl: PropTypes.object.isRequired,
-    myAccount: ImmutablePropTypes.map.isRequired,
-    domain: PropTypes.string.isRequired,
-    admin: ImmutablePropTypes.map,
-  };
-
-  state = {
-    currentIndex: 0,
-  };
-
-  componentWillMount() {
-    const { myAccount, admin, domain, intl } = this.props;
-    this.pages = [
-      <PageOne acct={myAccount.get('acct')} domain={domain} />,
-      <PageTwo myAccount={myAccount} />,
-      <PageThree myAccount={myAccount} />,
-      <PageFour domain={domain} intl={intl} />,
-      <PageSix admin={admin} domain={domain} />,
-    ];
-  };
-
-  componentDidMount() {
-    window.addEventListener('keyup', this.handleKeyUp);
-  }
-
-  componentWillUnmount() {
-    window.addEventListener('keyup', this.handleKeyUp);
-  }
-
-  handleSkip = (e) => {
-    e.preventDefault();
-    this.props.onClose();
-  }
-
-  handleDot = (e) => {
-    const i = Number(e.currentTarget.getAttribute('data-index'));
-    e.preventDefault();
-    this.setState({ currentIndex: i });
-  }
-
-  handlePrev = () => {
-    this.setState(({ currentIndex }) => ({
-      currentIndex: Math.max(0, currentIndex - 1),
-    }));
-  }
-
-  handleNext = () => {
-    const { pages } = this;
-    this.setState(({ currentIndex }) => ({
-      currentIndex: Math.min(currentIndex + 1, pages.length - 1),
-    }));
-  }
-
-  handleSwipe = (index) => {
-    this.setState({ currentIndex: index });
-  }
-
-  handleKeyUp = ({ key }) => {
-    switch (key) {
-    case 'ArrowLeft':
-      this.handlePrev();
-      break;
-    case 'ArrowRight':
-      this.handleNext();
-      break;
-    }
-  }
-
-  handleClose = () => {
-    this.props.onClose();
-  }
-
-  render () {
-    const { pages } = this;
-    const { currentIndex } = this.state;
-    const hasMore = currentIndex < pages.length - 1;
-
-    const nextOrDoneBtn = hasMore ? (
-      <button
-        onClick={this.handleNext}
-        className='onboarding-modal__nav onboarding-modal__next'
-      >
-        <FormattedMessage id='onboarding.next' defaultMessage='Next' />
-      </button>
-    ) : (
-      <button
-        onClick={this.handleClose}
-        className='onboarding-modal__nav onboarding-modal__done'
-      >
-        <FormattedMessage id='onboarding.done' defaultMessage='Done' />
-      </button>
-    );
-
-    return (
-      <div className='modal-root__modal onboarding-modal'>
-        <ReactSwipeableViews index={currentIndex} onChangeIndex={this.handleSwipe} className='onboarding-modal__pager'>
-          {pages.map((page, i) => {
-            const className = classNames('onboarding-modal__page__wrapper', {
-              'onboarding-modal__page__wrapper--active': i === currentIndex,
-            });
-            return (
-              <div key={i} className={className}>{page}</div>
-            );
-          })}
-        </ReactSwipeableViews>
-
-        <div className='onboarding-modal__paginator'>
-          <div>
-            <button
-              onClick={this.handleSkip}
-              className='onboarding-modal__nav onboarding-modal__skip'
-            >
-              <FormattedMessage id='onboarding.skip' defaultMessage='Skip' />
-            </button>
-          </div>
-
-          <div className='onboarding-modal__dots'>
-            {pages.map((_, i) => {
-              const className = classNames('onboarding-modal__dot', {
-                active: i === currentIndex,
-              });
-              return (
-                <div
-                  key={`dot-${i}`}
-                  role='button'
-                  tabIndex='0'
-                  data-index={i}
-                  onClick={this.handleDot}
-                  className={className}
-                />
-              );
-            })}
-          </div>
-
-          <div>
-            {nextOrDoneBtn}
-          </div>
-        </div>
-      </div>
-    );
-  }
-
-}
diff --git a/app/javascript/themes/glitch/features/ui/components/report_modal.js b/app/javascript/themes/glitch/features/ui/components/report_modal.js
deleted file mode 100644
index e6153948e..000000000
--- a/app/javascript/themes/glitch/features/ui/components/report_modal.js
+++ /dev/null
@@ -1,105 +0,0 @@
-import React from 'react';
-import { connect } from 'react-redux';
-import { changeReportComment, submitReport } from 'themes/glitch/actions/reports';
-import { refreshAccountTimeline } from 'themes/glitch/actions/timelines';
-import PropTypes from 'prop-types';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import { makeGetAccount } from 'themes/glitch/selectors';
-import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
-import StatusCheckBox from 'themes/glitch/features/report/containers/status_check_box_container';
-import { OrderedSet } from 'immutable';
-import ImmutablePureComponent from 'react-immutable-pure-component';
-import Button from 'themes/glitch/components/button';
-
-const messages = defineMessages({
-  placeholder: { id: 'report.placeholder', defaultMessage: 'Additional comments' },
-  submit: { id: 'report.submit', defaultMessage: 'Submit' },
-});
-
-const makeMapStateToProps = () => {
-  const getAccount = makeGetAccount();
-
-  const mapStateToProps = state => {
-    const accountId = state.getIn(['reports', 'new', 'account_id']);
-
-    return {
-      isSubmitting: state.getIn(['reports', 'new', 'isSubmitting']),
-      account: getAccount(state, accountId),
-      comment: state.getIn(['reports', 'new', 'comment']),
-      statusIds: OrderedSet(state.getIn(['timelines', `account:${accountId}`, 'items'])).union(state.getIn(['reports', 'new', 'status_ids'])),
-    };
-  };
-
-  return mapStateToProps;
-};
-
-@connect(makeMapStateToProps)
-@injectIntl
-export default class ReportModal extends ImmutablePureComponent {
-
-  static propTypes = {
-    isSubmitting: PropTypes.bool,
-    account: ImmutablePropTypes.map,
-    statusIds: ImmutablePropTypes.orderedSet.isRequired,
-    comment: PropTypes.string.isRequired,
-    dispatch: PropTypes.func.isRequired,
-    intl: PropTypes.object.isRequired,
-  };
-
-  handleCommentChange = (e) => {
-    this.props.dispatch(changeReportComment(e.target.value));
-  }
-
-  handleSubmit = () => {
-    this.props.dispatch(submitReport());
-  }
-
-  componentDidMount () {
-    this.props.dispatch(refreshAccountTimeline(this.props.account.get('id')));
-  }
-
-  componentWillReceiveProps (nextProps) {
-    if (this.props.account !== nextProps.account && nextProps.account) {
-      this.props.dispatch(refreshAccountTimeline(nextProps.account.get('id')));
-    }
-  }
-
-  render () {
-    const { account, comment, intl, statusIds, isSubmitting } = this.props;
-
-    if (!account) {
-      return null;
-    }
-
-    return (
-      <div className='modal-root__modal report-modal'>
-        <div className='report-modal__target'>
-          <FormattedMessage id='report.target' defaultMessage='Report {target}' values={{ target: <strong>{account.get('acct')}</strong> }} />
-        </div>
-
-        <div className='report-modal__container'>
-          <div className='report-modal__statuses'>
-            <div>
-              {statusIds.map(statusId => <StatusCheckBox id={statusId} key={statusId} disabled={isSubmitting} />)}
-            </div>
-          </div>
-
-          <div className='report-modal__comment'>
-            <textarea
-              className='setting-text light'
-              placeholder={intl.formatMessage(messages.placeholder)}
-              value={comment}
-              onChange={this.handleCommentChange}
-              disabled={isSubmitting}
-            />
-          </div>
-        </div>
-
-        <div className='report-modal__action-bar'>
-          <Button disabled={isSubmitting} text={intl.formatMessage(messages.submit)} onClick={this.handleSubmit} />
-        </div>
-      </div>
-    );
-  }
-
-}
diff --git a/app/javascript/themes/glitch/features/ui/components/tabs_bar.js b/app/javascript/themes/glitch/features/ui/components/tabs_bar.js
deleted file mode 100644
index ef5deae99..000000000
--- a/app/javascript/themes/glitch/features/ui/components/tabs_bar.js
+++ /dev/null
@@ -1,84 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import { NavLink } from 'react-router-dom';
-import { FormattedMessage, injectIntl } from 'react-intl';
-import { debounce } from 'lodash';
-import { isUserTouching } from 'themes/glitch/util/is_mobile';
-
-export const links = [
-  <NavLink className='tabs-bar__link primary' to='/statuses/new' data-preview-title-id='tabs_bar.compose' data-preview-icon='pencil' ><i className='fa fa-fw fa-pencil' /><FormattedMessage id='tabs_bar.compose' defaultMessage='Compose' /></NavLink>,
-  <NavLink className='tabs-bar__link primary' to='/timelines/home' data-preview-title-id='column.home' data-preview-icon='home' ><i className='fa fa-fw fa-home' /><FormattedMessage id='tabs_bar.home' defaultMessage='Home' /></NavLink>,
-  <NavLink className='tabs-bar__link primary' to='/notifications' data-preview-title-id='column.notifications' data-preview-icon='bell' ><i className='fa fa-fw fa-bell' /><FormattedMessage id='tabs_bar.notifications' defaultMessage='Notifications' /></NavLink>,
-
-  <NavLink className='tabs-bar__link secondary' to='/timelines/public/local' data-preview-title-id='column.community' data-preview-icon='users' ><i className='fa fa-fw fa-users' /><FormattedMessage id='tabs_bar.local_timeline' defaultMessage='Local' /></NavLink>,
-  <NavLink className='tabs-bar__link secondary' exact to='/timelines/public' data-preview-title-id='column.public' data-preview-icon='globe' ><i className='fa fa-fw fa-globe' /><FormattedMessage id='tabs_bar.federated_timeline' defaultMessage='Federated' /></NavLink>,
-
-  <NavLink className='tabs-bar__link primary' style={{ flexGrow: '0', flexBasis: '30px' }} to='/getting-started' data-preview-title-id='getting_started.heading' data-preview-icon='asterisk' ><i className='fa fa-fw fa-asterisk' /></NavLink>,
-];
-
-export function getIndex (path) {
-  return links.findIndex(link => link.props.to === path);
-}
-
-export function getLink (index) {
-  return links[index].props.to;
-}
-
-@injectIntl
-export default class TabsBar extends React.Component {
-
-  static contextTypes = {
-    router: PropTypes.object.isRequired,
-  }
-
-  static propTypes = {
-    intl: PropTypes.object.isRequired,
-  }
-
-  setRef = ref => {
-    this.node = ref;
-  }
-
-  handleClick = (e) => {
-    // Only apply optimization for touch devices, which we assume are slower
-    // We thus avoid the 250ms delay for non-touch devices and the lag for touch devices
-    if (isUserTouching()) {
-      e.preventDefault();
-      e.persist();
-
-      requestAnimationFrame(() => {
-        const tabs = Array(...this.node.querySelectorAll('.tabs-bar__link'));
-        const currentTab = tabs.find(tab => tab.classList.contains('active'));
-        const nextTab = tabs.find(tab => tab.contains(e.target));
-        const { props: { to } } = links[Array(...this.node.childNodes).indexOf(nextTab)];
-
-
-        if (currentTab !== nextTab) {
-          if (currentTab) {
-            currentTab.classList.remove('active');
-          }
-
-          const listener = debounce(() => {
-            nextTab.removeEventListener('transitionend', listener);
-            this.context.router.history.push(to);
-          }, 50);
-
-          nextTab.addEventListener('transitionend', listener);
-          nextTab.classList.add('active');
-        }
-      });
-    }
-
-  }
-
-  render () {
-    const { intl: { formatMessage } } = this.props;
-
-    return (
-      <nav className='tabs-bar' ref={this.setRef}>
-        {links.map(link => React.cloneElement(link, { key: link.props.to, onClick: this.handleClick, 'aria-label': formatMessage({ id: link.props['data-preview-title-id'] }) }))}
-      </nav>
-    );
-  }
-
-}
diff --git a/app/javascript/themes/glitch/features/ui/components/upload_area.js b/app/javascript/themes/glitch/features/ui/components/upload_area.js
deleted file mode 100644
index 72a450215..000000000
--- a/app/javascript/themes/glitch/features/ui/components/upload_area.js
+++ /dev/null
@@ -1,52 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import Motion from 'themes/glitch/util/optional_motion';
-import spring from 'react-motion/lib/spring';
-import { FormattedMessage } from 'react-intl';
-
-export default class UploadArea extends React.PureComponent {
-
-  static propTypes = {
-    active: PropTypes.bool,
-    onClose: PropTypes.func,
-  };
-
-  handleKeyUp = (e) => {
-    const keyCode = e.keyCode;
-    if (this.props.active) {
-      switch(keyCode) {
-      case 27:
-        e.preventDefault();
-        e.stopPropagation();
-        this.props.onClose();
-        break;
-      }
-    }
-  }
-
-  componentDidMount () {
-    window.addEventListener('keyup', this.handleKeyUp, false);
-  }
-
-  componentWillUnmount () {
-    window.removeEventListener('keyup', this.handleKeyUp);
-  }
-
-  render () {
-    const { active } = this.props;
-
-    return (
-      <Motion defaultStyle={{ backgroundOpacity: 0, backgroundScale: 0.95 }} style={{ backgroundOpacity: spring(active ? 1 : 0, { stiffness: 150, damping: 15 }), backgroundScale: spring(active ? 1 : 0.95, { stiffness: 200, damping: 3 }) }}>
-        {({ backgroundOpacity, backgroundScale }) =>
-          <div className='upload-area' style={{ visibility: active ? 'visible' : 'hidden', opacity: backgroundOpacity }}>
-            <div className='upload-area__drop'>
-              <div className='upload-area__background' style={{ transform: `scale(${backgroundScale})` }} />
-              <div className='upload-area__content'><FormattedMessage id='upload_area.title' defaultMessage='Drag & drop to upload' /></div>
-            </div>
-          </div>
-        }
-      </Motion>
-    );
-  }
-
-}
diff --git a/app/javascript/themes/glitch/features/ui/components/video_modal.js b/app/javascript/themes/glitch/features/ui/components/video_modal.js
deleted file mode 100644
index 91168c790..000000000
--- a/app/javascript/themes/glitch/features/ui/components/video_modal.js
+++ /dev/null
@@ -1,33 +0,0 @@
-import React from 'react';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import PropTypes from 'prop-types';
-import Video from 'themes/glitch/features/video';
-import ImmutablePureComponent from 'react-immutable-pure-component';
-
-export default class VideoModal extends ImmutablePureComponent {
-
-  static propTypes = {
-    media: ImmutablePropTypes.map.isRequired,
-    time: PropTypes.number,
-    onClose: PropTypes.func.isRequired,
-  };
-
-  render () {
-    const { media, time, onClose } = this.props;
-
-    return (
-      <div className='modal-root__modal media-modal'>
-        <div>
-          <Video
-            preview={media.get('preview_url')}
-            src={media.get('url')}
-            startTime={time}
-            onCloseVideo={onClose}
-            description={media.get('description')}
-          />
-        </div>
-      </div>
-    );
-  }
-
-}