Doug Lauder
2023-03-22 17:22:27 -04:00
коммит произвёл GitHub
родитель b61c096497
Коммит c943ed6859
13276 изменённых файлов: 1695615 добавлений и 223189 удалений

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@@ -0,0 +1,73 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {connect} from 'react-redux';
import {bindActionCreators, Dispatch} from 'redux';
import {savePreferences} from 'mattermost-redux/actions/preferences';
import {setStatus, unsetCustomStatus} from 'mattermost-redux/actions/users';
import {Client4} from 'mattermost-redux/client';
import {Preferences} from 'mattermost-redux/constants';
import {get, getBool, getInt} from 'mattermost-redux/selectors/entities/preferences';
import {getCurrentUser, getStatusForUserId} from 'mattermost-redux/selectors/entities/users';
import {openModal} from 'actions/views/modals';
import {setStatusDropdown} from 'actions/views/status_dropdown';
import {getCurrentUserTimezone} from 'selectors/general';
import {makeGetCustomStatus, isCustomStatusEnabled, showStatusDropdownPulsatingDot, isCustomStatusExpired} from 'selectors/views/custom_status';
import {isStatusDropdownOpen} from 'selectors/views/status_dropdown';
import {GenericAction} from 'mattermost-redux/types/actions';
import {GlobalState} from 'types/store';
import {
OnboardingTaskCategory,
OnboardingTasksName,
TaskNameMapToSteps,
} from 'components/onboarding_tasks';
import StatusDropdown from './status_dropdown';
function makeMapStateToProps() {
const getCustomStatus = makeGetCustomStatus();
return function mapStateToProps(state: GlobalState) {
const currentUser = getCurrentUser(state);
const userId = currentUser?.id;
const customStatus = getCustomStatus(state, userId);
const isMilitaryTime = getBool(state, Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.USE_MILITARY_TIME, false);
const step = getInt(state, OnboardingTaskCategory, OnboardingTasksName.COMPLETE_YOUR_PROFILE, 0);
const showCompleteYourProfileTour = step === TaskNameMapToSteps[OnboardingTasksName.COMPLETE_YOUR_PROFILE].STARTED;
return {
userId,
profilePicture: Client4.getProfilePictureUrl(userId, currentUser?.last_picture_update),
autoResetPref: get(state, Preferences.CATEGORY_AUTO_RESET_MANUAL_STATUS, userId, ''),
status: getStatusForUserId(state, userId),
customStatus,
currentUser,
isCustomStatusEnabled: isCustomStatusEnabled(state),
isCustomStatusExpired: isCustomStatusExpired(state, customStatus),
isMilitaryTime,
isStatusDropdownOpen: isStatusDropdownOpen(state),
showCustomStatusPulsatingDot: showStatusDropdownPulsatingDot(state),
showCompleteYourProfileTour,
timezone: getCurrentUserTimezone(state),
};
};
}
function mapDispatchToProps(dispatch: Dispatch<GenericAction>) {
return {
actions: bindActionCreators({
openModal,
setStatus,
unsetCustomStatus,
setStatusDropdown,
savePreferences,
}, dispatch),
};
}
export default connect(makeMapStateToProps, mapDispatchToProps)(StatusDropdown);

Просмотреть файл

