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..7d01a2f5a
--- /dev/null
+++ b/app/soapbox/features/ui/components/profile_media_panel.js
@@ -0,0 +1,80 @@
+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,
+ };
+
+ 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 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..980f8ee6b 100644
--- a/app/soapbox/pages/profile_page.js
+++ b/app/soapbox/pages/profile_page.js
@@ -9,6 +9,7 @@ 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';
@@ -81,6 +82,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/media-gallery.scss b/app/styles/components/media-gallery.scss
index 22b59c64c..731f361fc 100644
--- a/app/styles/components/media-gallery.scss
+++ b/app/styles/components/media-gallery.scss
@@ -5,6 +5,7 @@
border-radius: 4px;
position: relative;
width: 100%;
+ height: auto;
background-color: var(--brand-color--faint);
}
@@ -41,7 +42,7 @@
width: 100%;
img {
- object-fit: contain;
+ object-fit: cover;
}
}
@@ -63,6 +64,18 @@
}
}
+.status__wrapper, .detailed-status__wrapper {
+ .media-gallery__item-thumbnail {
+ &,
+ .still-image {
+
+ img {
+ object-fit: contain;
+ }
+ }
+ }
+}
+
.media-gallery__preview {
width: 100%;
height: 100%;
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;
+ }
+}