Adding compact layout (#2991)

* Adding compact layout

* Fixing ESLint error
This commit is contained in:
Asaad Mahmood
2016-05-16 17:06:26 +05:00
committed by Christopher Speller
parent 565b111234
commit 0258fcfa5c
16 changed files with 409 additions and 32 deletions

View File

@@ -8,6 +8,7 @@ import Client from 'utils/web_client.jsx';
import Constants from 'utils/constants.jsx';
import {intlShape, injectIntl, defineMessages} from 'react-intl';
import {Tooltip, OverlayTrigger} from 'react-bootstrap';
const holders = defineMessages({
download: {
@@ -169,6 +170,42 @@ class FileAttachment extends React.Component {
} else {
trimmedFilename = filenameString;
}
var filenameOverlay = (
<OverlayTrigger
delayShow={1000}
placement='top'
overlay={<Tooltip id='file-name__tooltip'>{this.props.intl.formatMessage(holders.download) + ' "' + filenameString + '"'}</Tooltip>}
>
<a
href={fileUrl}
download={filenameString}
className='post-image__name'
target='_blank'
rel='noopener noreferrer'
>
{trimmedFilename}
</a>
</OverlayTrigger>
);
if (this.props.compactDisplay) {
filenameOverlay = (
<OverlayTrigger
delayShow={1000}
placement='top'
overlay={<Tooltip id='file-name__tooltip'>{filenameString}</Tooltip>}
>
<a
href='#'
onClick={() => this.props.handleImageClick(this.props.index)}
className='post-image__name'
rel='noopener noreferrer'
>
<i className='glyphicon glyphicon-paperclip'/>{trimmedFilename}
</a>
</OverlayTrigger>
);
}
return (
<div
@@ -183,17 +220,7 @@ class FileAttachment extends React.Component {
{thumbnail}
</a>
<div className='post-image__details'>
<a
href={fileUrl}
download={filenameString}
data-toggle='tooltip'
title={this.props.intl.formatMessage(holders.download) + ' "' + filenameString + '"'}
className='post-image__name'
target='_blank'
rel='noopener noreferrer'
>
{trimmedFilename}
</a>
{filenameOverlay}
<div>
<a
href={fileUrl}
@@ -225,7 +252,9 @@ FileAttachment.propTypes = {
index: React.PropTypes.number.isRequired,
// handler for when the thumbnail is clicked passed the index above
handleImageClick: React.PropTypes.func
handleImageClick: React.PropTypes.func,
compactDisplay: React.PropTypes.bool
};
export default injectIntl(FileAttachment);

View File

@@ -29,6 +29,7 @@ export default class FileAttachmentList extends React.Component {
filename={filenames[i]}
index={i}
handleImageClick={this.handleImageClick}
compactDisplay={this.props.compactDisplay}
/>
);
}
@@ -60,5 +61,7 @@ FileAttachmentList.propTypes = {
channelId: React.PropTypes.string,
// the user that owns the post that this is attached to
userId: React.PropTypes.string
userId: React.PropTypes.string,
compactDisplay: React.PropTypes.bool
};

View File

@@ -103,6 +103,10 @@ export default class Post extends React.Component {
return true;
}
if (nextProps.compactDisplay !== this.props.compactDisplay) {
return true;
}
if (!Utils.areObjectsEqual(nextProps.user, this.props.user)) {
return true;
}
@@ -188,7 +192,7 @@ export default class Post extends React.Component {
}
let profilePic = null;
if (!this.props.hideProfilePic) {
if (!this.props.hideProfilePic || this.props.compactDisplay) {
profilePic = (
<img
src={Utils.getProfilePicSrcForPost(post, timestamp)}
@@ -212,11 +216,16 @@ export default class Post extends React.Component {
centerClass = 'center';
}
let compactClass = '';
if (this.props.compactDisplay) {
compactClass = 'post--compact';
}
return (
<div>
<div
id={'post_' + post.id}
className={'post ' + sameUserClass + ' ' + rootUser + ' ' + postType + ' ' + currentUserCss + ' ' + shouldHighlightClass + ' ' + systemMessageClass}
className={'post ' + sameUserClass + ' ' + compactClass + ' ' + rootUser + ' ' + postType + ' ' + currentUserCss + ' ' + shouldHighlightClass + ' ' + systemMessageClass}
>
<div className={'post__content ' + centerClass}>
<div className='post__img'>{profilePic}</div>
@@ -231,6 +240,7 @@ export default class Post extends React.Component {
sameUser={this.props.sameUser}
user={this.props.user}
currentUser={this.props.currentUser}
compactDisplay={this.props.compactDisplay}
/>
<PostBody
post={post}
@@ -239,6 +249,7 @@ export default class Post extends React.Component {
posts={posts}
handleCommentClick={this.handleCommentClick}
retryPost={this.retryPost}
compactDisplay={this.props.compactDisplay}
/>
</div>
</div>
@@ -261,5 +272,6 @@ Post.propTypes = {
displayNameType: React.PropTypes.string,
hasProfiles: React.PropTypes.bool,
currentUser: React.PropTypes.object.isRequired,
center: React.PropTypes.bool
center: React.PropTypes.bool,
compactDisplay: React.PropTypes.bool
};

View File

@@ -24,6 +24,10 @@ export default class PostBody extends React.Component {
return true;
}
if (!Utils.areObjectsEqual(nextProps.compactDisplay, this.props.compactDisplay)) {
return true;
}
if (nextProps.retryPost.toString() !== this.props.retryPost.toString()) {
return true;
}
@@ -136,9 +140,11 @@ export default class PostBody extends React.Component {
if (filenames && filenames.length > 0) {
fileAttachmentHolder = (
<FileAttachmentList
filenames={filenames}
channelId={post.channel_id}
userId={post.user_id}
compactDisplay={this.props.compactDisplay}
/>
);
}
@@ -189,5 +195,6 @@ PostBody.propTypes = {
post: React.PropTypes.object.isRequired,
parentPost: React.PropTypes.object,
retryPost: React.PropTypes.func.isRequired,
handleCommentClick: React.PropTypes.func.isRequired
handleCommentClick: React.PropTypes.func.isRequired,
compactDisplay: React.PropTypes.bool
};

View File

@@ -14,6 +14,7 @@ export default class PostHeader extends React.Component {
super(props);
this.state = {};
}
render() {
const post = this.props.post;
@@ -56,6 +57,7 @@ export default class PostHeader extends React.Component {
isLastComment={this.props.isLastComment}
sameUser={this.props.sameUser}
currentUser={this.props.currentUser}
compactDisplay={this.props.compactDisplay}
/>
</li>
</ul>
@@ -76,5 +78,6 @@ PostHeader.propTypes = {
commentCount: React.PropTypes.number.isRequired,
isLastComment: React.PropTypes.bool.isRequired,
handleCommentClick: React.PropTypes.func.isRequired,
sameUser: React.PropTypes.bool.isRequired
sameUser: React.PropTypes.bool.isRequired,
compactDisplay: React.PropTypes.bool
};

View File

@@ -219,6 +219,7 @@ export default class PostInfo extends React.Component {
<TimeSince
eventTime={post.create_at}
sameUser={this.props.sameUser}
compactDisplay={this.props.compactDisplay}
/>
</li>
<li className='col col__reply'>
@@ -250,5 +251,6 @@ PostInfo.propTypes = {
allowReply: React.PropTypes.string.isRequired,
handleCommentClick: React.PropTypes.func.isRequired,
sameUser: React.PropTypes.bool.isRequired,
currentUser: React.PropTypes.object.isRequired
currentUser: React.PropTypes.object.isRequired,
compactDisplay: React.PropTypes.bool
};

View File

@@ -55,6 +55,7 @@ export default class PostsView extends React.Component {
this.state = {
displayNameType: PreferenceStore.get(Preferences.CATEGORY_DISPLAY_SETTINGS, 'name_format', 'false'),
centerPosts: PreferenceStore.get(Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.CHANNEL_DISPLAY_MODE, Preferences.CHANNEL_DISPLAY_MODE_DEFAULT) === Preferences.CHANNEL_DISPLAY_MODE_CENTERED,
compactPosts: PreferenceStore.get(Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.MESSAGE_DISPLAY, Preferences.MESSAGE_DISPLAY_DEFAULT) === Preferences.MESSAGE_DISPLAY_COMPACT,
isScrolling: false,
topPostId: null,
currentUser: UserStore.getCurrentUser(),
@@ -79,7 +80,8 @@ export default class PostsView extends React.Component {
updateState() {
this.setState({
displayNameType: PreferenceStore.get(Preferences.CATEGORY_DISPLAY_SETTINGS, 'name_format', 'false'),
centerPosts: PreferenceStore.get(Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.CHANNEL_DISPLAY_MODE, Preferences.CHANNEL_DISPLAY_MODE_DEFAULT) === Preferences.CHANNEL_DISPLAY_MODE_CENTERED
centerPosts: PreferenceStore.get(Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.CHANNEL_DISPLAY_MODE, Preferences.CHANNEL_DISPLAY_MODE_DEFAULT) === Preferences.CHANNEL_DISPLAY_MODE_CENTERED,
compactPosts: PreferenceStore.get(Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.MESSAGE_DISPLAY, Preferences.MESSAGE_DISPLAY_DEFAULT) === Preferences.MESSAGE_DISPLAY_COMPACT
});
}
onUserChange() {
@@ -274,6 +276,7 @@ export default class PostsView extends React.Component {
user={profile}
currentUser={this.state.currentUser}
center={this.state.centerPosts}
compactDisplay={this.state.compactPosts}
/>
);
@@ -479,6 +482,9 @@ export default class PostsView extends React.Component {
if (this.state.centerPosts !== nextState.centerPosts) {
return true;
}
if (this.state.compactPosts !== nextState.compactPosts) {
return true;
}
if (!Utils.areObjectsEqual(this.state.profiles, nextState.profiles)) {
return true;
}
@@ -592,7 +598,8 @@ PostsView.propTypes = {
showMoreMessagesBottom: React.PropTypes.bool,
channel: React.PropTypes.object,
messageSeparatorTime: React.PropTypes.number,
postsToHighlight: React.PropTypes.object
postsToHighlight: React.PropTypes.object,
compactDisplay: React.PropTypes.bool
};
function ScrollToBottomArrows({isScrolling, atBottom, onClick}) {

View File

@@ -26,7 +26,7 @@ export default class TimeSince extends React.Component {
clearInterval(this.intervalId);
}
render() {
if (this.props.sameUser) {
if (this.props.sameUser || this.props.compactDisplay) {
return (
<time className='post__time'>
{Utils.displayTimeFormatted(this.props.eventTime)}
@@ -69,5 +69,6 @@ TimeSince.defaultProps = {
TimeSince.propTypes = {
eventTime: React.PropTypes.number.isRequired,
sameUser: React.PropTypes.bool
sameUser: React.PropTypes.bool,
compactDisplay: React.PropTypes.bool
};

View File

@@ -23,7 +23,8 @@ function getDisplayStateFromStores() {
militaryTime: PreferenceStore.get(Preferences.CATEGORY_DISPLAY_SETTINGS, 'use_military_time', 'false'),
nameFormat: PreferenceStore.get(Preferences.CATEGORY_DISPLAY_SETTINGS, 'name_format', 'username'),
selectedFont: PreferenceStore.get(Preferences.CATEGORY_DISPLAY_SETTINGS, 'selected_font', Constants.DEFAULT_FONT),
channelDisplayMode: PreferenceStore.get(Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.CHANNEL_DISPLAY_MODE, Preferences.CHANNEL_DISPLAY_MODE_DEFAULT)
channelDisplayMode: PreferenceStore.get(Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.CHANNEL_DISPLAY_MODE, Preferences.CHANNEL_DISPLAY_MODE_DEFAULT),
messageDisplay: PreferenceStore.get(Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.MESSAGE_DISPLAY, Preferences.MESSAGE_DISPLAY_DEFAULT)
};
}
@@ -70,8 +71,14 @@ export default class UserSettingsDisplay extends React.Component {
name: Preferences.CHANNEL_DISPLAY_MODE,
value: this.state.channelDisplayMode
};
const messageDisplayPreference = {
user_id: userId,
category: Preferences.CATEGORY_DISPLAY_SETTINGS,
name: Preferences.MESSAGE_DISPLAY,
value: this.state.messageDisplay
};
AsyncClient.savePreferences([timePreference, namePreference, fontPreference, channelDisplayModePreference],
AsyncClient.savePreferences([timePreference, namePreference, fontPreference, channelDisplayModePreference, messageDisplayPreference],
() => {
this.updateSection('');
},
@@ -89,6 +96,9 @@ export default class UserSettingsDisplay extends React.Component {
handleChannelDisplayModeRadio(channelDisplayMode) {
this.setState({channelDisplayMode});
}
handlemessageDisplayRadio(messageDisplay) {
this.setState({messageDisplay});
}
handleFont(selectedFont) {
Utils.applyFont(selectedFont);
this.setState({selectedFont});
@@ -115,6 +125,7 @@ export default class UserSettingsDisplay extends React.Component {
let channelDisplayModeSection;
let fontSection;
let languagesSection;
let messageDisplaySection;
if (this.props.activeSection === 'clock') {
const clockFormat = [false, false];
@@ -350,6 +361,105 @@ export default class UserSettingsDisplay extends React.Component {
);
}
if (this.props.activeSection === Preferences.MESSAGE_DISPLAY) {
const messageDisplay = [false, false];
if (this.state.messageDisplay === Preferences.MESSAGE_DISPLAY_CLEAN) {
messageDisplay[0] = true;
} else {
messageDisplay[1] = true;
}
const inputs = [
<div key='userDisplayNameOptions'>
<div className='radio'>
<label>
<input
type='radio'
checked={messageDisplay[0]}
onChange={this.handlemessageDisplayRadio.bind(this, Preferences.MESSAGE_DISPLAY_CLEAN)}
/>
<FormattedMessage
id='user.settings.display.messageDisplayClean'
defaultMessage='Clean'
/>
</label>
<br/>
</div>
<div className='radio'>
<label>
<input
type='radio'
checked={messageDisplay[1]}
onChange={this.handlemessageDisplayRadio.bind(this, Preferences.MESSAGE_DISPLAY_COMPACT)}
/>
<FormattedMessage
id='user.settings.display.messageDisplayCompact'
defaultMessage='Compact'
/>
</label>
<br/>
</div>
<div>
<br/>
<FormattedMessage
id='user.settings.display.messageDisplayDescription'
defaultMessage='Select how messages in a channel should be displayed.'
/>
</div>
</div>
];
messageDisplaySection = (
<SettingItemMax
title={
<FormattedMessage
id='user.settings.display.messageDisplayTitle'
defaultMessage='Message Display'
/>
}
inputs={inputs}
submit={this.handleSubmit}
server_error={serverError}
updateSection={(e) => {
this.updateSection('');
e.preventDefault();
}}
/>
);
} else {
let describe;
if (this.state.messageDisplay === Preferences.MESSAGE_DISPLAY_CLEAN) {
describe = (
<FormattedMessage
id='user.settings.display.messageDisplayClean'
defaultMessage='Clean'
/>
);
} else {
describe = (
<FormattedMessage
id='user.settings.display.messageDisplayCompact'
defaultMessage='Compact'
/>
);
}
messageDisplaySection = (
<SettingItemMin
title={
<FormattedMessage
id='user.settings.display.messageDisplayTitle'
defaultMessage='Message Display'
/>
}
describe={describe}
updateSection={() => {
this.props.updateSection(Preferences.MESSAGE_DISPLAY);
}}
/>
);
}
if (this.props.activeSection === Preferences.CHANNEL_DISPLAY_MODE) {
const channelDisplayMode = [false, false];
if (this.state.channelDisplayMode === Preferences.CHANNEL_DISPLAY_MODE_CENTERED) {
@@ -392,7 +502,7 @@ export default class UserSettingsDisplay extends React.Component {
<br/>
<FormattedMessage
id='user.settings.display.channeldisplaymode'
defaultMessage='Select how text in a channel is displayed.'
defaultMessage='Select the width of the center channel.'
/>
</div>
</div>
@@ -601,6 +711,8 @@ export default class UserSettingsDisplay extends React.Component {
<div className='divider-dark'/>
{nameFormatSection}
<div className='divider-dark'/>
{messageDisplaySection}
<div className='divider-dark'/>
{channelDisplayModeSection}
<div className='divider-dark'/>
{languagesSection}

View File

@@ -1288,9 +1288,13 @@
"user.settings.developer.thirdParty": "Open to register a new third-party application",
"user.settings.developer.title": "Developer Settings",
"user.settings.display.channelDisplayTitle": "Channel Display Mode",
"user.settings.display.channeldisplaymode": "Select how text in a channel is displayed.",
"user.settings.display.channeldisplaymode": "Select the width of the center channel.",
"user.settings.display.clockDisplay": "Clock Display",
"user.settings.display.fixedWidthCentered": "Fixed width, centered",
"user.settings.display.messageDisplayTitle": "Message Display",
"user.settings.display.messageDisplayDescription": "Select how messages in a channel should be displayed.",
"user.settings.display.messageDisplayClean": "Clean",
"user.settings.display.messageDisplayCompact": "Compact",
"user.settings.display.fontDesc": "Select the font displayed in the Mattermost user interface.",
"user.settings.display.fontTitle": "Display Font",
"user.settings.display.fullScreen": "Full width",

View File

@@ -448,7 +448,7 @@
@include opacity(.8);
float: right;
margin-right: 3px;
margin-top: 12px;
margin-top: 8px;
}
.member-select__container {
@@ -459,7 +459,7 @@
select {
@include opacity(.8);
float: right;
margin: 5px 5px 0 2px;
margin: 1px 5px 0 2px;
width: auto;
}
}

View File

@@ -417,7 +417,8 @@ body.ios {
.post-create-footer {
@include clearfix;
font-size: 13px;
padding: 3px 0 0 0;
padding: 3px 0 0;
.control-label {
font-weight: normal;
margin-bottom: 0;
@@ -482,6 +483,79 @@ body.ios {
background-color: beige;
}
&.post--compact {
.markdown__heading {
font-size: 1em;
margin: 0 0 7px;
}
.post__body {
background: transparent !important;
margin-top: -1px;
padding: 3px 0;
}
.post-image__columns {
clear: both;
}
.post-image__column {
@include border-radius(2px);
font-size: .9em;
height: 26px;
line-height: 25px;
padding: 0 7px;
.post-image__thumbnail {
display: none;
}
.post-image__details {
background: transparent;
border: none;
padding: 0;
width: 100%;
> div {
display: none;
}
}
.post-image__name {
@include clearfix;
display: block;
margin: 0;
padding-right: 10px;
text-overflow: ellipsis;
white-space: nowrap;
i {
font-size: .9em;
margin-right: 5px;
opacity: .5;
}
}
a {
&:hover {
text-decoration: none;
}
}
}
.post__img {
padding-top: 3px;
width: 28px;
img,
svg {
height: 20px;
width: 20px;
}
}
}
p {
font-size: .97em;
line-height: 1.6em;

View File

@@ -45,6 +45,11 @@
}
.post {
&.post--compact {
}
.post__dropdown {
display: inline-block;
height: 20px;

View File

@@ -65,6 +65,7 @@
}
}
// Tablet and desktop
@media screen and (min-width: 768px) {
.second-bar {
display: none;
@@ -83,6 +84,111 @@
}
.post {
&.post--compact {
padding: 5px .5em 0 80px;
.post__link {
margin: 4px 0 7px;
}
.post__time {
font-size: .85em;
left: -70px;
position: absolute;
top: 2px;
}
span {
p {
&:last-child {
margin-bottom: .3em;
}
}
}
.post__header {
float: left;
height: 18px;
padding-top: 3px;
.col__name {
font-weight: bold;
}
.col__reply {
top: 2px;
}
}
&.other--root {
.post__body {
> div {
&:first-child {
min-height: 21px;
}
}
}
.post__link + .post__body {
clear: both;
}
&.post--comment {
.post__header {
.col__reply {
top: 0;
}
}
}
}
.post-code {
clear: both;
}
&.same--root {
&.same--user {
padding-left: 80px;
.post__img {
img {
display: none;
}
}
}
&.post--comment {
padding-top: 1px;
.post__img {
img {
display: inline-block;
}
}
&.same--user {
.post__img {
img {
display: none;
}
}
}
.post__header {
margin-left: 12px;
}
}
}
.post__body {
width: 100%;
}
.post__content {
padding-right: 85px;
}
}
&.same--root {
&.same--user {
.post__time {
@@ -94,6 +200,14 @@
text-rendering: auto;
top: -2px;
}
&.post--compact {
.post__time {
font-size: .85em;
left: -70px;
top: -5px;
}
}
}
}
}

View File

@@ -531,7 +531,11 @@ export default {
CHANNEL_DISPLAY_MODE: 'channel_display_mode',
CHANNEL_DISPLAY_MODE_CENTERED: 'centered',
CHANNEL_DISPLAY_MODE_FULL_SCREEN: 'full',
CHANNEL_DISPLAY_MODE_DEFAULT: 'centered'
CHANNEL_DISPLAY_MODE_DEFAULT: 'centered',
MESSAGE_DISPLAY: 'message_display',
MESSAGE_DISPLAY_CLEAN: 'clean',
MESSAGE_DISPLAY_COMPACT: 'compact',
MESSAGE_DISPLAY_DEFAULT: 'clean'
},
TutorialSteps: {
INTRO_SCREENS: 0,

View File

@@ -734,7 +734,7 @@ export function applyTheme(theme) {
changeCss('.app__body .scrollbar--horizontal, .app__body .scrollbar--vertical', 'background:' + changeOpacity(theme.centerChannelColor, 0.5), 2);
changeCss('.app__body .post-list__new-messages-below', 'background:' + changeColor(theme.centerChannelColor, 0.5), 2);
changeCss('.app__body .post.post--comment .post__body', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 1);
changeCss('.app__body .post.post--comment.current--user .post__body', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.13), 1);
changeCss('.app__body .post.post--comment.current--user .post__body', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 1);
}
if (theme.newMessageSeparator) {