@@ -0,0 +1,345 @@
#statusDropdownMenu {
.icon {
position: relative;
display: inline-flex;
width: 24px;
height: 24px;
justify-content: center;
padding: 0;
margin: 0 8px 0 0;
span {
display: flex;
}
svg {
width: 16px;
height: 16px;
}
}
#status-menu-dnd {
padding: 4px 16px;
.dropdown-menu {
margin: auto;
#dndSubMenu-header {
font-weight: bold;
pointer-events: none;
}
}
.icon {
top: 2px;
}
.SubMenu__icon-right {
padding-top: 5px;
}
}
}
#status-menu-dnd_menuitem {
.SubMenuItem.MenuItem {
flex-grow: 1;
}
.SubMenuItemContainer {
&:hover,
&:focus {
background-color: unset;
}
}
#dndTime-tomorrow .dndTime-tomorrow_timestamp {
&::before {
content: "\00a0";
}
margin-left: auto;
opacity: 0.7;
}
}
button.custom_status__row {
position: relative;
height: auto;
font-size: inherit;
&.flex {
display: flex;
}
&:hover {
background-color: rgba(var(--center-channel-color-rgb), 0.08);
.custom-status-emoji {
fill: rgba(var(--center-channel-color-rgb), 0.72);
}
}
&:active {
background-color: rgba(var(--button-bg-rgb), 0.08);
}
}
.custom_status {
&__container {
display: flex;
padding: 7px 0;
line-height: 20px;
}
&__icon {
margin-right: 12px;
}
&__text {
overflow: hidden;
max-width: 73%;
height: 16px;
line-height: 16px;
text-overflow: ellipsis;
white-space: nowrap;
}
&__expiry {
font-size: 12px;
line-height: 16px;
opacity: 0.7;
&.padded {
display: block;
padding-left: 28px;
}
}
}
#custom_status__clear {
position: absolute;
top: 0;
right: 10px;
width: auto;
padding: 0;
color: rgba(var(--center-channel-color-rgb), 0.52);
&:hover {
background: transparent;
}
}
.custom-status {
&-emoji {
fill: rgba(var(--center-channel-color-rgb), 0.56);
}
&-suggestion-emoji > span:first-child {
background-size: 20px;
}
}
.status-dropdown-menu-global-header {
padding: 4px 8px;
border-radius: 4px;
.icon > i:not(.status-icon) {
color: rgba(var(--center-channel-color-rgb), 0.56);
font-family: "compass-icons";
}
&:hover {
background: var(--sidebar-text-08);
cursor: pointer;
.status {
background-image:
linear-gradient(
0deg,
rgba(var(--global-header-text-rgb), 0.08),
rgba(var(--global-header-text-rgb), 0.08)
);
}
}
&.active {
background: rgba(var(--global-header-text-rgb), 0.16);
.status {
background-image:
linear-gradient(
0deg,
rgba(var(--global-header-text-rgb), 0.16),
rgba(var(--global-header-text-rgb), 0.16)
);
}
}
.Menu__content {
right: -1px;
left: inherit;
width: 264px;
}
.custom_status__row > #custom_status__clear {
top: 4px;
}
.custom_status__container {
align-items: center;
line-height: 14px;
}
.username-wrapper {
padding: 0 12px;
> p {
overflow: hidden;
max-width: 150px;
text-overflow: ellipsis;
white-space: nowrap;
&.bold {
font-weight: 600;
}
}
}
.MenuHeader {
height: 50px;
padding: 0 16px;
cursor: pointer;
&:hover {
background-color: rgba(var(--center-channel-color-rgb), 0.1);
}
}
.MenuItem > button,
#status-menu-dnd {
padding: 1px 16px;
.flex {
display: flex;
}
.MenuItem__primary-text,
.grid {
display: grid;
align-items: center;
grid-template-columns: auto 1fr auto auto auto;
.SubMenu__icon-right {
padding-top: 0;
padding-left: 12px;
}
}
> .MenuItem__help-text {
padding-left: 28px;
}
}
#statusDropdownMenu,
#statusDropdownMenu #status-menu-dnd {
.icon {
top: unset;
width: auto;
height: auto;
padding: 0 12px 0 0;
margin: 0;
}
.SubMenu__icon-right {
padding-top: 0;
}
}
.dnd--icon {
vertical-align: middle;
}
.status-wrapper {
display: flex;
height: auto;
align-items: center;
.status {
position: absolute;
top: auto;
right: -4px;
bottom: -4px;
display: inline-flex;
width: 16px;
height: 16px;
align-items: center;
justify-content: center;
background-color: var(--global-header-background);
color: var(--global-header-text);
&:hover {
cursor: pointer;
}
svg {
top: 0;
}
}
}
}
@keyframes pulse1 {
0% {
opacity: 1;
transform: scale(1);
}
80% {
opacity: 0;
transform: scale(2.5);
}
100% {
opacity: 0;
transform: scale(2.5);
}
}
@keyframes pulse2 {
0% {
opacity: 1;
transform: scale(1);
}
30% {
opacity: 1;
transform: scale(1);
}
100% {
opacity: 0;
transform: scale(2.5);
}
}
.status-dropdpwn-menu .status-dropdown-menu__clear-container .input-clear {
position: absolute;
color: rgba(var(--center-channel-color-rgb), 0.72);
opacity: 1;
}
.status-dropdpwn-account-settings {
position: relative;
}
#accountSettings {
position: relative;
}
.account-settings-complete {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
}

