diff --git a/app/soapbox/features/ui/components/profile_media_panel.js b/app/soapbox/features/ui/components/profile_media_panel.js
new file mode 100644
index 000000000..a48427574
--- /dev/null
+++ b/app/soapbox/features/ui/components/profile_media_panel.js
@@ -0,0 +1,85 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { FormattedMessage, injectIntl } from 'react-intl';
+import { connect } from 'react-redux';
+import { getAccountGallery } from 'soapbox/selectors';
+import { openModal } from 'soapbox/actions/modal';
+import { expandAccountMediaTimeline } from '../../../actions/timelines';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import MediaItem from '../../account_gallery/components/media_item';
+import Icon from 'soapbox/components/icon';
+
+class ProfileMediaPanel extends ImmutablePureComponent {
+
+ static propTypes = {
+ account: ImmutablePropTypes.map,
+ attachments: ImmutablePropTypes.list,
+ dispatch: PropTypes.func.isRequired,
+ };
+
+ state = {
+ width: 255,
+ };
+
+ handleOpenMedia = attachment => {
+ if (attachment.get('type') === 'video') {
+ this.props.dispatch(openModal('VIDEO', { media: attachment, status: attachment.get('status') }));
+ } else {
+ const media = attachment.getIn(['status', 'media_attachments']);
+ const index = media.findIndex(x => x.get('id') === attachment.get('id'));
+
+ this.props.dispatch(openModal('MEDIA', { media, index, status: attachment.get('status'), account: attachment.get('account') }));
+ }
+ }
+
+ componentDidMount() {
+ const { account } = this.props;
+ const accountId = account.get('id');
+ this.props.dispatch(expandAccountMediaTimeline(accountId));
+ }
+
+ render() {
+ const { attachments } = this.props;
+ const { width } = this.state;
+ const nineAttachments = attachments.slice(0, 9);
+
+ if (attachments.isEmpty()) {
+ return null;
+ }
+
+ return (
+
+
+
+
+
+
+
+
+
+ {nineAttachments.map((attachment, index) => (
+
+ ))}
+
+
+
+ );
+ };
+
+};
+
+const mapStateToProps = (state, { account }) => ({
+ attachments: getAccountGallery(state, account.get('id')),
+});
+
+export default injectIntl(
+ connect(mapStateToProps, null, null, {
+ forwardRef: true,
+ }
+ )(ProfileMediaPanel));
diff --git a/app/soapbox/pages/profile_page.js b/app/soapbox/pages/profile_page.js
index fad1ced8b..b2d3c8834 100644
--- a/app/soapbox/pages/profile_page.js
+++ b/app/soapbox/pages/profile_page.js
@@ -9,9 +9,11 @@ import WhoToFollowPanel from '../features/ui/components/who_to_follow_panel';
import LinkFooter from '../features/ui/components/link_footer';
import SignUpPanel from '../features/ui/components/sign_up_panel';
import ProfileInfoPanel from '../features/ui/components/profile_info_panel';
+import ProfileMediaPanel from '../features/ui/components/profile_media_panel';
import { acctFull } from 'soapbox/utils/accounts';
import { getFeatures } from 'soapbox/utils/features';
import { makeGetAccount } from '../selectors';
+import { debounce } from 'lodash';
const mapStateToProps = (state, { params: { username }, withReplies = false }) => {
const accounts = state.getIn(['accounts']);
@@ -48,9 +50,28 @@ class ProfilePage extends ImmutablePureComponent {
features: PropTypes.object,
};
+ state = {
+ isSmallScreen: (window.innerWidth <= 1200),
+ }
+
+ componentDidMount() {
+ window.addEventListener('resize', this.handleResize, { passive: true });
+ }
+
+ componentWillUnmount() {
+ window.removeEventListener('resize', this.handleResize);
+ }
+
+ handleResize = debounce(() => {
+ this.setState({ isSmallScreen: (window.innerWidth <= 1200) });
+ }, 5, {
+ trailing: true,
+ });
+
render() {
const { children, accountId, account, accountUsername, features } = this.props;
const bg = account ? account.getIn(['customizations', 'background']) : undefined;
+ const { isSmallScreen } = this.state;
return (
@@ -68,6 +89,7 @@ class ProfilePage extends ImmutablePureComponent {
+ {isSmallScreen && account &&
}
@@ -81,6 +103,7 @@ class ProfilePage extends ImmutablePureComponent {
{features.suggestions &&
}
+ {account &&
}
diff --git a/app/styles/application.scss b/app/styles/application.scss
index 7c2cd14b8..2b4c52a83 100644
--- a/app/styles/application.scss
+++ b/app/styles/application.scss
@@ -65,6 +65,7 @@
@import 'components/theme-toggle';
@import 'components/trends';
@import 'components/wtf-panel';
+@import 'components/profile-media-panel';
@import 'components/profile-info-panel';
@import 'components/setting-toggle';
@import 'components/spoiler-button';
diff --git a/app/styles/components/profile-media-panel.scss b/app/styles/components/profile-media-panel.scss
new file mode 100644
index 000000000..3089bf19a
--- /dev/null
+++ b/app/styles/components/profile-media-panel.scss
@@ -0,0 +1,48 @@
+.media-panel {
+ @include standard-panel-shadow;
+ display: flex;
+ width: 100%;
+ border-radius: 10px;
+ flex-direction: column;
+ height: auto;
+ box-sizing: border-box;
+ background: var(--foreground-color);
+
+ &:first-child {
+ margin-top: 0;
+ }
+
+ &:not(:last-of-type) {
+ margin-bottom: 10px;
+ }
+
+ .media-panel-header {
+ display: flex;
+ align-items: baseline;
+ margin-bottom: 10px;
+ padding: 15px 15px 0;
+
+ &__icon {
+ margin-right: 10px;
+ }
+
+ &__label {
+ flex: 1 1;
+ color: var(--primary-text-color);
+ font-size: 16px;
+ font-weight: bold;
+ line-height: 19px;
+ }
+ }
+
+ &__content {
+ width: 100%;
+ padding: 8px 0;
+ }
+
+ &__list {
+ padding: 0 5px;
+ display: flex;
+ flex-wrap: wrap;
+ }
+}