Просмотреть файл

@@ -0,0 +1,195 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
import {CustomStatusDuration, UserProfile} from '@mattermost/types/users';
import {fakeDate} from 'tests/helpers/date';
import {shallowWithIntl} from 'tests/helpers/intl-test-helper';
import StatusDropdown from './status_dropdown';
describe('components/StatusDropdown', () => {
let resetFakeDate: () => void;
beforeEach(() => {
resetFakeDate = fakeDate(new Date('2021-11-02T22:48:57Z'));
});
afterEach(() => {
resetFakeDate();
});
const actions = {
openModal: jest.fn(),
setStatus: jest.fn(),
unsetCustomStatus: jest.fn(),
setStatusDropdown: jest.fn(),
savePreferences: jest.fn(),
};
const baseProps = {
actions,
userId: '',
currentUser: {
id: 'user_id',
first_name: 'Nev',
last_name: 'Aa',
} as UserProfile,
userTimezone: {
useAutomaticTimezone: 'true',
automaticTimezone: 'America/New_York',
manualTimezone: '',
},
status: 'away',
isTimezoneEnabled: true,
isMilitaryTime: false,
isCustomStatusEnabled: false,
isCustomStatusExpired: false,
isStatusDropdownOpen: false,
showCustomStatusPulsatingDot: false,
showCompleteYourProfileTour: false,
};
test('should match snapshot in default state', () => {
const wrapper = shallowWithIntl(
<StatusDropdown {...baseProps}/>,
);
expect(wrapper).toMatchSnapshot();
});
test('should match snapshot with profile picture URL', () => {
const props = {
...baseProps,
profilePicture: 'http://localhost:8065/api/v4/users/jsx5jmdiyjyuzp9rzwfaf5pwjo/image?_=1590519110944',
};
const wrapper = shallowWithIntl(
<StatusDropdown {...props}/>,
);
expect(wrapper).toMatchSnapshot();
});
test('should match snapshot with status dropdown open', () => {
const props = {
...baseProps,
isStatusDropdownOpen: true,
};
const wrapper = shallowWithIntl(
<StatusDropdown {...props}/>,
);
expect(wrapper).toMatchSnapshot();
});
test('should match snapshot with custom status enabled', () => {
const props = {
...baseProps,
isStatusDropdownOpen: true,
isCustomStatusEnabled: true,
};
const wrapper = shallowWithIntl(
<StatusDropdown {...props}/>,
);
expect(wrapper).toMatchSnapshot();
});
test('should match snapshot with custom status pulsating dot enabled', () => {
const props = {
...baseProps,
isStatusDropdownOpen: true,
isCustomStatusEnabled: true,
showCustomStatusPulsatingDot: true,
};
const wrapper = shallowWithIntl(
<StatusDropdown {...props}/>,
);
expect(wrapper).toMatchSnapshot();
});
test('should match snapshot with custom status and expiry', () => {
const customStatus = {
emoji: 'calendar',
text: 'In a meeting',
duration: CustomStatusDuration.TODAY,
expires_at: '2021-05-03T23:59:59.000Z',
};
const props = {
...baseProps,
isStatusDropdownOpen: true,
isCustomStatusEnabled: true,
customStatus,
};
const wrapper = shallowWithIntl(
<StatusDropdown {...props}/>,
);
expect(wrapper).toMatchSnapshot();
});
test('should match snapshot with custom status expired', () => {
const customStatus = {
emoji: 'calendar',
text: 'In a meeting',
duration: CustomStatusDuration.TODAY,
expires_at: '2021-05-03T23:59:59.000Z',
};
const props = {
...baseProps,
isStatusDropdownOpen: true,
isCustomStatusEnabled: true,
isCustomStatusExpired: true,
customStatus,
};
const wrapper = shallowWithIntl(
<StatusDropdown {...props}/>,
);
expect(wrapper).toMatchSnapshot();
});
test('should show clear status button when custom status is set', () => {
const customStatus = {
emoji: 'calendar',
text: 'In a meeting',
duration: CustomStatusDuration.TODAY,
expires_at: '2021-05-03T23:59:59.000Z',
};
const props = {
...baseProps,
isStatusDropdownOpen: true,
isCustomStatusEnabled: true,
isCustomStatusExpired: false,
customStatus,
};
const wrapper = shallowWithIntl(
<StatusDropdown {...props}/>,
);
expect(wrapper.find('.status-dropdown-menu__clear-container').exists()).toBe(true);
expect(wrapper).toMatchSnapshot();
});
test('should not show clear status button when custom status is not set', () => {
const customStatus = undefined;
const props = {
...baseProps,
isStatusDropdownOpen: true,
isCustomStatusEnabled: true,
isCustomStatusExpired: false,
customStatus,
};
const wrapper = shallowWithIntl(
<StatusDropdown {...props}/>,
);
expect(wrapper.find('.status-dropdown-menu__clear-container').exists()).toBe(false);
expect(wrapper).toMatchSnapshot();
});
});

Просмотреть файл

@@ -0,0 +1,609 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {ReactNode} from 'react';
import {IntlShape, injectIntl, FormattedDate, FormattedMessage, FormattedTime} from 'react-intl';
import classNames from 'classnames';
import StatusIcon from '@mattermost/compass-components/components/status-icon';
import Text from '@mattermost/compass-components/components/text';
import Icon from '@mattermost/compass-components/foundations/icon/Icon';
import {TUserStatus} from '@mattermost/compass-components/shared';
import {ActionFunc} from 'mattermost-redux/types/actions';
import * as GlobalActions from 'actions/global_actions';
import CustomStatusEmoji from 'components/custom_status/custom_status_emoji';
import CustomStatusModal from 'components/custom_status/custom_status_modal';
import CustomStatusText from 'components/custom_status/custom_status_text';
import ExpiryTime from 'components/custom_status/expiry_time';
import DndCustomTimePicker from 'components/dnd_custom_time_picker_modal';
import OverlayTrigger from 'components/overlay_trigger';
import ResetStatusModal from 'components/reset_status_modal';
import UserSettingsModal from 'components/user_settings/modal';
import EmojiIcon from 'components/widgets/icons/emoji_icon';
import Menu from 'components/widgets/menu/menu';
import MenuWrapper from 'components/widgets/menu/menu_wrapper';
import Avatar, {TAvatarSizeToken} from 'components/widgets/users/avatar/avatar';
import {OnboardingTaskCategory, OnboardingTasksName, TaskNameMapToSteps, CompleteYourProfileTour} from 'components/onboarding_tasks';
import Tooltip from 'components/tooltip';
import {ModalData} from 'types/actions';
import {Constants, ModalIdentifiers, UserStatuses} from 'utils/constants';
import {t} from 'utils/i18n';
import {getCurrentDateTimeForTimezone, getCurrentMomentForTimezone} from 'utils/timezone';
import {localizeMessage} from 'utils/utils';
import {CustomStatusDuration, UserCustomStatus, UserProfile, UserStatus} from '@mattermost/types/users';
import {PulsatingDot} from '@mattermost/components';
import {PreferenceType} from '@mattermost/types/preferences';
import './status_dropdown.scss';
type Props = {
intl: IntlShape;
status?: string;
userId: string;
profilePicture?: string;
autoResetPref?: string;
actions: {
openModal: <P>(modalData: ModalData<P>) => void;
setStatus: (status: UserStatus) => ActionFunc;
unsetCustomStatus: () => ActionFunc;
savePreferences: (userId: string, preferences: PreferenceType[]) => void;
setStatusDropdown: (open: boolean) => void;
};
customStatus?: UserCustomStatus;
currentUser: UserProfile;
isCustomStatusEnabled: boolean;
isCustomStatusExpired: boolean;
isMilitaryTime: boolean;
isStatusDropdownOpen: boolean;
showCompleteYourProfileTour: boolean;
showCustomStatusPulsatingDot: boolean;
timezone?: string;
}
type State = {
openUp: boolean;
width: number;
isStatusSet: boolean;
};
export class StatusDropdown extends React.PureComponent<Props, State> {
dndTimes = [
{id: 'thirty_minutes', label: t('status_dropdown.dnd_sub_menu_item.thirty_minutes'), labelDefault: '30 mins'},
{id: 'one_hour', label: t('status_dropdown.dnd_sub_menu_item.one_hour'), labelDefault: '1 hour'},
{id: 'two_hours', label: t('status_dropdown.dnd_sub_menu_item.two_hours'), labelDefault: '2 hours'},
{id: 'tomorrow', label: t('status_dropdown.dnd_sub_menu_item.tomorrow'), labelDefault: 'Tomorrow'},
{id: 'custom', label: t('status_dropdown.dnd_sub_menu_item.custom'), labelDefault: 'Custom'},
];
static defaultProps = {
userId: '',
profilePicture: '',
}
constructor(props: Props) {
super(props);
this.state = {
openUp: false,
width: 0,
isStatusSet: false,
};
}
openProfileModal = (): void => {
this.props.actions.openModal({
modalId: ModalIdentifiers.USER_SETTINGS,
dialogType: UserSettingsModal,
dialogProps: {isContentProductSettings: false},
});
}
setStatus = (status: string, dndEndTime?: number): void => {
this.props.actions.setStatus({
user_id: this.props.userId,
status,
dnd_end_time: dndEndTime,
});
}
isUserOutOfOffice = (): boolean => {
return this.props.status === UserStatuses.OUT_OF_OFFICE;
}
setOnline = (event: Event): void => {
event.preventDefault();
this.setStatus(UserStatuses.ONLINE);
}
setOffline = (event: Event): void => {
event.preventDefault();
this.setStatus(UserStatuses.OFFLINE);
}
setAway = (event: Event): void => {
event.preventDefault();
this.setStatus(UserStatuses.AWAY);
}
setDnd = (index: number): void => {
const currentDate = getCurrentMomentForTimezone(this.props.timezone);
let endTime = currentDate;
switch (index) {
case 0:
// add 30 minutes in current time
endTime = currentDate.add(30, 'minutes');
break;
case 1:
// add 1 hour in current time
endTime = currentDate.add(1, 'hour');
break;
case 2:
// add 2 hours in current time
endTime = currentDate.add(2, 'hours');
break;
case 3:
// add one day in current date
endTime = currentDate.add(1, 'day');
break;
}
this.setStatus(UserStatuses.DND, endTime.utc().unix());
}
setCustomTimedDnd = (): void => {
const dndCustomTimePicker = {
modalId: ModalIdentifiers.DND_CUSTOM_TIME_PICKER,
dialogType: DndCustomTimePicker,
dialogProps: {
currentDate: this.props.timezone ? getCurrentDateTimeForTimezone(this.props.timezone) : new Date(),
},
};
this.props.actions.openModal(dndCustomTimePicker);
}
showStatusChangeConfirmation = (status: string): void => {
const resetStatusModalData = {
modalId: ModalIdentifiers.RESET_STATUS,
dialogType: ResetStatusModal,
dialogProps: {newStatus: status},
};
this.props.actions.openModal(resetStatusModalData);
};
renderProfilePicture = (size: TAvatarSizeToken): ReactNode => {
if (!this.props.profilePicture) {
return null;
}
return (
<Avatar
size={size}
url={this.props.profilePicture}
tabIndex={undefined}
/>
);
}
handleClearStatus = (e: React.MouseEvent<HTMLButtonElement> | React.MouseEvent<HTMLDivElement> | React.TouchEvent): void => {
e.stopPropagation();
e.preventDefault();
this.props.actions.unsetCustomStatus();
};
handleEmitUserLoggedOutEvent = (): void => {
GlobalActions.emitUserLoggedOutEvent();
}
onToggle = (open: boolean): void => {
this.props.actions.setStatusDropdown(open);
}
handleCompleteYourProfileTask = (): void => {
const taskName = OnboardingTasksName.COMPLETE_YOUR_PROFILE;
const steps = TaskNameMapToSteps[taskName];
const currentUserId = this.props.currentUser.id;
const preferences = [
{
user_id: currentUserId,
category: OnboardingTaskCategory,
name: taskName,
value: steps.FINISHED.toString(),
},
];
this.props.actions.savePreferences(currentUserId, preferences);
}
handleCustomStatusEmojiClick = (event: React.MouseEvent): void => {
event.stopPropagation();
const customStatusInputModalData = {
modalId: ModalIdentifiers.CUSTOM_STATUS,
dialogType: CustomStatusModal,
};
this.props.actions.openModal(customStatusInputModalData);
}
renderCustomStatus = (isStatusSet: boolean | undefined): ReactNode => {
if (!this.props.isCustomStatusEnabled) {
return null;
}
const {customStatus} = this.props;
let customStatusText;
let customStatusHelpText;
switch (true) {
case isStatusSet && customStatus?.text && customStatus.text.length > 0:
customStatusText = customStatus?.text;
break;
case isStatusSet && !customStatus?.text && customStatus?.duration === CustomStatusDuration.DONT_CLEAR:
customStatusHelpText = localizeMessage('status_dropdown.set_custom_text', 'Set Custom Status Text...');
break;
case isStatusSet && !customStatus?.text && customStatus?.duration !== CustomStatusDuration.DONT_CLEAR:
customStatusText = '';
break;
case !isStatusSet:
customStatusHelpText = localizeMessage('status_dropdown.set_custom', 'Set a Custom Status');
}
const customStatusEmoji = isStatusSet ? (
<span className='d-flex'>
<CustomStatusEmoji
showTooltip={false}
emojiStyle={{marginLeft: 0}}
/>
</span>
) : (
<EmojiIcon className={'custom-status-emoji'}/>
);
const pulsatingDot = !isStatusSet && this.props.showCustomStatusPulsatingDot && (
<PulsatingDot/>
);
const clearableTooltip = (
<Tooltip id={'InputClearTooltip'}>
<FormattedMessage
id={'input.clear'}
defaultMessage='Clear'
/>
</Tooltip>
);
const clearButton = isStatusSet && !pulsatingDot && (
<div
className={classNames('status-dropdown-menu__clear-container', 'input-clear visible')}
onClick={this.handleClearStatus}
onTouchEnd={this.handleClearStatus}
>
<OverlayTrigger
delayShow={Constants.OVERLAY_TIME_DELAY}
placement={'left'}
overlay={clearableTooltip}
>
<span
className='input-clear-x'
aria-hidden='true'
>
<i className='icon icon-close-circle'/>
</span>
</OverlayTrigger>
</div>
);
const expiryTime = isStatusSet && customStatus?.expires_at && customStatus.duration !== CustomStatusDuration.DONT_CLEAR &&
(
<ExpiryTime
time={customStatus.expires_at}
timezone={this.props.timezone}
className={classNames('custom_status__expiry', {
padded: customStatus?.text.length > 0,
})}
withinBrackets={true}
/>
);
return (
<Menu.Group>
<Menu.ItemToggleModalRedux
ariaLabel={customStatusText || customStatusHelpText}
modalId={ModalIdentifiers.CUSTOM_STATUS}
dialogType={CustomStatusModal}
className={classNames('MenuItem__primary-text custom_status__row', {
flex: customStatus?.text.length === 0,
})}
id={'status-menu-custom-status'}
>
<span className='custom_status__container'>
<span className='custom_status__icon'>
{customStatusEmoji}
</span>
<CustomStatusText
text={customStatusText}
className='custom_status__text'
/>
<Text
margin='none'
color='disabled'
>
{customStatusHelpText}
</Text>
{clearButton}
{pulsatingDot}
</span>
{expiryTime}
</Menu.ItemToggleModalRedux>
</Menu.Group>
);
}
render = (): JSX.Element => {
const {intl} = this.props;
const needsConfirm = this.isUserOutOfOffice() && this.props.autoResetPref === '';
const {status, customStatus, isCustomStatusExpired, currentUser} = this.props;
const isStatusSet = customStatus && (customStatus.text.length > 0 || customStatus.emoji.length > 0) && !isCustomStatusExpired;
const setOnline = needsConfirm ? () => this.showStatusChangeConfirmation('online') : this.setOnline;
const setDnd = needsConfirm ? () => this.showStatusChangeConfirmation('dnd') : this.setDnd;
const setAway = needsConfirm ? () => this.showStatusChangeConfirmation('away') : this.setAway;
const setOffline = needsConfirm ? () => this.showStatusChangeConfirmation('offline') : this.setOffline;
const setCustomTimedDnd = needsConfirm ? () => this.showStatusChangeConfirmation('dnd') : this.setCustomTimedDnd;
const selectedIndicator = (
<Icon
glyph={'check'}
size={16}
color={'success'}
/>
);
const dndSubMenuItems = [
{
id: 'dndSubMenu-header',
direction: 'right',
text: localizeMessage('status_dropdown.dnd_sub_menu_header', 'Disable notifications until:'),
isHeader: true,
} as any,
].concat(
this.dndTimes.map(({id, label, labelDefault}, index) => {
let text: React.ReactNode = localizeMessage(label, labelDefault);
if (index === 3) {
const tomorrow = getCurrentMomentForTimezone(this.props.timezone).add(1, 'day').toDate();
text = (
<>
{text}
<span className={`dndTime-${id}_timestamp`}>
<FormattedDate
value={tomorrow}
weekday='short'
timeZone={this.props.timezone}
/>
{', '}
<FormattedTime
value={tomorrow}
timeStyle='short'
hour12={!this.props.isMilitaryTime}
timeZone={this.props.timezone}
/>
</span>
</>
);
}
return {
id: `dndTime-${id}`,
direction: 'right',
text,
action:
index === 4 ? () => setCustomTimedDnd() : () => setDnd(index),
} as any;
}),
);
const customStatusComponent = this.renderCustomStatus(isStatusSet);
let menuAriaLabeltext;
switch (this.props.status) {
case UserStatuses.AWAY:
menuAriaLabeltext = intl.formatMessage({
id: 'status_dropdown.profile_button_label.away',
defaultMessage: 'Current status: Away. Select to open profile and status menu.',
});
break;
case UserStatuses.DND:
menuAriaLabeltext = intl.formatMessage({
id: 'status_dropdown.profile_button_label.dnd',
defaultMessage: 'Current status: Do not disturb. Select to open profile and status menu.',
});
break;
case UserStatuses.OFFLINE:
menuAriaLabeltext = intl.formatMessage({
id: 'status_dropdown.profile_button_label.offline',
defaultMessage: 'Current status: Offline. Select to open profile and status menu.',
});
break;
case UserStatuses.ONLINE:
menuAriaLabeltext = intl.formatMessage({
id: 'status_dropdown.profile_button_label.online',
defaultMessage: 'Current status: Online. Select to open profile and status menu.',
});
break;
case UserStatuses.OUT_OF_OFFICE:
menuAriaLabeltext = intl.formatMessage({
id: 'status_dropdown.profile_button_label.ooo',
defaultMessage: 'Current status: Out of office. Select to open profile and status menu.',
});
break;
default:
menuAriaLabeltext = intl.formatMessage({
id: 'status_dropdown.profile_button_label',
defaultMessage: 'Select to open profile and status menu.',
});
}
return (
<MenuWrapper
onToggle={this.onToggle}
open={this.props.isStatusDropdownOpen}
className={classNames('status-dropdown-menu status-dropdown-menu-global-header', {
active: this.props.isStatusDropdownOpen || isStatusSet,
})}
>
<button
className='status-wrapper style--none'
aria-label={menuAriaLabeltext}
aria-expanded={this.props.isStatusDropdownOpen}
aria-controls='statusDropdownMenu'
>
<CustomStatusEmoji
showTooltip={true}
tooltipDirection={'bottom'}
emojiStyle={{marginRight: '6px'}}
onClick={this.handleCustomStatusEmojiClick as () => void}
/>
{this.renderProfilePicture('sm')}
<div
className='status'
>
<StatusIcon
size={'sm'}
status={(this.props.status || 'offline') as TUserStatus}
/>
</div>
</button>
<Menu
ariaLabel={localizeMessage('status_dropdown.menuAriaLabel', 'Set a status')}
id={'statusDropdownMenu'}
listId={'status-drop-down-menu-list'}
>
{currentUser && (
<Menu.Header onClick={this.openProfileModal}>
{this.renderProfilePicture('lg')}
<div className={'username-wrapper'}>
<Text
className={'bold'}
margin={'none'}
>{`${currentUser.first_name} ${currentUser.last_name}`}</Text>
<Text
margin={'none'}
className={!currentUser.first_name && !currentUser.last_name ? 'bold' : ''}
color={!currentUser.first_name && !currentUser.last_name ? undefined : 'disabled'}
>
{'@' + currentUser.username}
</Text>
</div>
</Menu.Header>
)}
<Menu.Group>
<Menu.ItemAction
show={this.isUserOutOfOffice()}
onClick={() => null}
ariaLabel={localizeMessage('status_dropdown.set_ooo', 'Out of office').toLowerCase()}
text={localizeMessage('status_dropdown.set_ooo', 'Out of office')}
extraText={localizeMessage('status_dropdown.set_ooo.extra', 'Automatic Replies are enabled')}
/>
</Menu.Group>
{customStatusComponent}
<Menu.Group>
<Menu.ItemAction
onClick={setOnline}
ariaLabel={localizeMessage('status_dropdown.set_online', 'Online').toLowerCase()}
text={localizeMessage('status_dropdown.set_online', 'Online')}
icon={(
<StatusIcon
status={'online'}
className={'status-icon'}
/>
)}
rightDecorator={status === 'online' && selectedIndicator}
id={'status-menu-online'}
/>
<Menu.ItemAction
onClick={setAway}
ariaLabel={localizeMessage('status_dropdown.set_away', 'Away').toLowerCase()}
text={localizeMessage('status_dropdown.set_away', 'Away')}
icon={(
<StatusIcon
status={'away'}
className={'status-icon'}
/>
)}
rightDecorator={status === 'away' && selectedIndicator}
id={'status-menu-away'}
/>
<Menu.ItemSubMenu
subMenu={dndSubMenuItems}
ariaLabel={`${localizeMessage('status_dropdown.set_dnd', 'Do not disturb').toLowerCase()}. ${localizeMessage('status_dropdown.set_dnd.extra', 'Disables desktop, email and push notifications').toLowerCase()}`}
text={localizeMessage('status_dropdown.set_dnd', 'Do not disturb')}
extraText={localizeMessage('status_dropdown.set_dnd.extra', 'Disables all notifications')}
icon={(
<StatusIcon
status={'dnd'}
className={'status-icon'}
/>
)}
rightDecorator={status === 'dnd' && selectedIndicator}
direction={'left'}
openUp={this.state.openUp}
id={'status-menu-dnd'}
/>
<Menu.ItemAction
onClick={setOffline}
ariaLabel={localizeMessage('status_dropdown.set_offline', 'Offline').toLowerCase()}
text={localizeMessage('status_dropdown.set_offline', 'Offline')}
icon={(
<StatusIcon
status={'offline'}
className={'status-icon'}
/>
)}
rightDecorator={status === 'offline' && selectedIndicator}
id={'status-menu-offline'}
/>
</Menu.Group>
<Menu.Group>
<Menu.ItemToggleModalRedux
id='accountSettings'
ariaLabel='Profile'
modalId={ModalIdentifiers.USER_SETTINGS}
dialogType={UserSettingsModal}
dialogProps={{isContentProductSettings: false}}
text={localizeMessage('navbar_dropdown.profileSettings', 'Profile')}
icon={(
<Icon
size={16}
glyph={'account-outline'}
/>
)}
>
{this.props.showCompleteYourProfileTour && (
<div
onClick={this.handleCompleteYourProfileTask}
className={'account-settings-complete'}
>
<CompleteYourProfileTour/>
</div>
)}
</Menu.ItemToggleModalRedux>
</Menu.Group>
<Menu.Group>
<Menu.ItemAction
id='logout'
onClick={this.handleEmitUserLoggedOutEvent}
text={localizeMessage('navbar_dropdown.logout', 'Log Out')}
icon={(
<Icon
size={16}
glyph={'exit-to-app'}
/>
)}
/>
</Menu.Group>
</Menu>
</MenuWrapper>
);
}
}
export default injectIntl(StatusDropdown);