[MM-57080] Add Channel-specific notification sound options in new Notifications settings modal (#27730)
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
e121bebba5
Коммит
b0d58bb65e
@@ -144,8 +144,8 @@ describe('CRT Desktop notifications', () => {
|
|||||||
cy.visit(testChannelUrl);
|
cy.visit(testChannelUrl);
|
||||||
cy.uiOpenChannelMenu('Notification Preferences');
|
cy.uiOpenChannelMenu('Notification Preferences');
|
||||||
|
|
||||||
cy.get('.channel-notifications-settings-modal__body').scrollTo('center').get('#desktopNotification-mention').should('be.visible').click().then(() => {
|
cy.get('#desktopNotification-mention').scrollIntoView().should('be.visible').click().then(() => {
|
||||||
cy.get('[data-testid="desktopReplyThreads"]').should('be.visible').click();
|
cy.get('[data-testid="desktopReplyThreads"]').scrollIntoView().should('be.visible').click();
|
||||||
});
|
});
|
||||||
cy.get('.channel-notifications-settings-modal__body').scrollTo('center').get('[data-testid="desktopReplyThreads"]').should('be.visible').click();
|
cy.get('.channel-notifications-settings-modal__body').scrollTo('center').get('[data-testid="desktopReplyThreads"]').should('be.visible').click();
|
||||||
cy.get('.channel-notifications-settings-modal__body').get('[data-testid="sameMobileSettingsDesktop"]').scrollIntoView().click().should('be.checked').then(() => {
|
cy.get('.channel-notifications-settings-modal__body').get('[data-testid="sameMobileSettingsDesktop"]').scrollIntoView().click().should('be.checked').then(() => {
|
||||||
@@ -174,7 +174,7 @@ describe('CRT Desktop notifications', () => {
|
|||||||
// Setup notification spy
|
// Setup notification spy
|
||||||
spyNotificationAs('notifySpy', 'granted');
|
spyNotificationAs('notifySpy', 'granted');
|
||||||
cy.uiOpenChannelMenu('Notification Preferences');
|
cy.uiOpenChannelMenu('Notification Preferences');
|
||||||
cy.get('.channel-notifications-settings-modal__body').scrollTo('center').get('#desktopNotification-mention').should('be.visible').click();
|
cy.get('#desktopNotification-mention').scrollIntoView().should('be.visible').click();
|
||||||
|
|
||||||
// # Save the changes
|
// # Save the changes
|
||||||
cy.findByText('Save').should('be.visible').click();
|
cy.findByText('Save').should('be.visible').click();
|
||||||
|
|||||||
@@ -220,6 +220,87 @@ Object {
|
|||||||
</fieldset>
|
</fieldset>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
class="mm-modal-generic-section-item"
|
||||||
|
>
|
||||||
|
<h4
|
||||||
|
class="mm-modal-generic-section-item__title"
|
||||||
|
data-testid="mm-modal-generic-section-item__title"
|
||||||
|
>
|
||||||
|
Sounds
|
||||||
|
</h4>
|
||||||
|
<div
|
||||||
|
class="mm-modal-generic-section-item__content inline"
|
||||||
|
data-testid="mm-modal-generic-section-item__content"
|
||||||
|
>
|
||||||
|
<fieldset
|
||||||
|
class="mm-modal-generic-section-item__fieldset-checkbox-ctr"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="mm-modal-generic-section-item__fieldset-checkbox"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
checked=""
|
||||||
|
class="mm-modal-generic-section-item__input-checkbox"
|
||||||
|
data-testid="desktopNotificationSoundsCheckbox"
|
||||||
|
name="desktopNotificationSoundsCheckbox"
|
||||||
|
type="checkbox"
|
||||||
|
/>
|
||||||
|
Message notification sounds
|
||||||
|
</label>
|
||||||
|
</fieldset>
|
||||||
|
<fieldset
|
||||||
|
class="mm-modal-generic-section-item__fieldset-react-select"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="react-select singleSelect react-select-top css-2b097c-container"
|
||||||
|
id="desktopNotificationSoundsSelect"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="react-select__control css-yk16xz-control"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="react-select__value-container react-select__value-container--has-value css-1hwfws3"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="react-select__single-value css-1uccc91-singleValue"
|
||||||
|
>
|
||||||
|
Bing
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
class="css-62g3xt-dummyInput"
|
||||||
|
id="desktopNotificationSoundsSelectInputId"
|
||||||
|
readonly=""
|
||||||
|
tabindex="0"
|
||||||
|
value=""
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="react-select__indicators css-1hb7zxy-IndicatorsContainer"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
aria-hidden="true"
|
||||||
|
class="react-select__indicator react-select__dropdown-indicator css-tlfecz-indicatorContainer"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="css-6q0nyr-Svg"
|
||||||
|
focusable="false"
|
||||||
|
height="20"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
width="20"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M4.516 7.548c0.436-0.446 1.043-0.481 1.576 0l3.908 3.747 3.908-3.747c0.533-0.481 1.141-0.446 1.574 0 0.436 0.445 0.408 1.197 0 1.615-0.406 0.418-4.695 4.502-4.695 4.502-0.217 0.223-0.502 0.335-0.787 0.335s-0.57-0.112-0.789-0.335c0 0-4.287-4.084-4.695-4.502s-0.436-1.17 0-1.615z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<div
|
<div
|
||||||
@@ -237,6 +318,7 @@ Object {
|
|||||||
Mobile Notifications
|
Mobile Notifications
|
||||||
<button
|
<button
|
||||||
class="channel-notifications-settings-modal__reset-btn"
|
class="channel-notifications-settings-modal__reset-btn"
|
||||||
|
data-testid="resetToDefaultButton-push"
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
@@ -579,6 +661,7 @@ Object {
|
|||||||
Desktop Notifications
|
Desktop Notifications
|
||||||
<button
|
<button
|
||||||
class="channel-notifications-settings-modal__reset-btn"
|
class="channel-notifications-settings-modal__reset-btn"
|
||||||
|
data-testid="resetToDefaultButton-desktop"
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
@@ -678,6 +761,7 @@ Object {
|
|||||||
Mobile Notifications
|
Mobile Notifications
|
||||||
<button
|
<button
|
||||||
class="channel-notifications-settings-modal__reset-btn"
|
class="channel-notifications-settings-modal__reset-btn"
|
||||||
|
data-testid="resetToDefaultButton-push"
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
@@ -1085,6 +1169,87 @@ Object {
|
|||||||
</fieldset>
|
</fieldset>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
class="mm-modal-generic-section-item"
|
||||||
|
>
|
||||||
|
<h4
|
||||||
|
class="mm-modal-generic-section-item__title"
|
||||||
|
data-testid="mm-modal-generic-section-item__title"
|
||||||
|
>
|
||||||
|
Sounds
|
||||||
|
</h4>
|
||||||
|
<div
|
||||||
|
class="mm-modal-generic-section-item__content inline"
|
||||||
|
data-testid="mm-modal-generic-section-item__content"
|
||||||
|
>
|
||||||
|
<fieldset
|
||||||
|
class="mm-modal-generic-section-item__fieldset-checkbox-ctr"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="mm-modal-generic-section-item__fieldset-checkbox"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
checked=""
|
||||||
|
class="mm-modal-generic-section-item__input-checkbox"
|
||||||
|
data-testid="desktopNotificationSoundsCheckbox"
|
||||||
|
name="desktopNotificationSoundsCheckbox"
|
||||||
|
type="checkbox"
|
||||||
|
/>
|
||||||
|
Message notification sounds
|
||||||
|
</label>
|
||||||
|
</fieldset>
|
||||||
|
<fieldset
|
||||||
|
class="mm-modal-generic-section-item__fieldset-react-select"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="react-select singleSelect react-select-top css-2b097c-container"
|
||||||
|
id="desktopNotificationSoundsSelect"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="react-select__control css-yk16xz-control"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="react-select__value-container react-select__value-container--has-value css-1hwfws3"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="react-select__single-value css-1uccc91-singleValue"
|
||||||
|
>
|
||||||
|
Bing
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
class="css-62g3xt-dummyInput"
|
||||||
|
id="desktopNotificationSoundsSelectInputId"
|
||||||
|
readonly=""
|
||||||
|
tabindex="0"
|
||||||
|
value=""
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="react-select__indicators css-1hb7zxy-IndicatorsContainer"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
aria-hidden="true"
|
||||||
|
class="react-select__indicator react-select__dropdown-indicator css-tlfecz-indicatorContainer"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="css-6q0nyr-Svg"
|
||||||
|
focusable="false"
|
||||||
|
height="20"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
width="20"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M4.516 7.548c0.436-0.446 1.043-0.481 1.576 0l3.908 3.747 3.908-3.747c0.533-0.481 1.141-0.446 1.574 0 0.436 0.445 0.408 1.197 0 1.615-0.406 0.418-4.695 4.502-4.695 4.502-0.217 0.223-0.502 0.335-0.787 0.335s-0.57-0.112-0.789-0.335c0 0-4.287-4.084-4.695-4.502s-0.436-1.17 0-1.615z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<div
|
<div
|
||||||
@@ -1102,6 +1267,7 @@ Object {
|
|||||||
Mobile Notifications
|
Mobile Notifications
|
||||||
<button
|
<button
|
||||||
class="channel-notifications-settings-modal__reset-btn"
|
class="channel-notifications-settings-modal__reset-btn"
|
||||||
|
data-testid="resetToDefaultButton-push"
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
@@ -1764,6 +1930,87 @@ Object {
|
|||||||
</fieldset>
|
</fieldset>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
class="mm-modal-generic-section-item"
|
||||||
|
>
|
||||||
|
<h4
|
||||||
|
class="mm-modal-generic-section-item__title"
|
||||||
|
data-testid="mm-modal-generic-section-item__title"
|
||||||
|
>
|
||||||
|
Sounds
|
||||||
|
</h4>
|
||||||
|
<div
|
||||||
|
class="mm-modal-generic-section-item__content inline"
|
||||||
|
data-testid="mm-modal-generic-section-item__content"
|
||||||
|
>
|
||||||
|
<fieldset
|
||||||
|
class="mm-modal-generic-section-item__fieldset-checkbox-ctr"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="mm-modal-generic-section-item__fieldset-checkbox"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
checked=""
|
||||||
|
class="mm-modal-generic-section-item__input-checkbox"
|
||||||
|
data-testid="desktopNotificationSoundsCheckbox"
|
||||||
|
name="desktopNotificationSoundsCheckbox"
|
||||||
|
type="checkbox"
|
||||||
|
/>
|
||||||
|
Message notification sounds
|
||||||
|
</label>
|
||||||
|
</fieldset>
|
||||||
|
<fieldset
|
||||||
|
class="mm-modal-generic-section-item__fieldset-react-select"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="react-select singleSelect react-select-top css-2b097c-container"
|
||||||
|
id="desktopNotificationSoundsSelect"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="react-select__control css-yk16xz-control"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="react-select__value-container react-select__value-container--has-value css-1hwfws3"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="react-select__single-value css-1uccc91-singleValue"
|
||||||
|
>
|
||||||
|
Bing
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
class="css-62g3xt-dummyInput"
|
||||||
|
id="desktopNotificationSoundsSelectInputId"
|
||||||
|
readonly=""
|
||||||
|
tabindex="0"
|
||||||
|
value=""
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="react-select__indicators css-1hb7zxy-IndicatorsContainer"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
aria-hidden="true"
|
||||||
|
class="react-select__indicator react-select__dropdown-indicator css-tlfecz-indicatorContainer"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="css-6q0nyr-Svg"
|
||||||
|
focusable="false"
|
||||||
|
height="20"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
width="20"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M4.516 7.548c0.436-0.446 1.043-0.481 1.576 0l3.908 3.747 3.908-3.747c0.533-0.481 1.141-0.446 1.574 0 0.436 0.445 0.408 1.197 0 1.615-0.406 0.418-4.695 4.502-4.695 4.502-0.217 0.223-0.502 0.335-0.787 0.335s-0.57-0.112-0.789-0.335c0 0-4.287-4.084-4.695-4.502s-0.436-1.17 0-1.615z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<div
|
<div
|
||||||
@@ -1781,6 +2028,7 @@ Object {
|
|||||||
Mobile Notifications
|
Mobile Notifications
|
||||||
<button
|
<button
|
||||||
class="channel-notifications-settings-modal__reset-btn"
|
class="channel-notifications-settings-modal__reset-btn"
|
||||||
|
data-testid="resetToDefaultButton-push"
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
@@ -2132,6 +2380,87 @@ Object {
|
|||||||
</fieldset>
|
</fieldset>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
class="mm-modal-generic-section-item"
|
||||||
|
>
|
||||||
|
<h4
|
||||||
|
class="mm-modal-generic-section-item__title"
|
||||||
|
data-testid="mm-modal-generic-section-item__title"
|
||||||
|
>
|
||||||
|
Sounds
|
||||||
|
</h4>
|
||||||
|
<div
|
||||||
|
class="mm-modal-generic-section-item__content inline"
|
||||||
|
data-testid="mm-modal-generic-section-item__content"
|
||||||
|
>
|
||||||
|
<fieldset
|
||||||
|
class="mm-modal-generic-section-item__fieldset-checkbox-ctr"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="mm-modal-generic-section-item__fieldset-checkbox"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
checked=""
|
||||||
|
class="mm-modal-generic-section-item__input-checkbox"
|
||||||
|
data-testid="desktopNotificationSoundsCheckbox"
|
||||||
|
name="desktopNotificationSoundsCheckbox"
|
||||||
|
type="checkbox"
|
||||||
|
/>
|
||||||
|
Message notification sounds
|
||||||
|
</label>
|
||||||
|
</fieldset>
|
||||||
|
<fieldset
|
||||||
|
class="mm-modal-generic-section-item__fieldset-react-select"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="react-select singleSelect react-select-top css-2b097c-container"
|
||||||
|
id="desktopNotificationSoundsSelect"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="react-select__control css-yk16xz-control"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="react-select__value-container react-select__value-container--has-value css-1hwfws3"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="react-select__single-value css-1uccc91-singleValue"
|
||||||
|
>
|
||||||
|
Bing
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
class="css-62g3xt-dummyInput"
|
||||||
|
id="desktopNotificationSoundsSelectInputId"
|
||||||
|
readonly=""
|
||||||
|
tabindex="0"
|
||||||
|
value=""
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="react-select__indicators css-1hb7zxy-IndicatorsContainer"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
aria-hidden="true"
|
||||||
|
class="react-select__indicator react-select__dropdown-indicator css-tlfecz-indicatorContainer"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="css-6q0nyr-Svg"
|
||||||
|
focusable="false"
|
||||||
|
height="20"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
width="20"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M4.516 7.548c0.436-0.446 1.043-0.481 1.576 0l3.908 3.747 3.908-3.747c0.533-0.481 1.141-0.446 1.574 0 0.436 0.445 0.408 1.197 0 1.615-0.406 0.418-4.695 4.502-4.695 4.502-0.217 0.223-0.502 0.335-0.787 0.335s-0.57-0.112-0.789-0.335c0 0-4.287-4.084-4.695-4.502s-0.436-1.17 0-1.615z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<div
|
<div
|
||||||
@@ -2149,6 +2478,7 @@ Object {
|
|||||||
Mobile Notifications
|
Mobile Notifications
|
||||||
<button
|
<button
|
||||||
class="channel-notifications-settings-modal__reset-btn"
|
class="channel-notifications-settings-modal__reset-btn"
|
||||||
|
data-testid="resetToDefaultButton-push"
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ describe('components/channel_notifications_modal/ChannelNotificationsModal', ()
|
|||||||
channelMember: {
|
channelMember: {
|
||||||
notify_props: {
|
notify_props: {
|
||||||
desktop: NotificationLevels.ALL,
|
desktop: NotificationLevels.ALL,
|
||||||
|
desktop_sound: 'on',
|
||||||
|
desktop_notification_sound: 'Bing',
|
||||||
mark_unread: NotificationLevels.ALL,
|
mark_unread: NotificationLevels.ALL,
|
||||||
push: NotificationLevels.DEFAULT,
|
push: NotificationLevels.DEFAULT,
|
||||||
ignore_channel_mentions: IgnoreChannelMentions.DEFAULT,
|
ignore_channel_mentions: IgnoreChannelMentions.DEFAULT,
|
||||||
@@ -35,6 +37,8 @@ describe('components/channel_notifications_modal/ChannelNotificationsModal', ()
|
|||||||
id: 'current_user_id',
|
id: 'current_user_id',
|
||||||
notify_props: {
|
notify_props: {
|
||||||
desktop: NotificationLevels.ALL,
|
desktop: NotificationLevels.ALL,
|
||||||
|
desktop_sound: 'true',
|
||||||
|
desktop_notification_sound: 'Bing',
|
||||||
desktop_threads: NotificationLevels.ALL,
|
desktop_threads: NotificationLevels.ALL,
|
||||||
} as UserNotifyProps,
|
} as UserNotifyProps,
|
||||||
}),
|
}),
|
||||||
@@ -73,6 +77,8 @@ describe('components/channel_notifications_modal/ChannelNotificationsModal', ()
|
|||||||
ignore_channel_mentions: 'off',
|
ignore_channel_mentions: 'off',
|
||||||
mark_unread: 'mention',
|
mark_unread: 'mention',
|
||||||
push: 'all',
|
push: 'all',
|
||||||
|
desktop_sound: 'on',
|
||||||
|
desktop_notification_sound: 'Bing',
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -98,6 +104,8 @@ describe('components/channel_notifications_modal/ChannelNotificationsModal', ()
|
|||||||
mark_unread:
|
mark_unread:
|
||||||
baseProps.channelMember?.notify_props.mark_unread,
|
baseProps.channelMember?.notify_props.mark_unread,
|
||||||
push: 'all',
|
push: 'all',
|
||||||
|
desktop_sound: 'on',
|
||||||
|
desktop_notification_sound: 'Bing',
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -139,12 +147,52 @@ describe('components/channel_notifications_modal/ChannelNotificationsModal', ()
|
|||||||
ignore_channel_mentions: 'off',
|
ignore_channel_mentions: 'off',
|
||||||
mark_unread: 'all',
|
mark_unread: 'all',
|
||||||
push: 'all',
|
push: 'all',
|
||||||
|
desktop_sound: 'on',
|
||||||
|
desktop_notification_sound: 'Bing',
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
expect(wrapper).toMatchSnapshot();
|
expect(wrapper).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should disable message notification sound if option is unchecked', async () => {
|
||||||
|
renderWithContext(<ChannelNotificationsModal {...baseProps}/>);
|
||||||
|
|
||||||
|
// Since the default value is on, we will uncheck the checkbox
|
||||||
|
fireEvent.click(screen.getByTestId('desktopNotificationSoundsCheckbox'));
|
||||||
|
expect(screen.getByTestId('desktopNotificationSoundsCheckbox')).not.toBeChecked();
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole('button', {name: /Save/i}));
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(baseProps.actions.updateChannelNotifyProps).toHaveBeenCalledWith(
|
||||||
|
'current_user_id',
|
||||||
|
'channel_id',
|
||||||
|
{
|
||||||
|
desktop: 'all',
|
||||||
|
ignore_channel_mentions: 'off',
|
||||||
|
mark_unread: 'all',
|
||||||
|
push: 'all',
|
||||||
|
desktop_sound: 'off',
|
||||||
|
desktop_notification_sound: 'Bing',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should default to user desktop notification sound if reset to default is clicked', async () => {
|
||||||
|
renderWithContext(<ChannelNotificationsModal {...baseProps}/>);
|
||||||
|
|
||||||
|
// Since the default value is on, we will uncheck the checkbox
|
||||||
|
fireEvent.click(screen.getByTestId('desktopNotificationSoundsCheckbox'));
|
||||||
|
expect(screen.getByTestId('desktopNotificationSoundsCheckbox')).not.toBeChecked();
|
||||||
|
|
||||||
|
// Reset to default button is clicked
|
||||||
|
fireEvent.click(screen.getByTestId('resetToDefaultButton-desktop'));
|
||||||
|
|
||||||
|
// Verify that the checkbox is checked to default to user desktop notification sound
|
||||||
|
expect(screen.getByTestId('desktopNotificationSoundsCheckbox')).toBeChecked();
|
||||||
|
});
|
||||||
|
|
||||||
test('should save the options exactly same as Desktop for mobile if use same as desktop checkbox is checked', async () => {
|
test('should save the options exactly same as Desktop for mobile if use same as desktop checkbox is checked', async () => {
|
||||||
const wrapper = renderWithContext(
|
const wrapper = renderWithContext(
|
||||||
<ChannelNotificationsModal {...baseProps}/>,
|
<ChannelNotificationsModal {...baseProps}/>,
|
||||||
@@ -170,6 +218,8 @@ describe('components/channel_notifications_modal/ChannelNotificationsModal', ()
|
|||||||
ignore_channel_mentions: 'off',
|
ignore_channel_mentions: 'off',
|
||||||
mark_unread: 'all',
|
mark_unread: 'all',
|
||||||
push: 'all',
|
push: 'all',
|
||||||
|
desktop_sound: 'on',
|
||||||
|
desktop_notification_sound: 'Bing',
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -209,6 +259,8 @@ describe('components/channel_notifications_modal/ChannelNotificationsModal', ()
|
|||||||
ignore_channel_mentions: 'off',
|
ignore_channel_mentions: 'off',
|
||||||
mark_unread: 'all',
|
mark_unread: 'all',
|
||||||
push: 'none',
|
push: 'none',
|
||||||
|
desktop_sound: 'on',
|
||||||
|
desktop_notification_sound: 'Bing',
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -240,6 +292,8 @@ describe('components/channel_notifications_modal/ChannelNotificationsModal', ()
|
|||||||
push: 'all',
|
push: 'all',
|
||||||
push_threads: 'default',
|
push_threads: 'default',
|
||||||
desktop_threads: 'all',
|
desktop_threads: 'all',
|
||||||
|
desktop_sound: 'on',
|
||||||
|
desktop_notification_sound: 'Bing',
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
import React, {useCallback, useState} from 'react';
|
import React, {useCallback, useState} from 'react';
|
||||||
import {Modal} from 'react-bootstrap';
|
import {Modal} from 'react-bootstrap';
|
||||||
import {FormattedMessage, useIntl} from 'react-intl';
|
import {FormattedMessage, useIntl} from 'react-intl';
|
||||||
|
import type {ValueType} from 'react-select';
|
||||||
|
|
||||||
import {BellOffOutlineIcon, RefreshIcon} from '@mattermost/compass-icons/components';
|
import {BellOffOutlineIcon, RefreshIcon} from '@mattermost/compass-icons/components';
|
||||||
import type {Channel, ChannelNotifyProps} from '@mattermost/types/channels';
|
import type {Channel, ChannelNotifyProps} from '@mattermost/types/channels';
|
||||||
@@ -11,14 +12,17 @@ import type {UserNotifyProps, UserProfile} from '@mattermost/types/users';
|
|||||||
|
|
||||||
import AlertBanner from 'components/alert_banner';
|
import AlertBanner from 'components/alert_banner';
|
||||||
import CheckboxSettingItem from 'components/widgets/modals/components/checkbox_setting_item';
|
import CheckboxSettingItem from 'components/widgets/modals/components/checkbox_setting_item';
|
||||||
|
import CheckboxWithSelectSettingItem from 'components/widgets/modals/components/checkbox_with_select_item';
|
||||||
import ModalHeader from 'components/widgets/modals/components/modal_header';
|
import ModalHeader from 'components/widgets/modals/components/modal_header';
|
||||||
import ModalSection from 'components/widgets/modals/components/modal_section';
|
import ModalSection from 'components/widgets/modals/components/modal_section';
|
||||||
import RadioSettingItem from 'components/widgets/modals/components/radio_setting_item';
|
import RadioSettingItem from 'components/widgets/modals/components/radio_setting_item';
|
||||||
|
import type {Option} from 'components/widgets/modals/components/react_select_item';
|
||||||
|
|
||||||
import {IgnoreChannelMentions, NotificationLevels} from 'utils/constants';
|
import {IgnoreChannelMentions, NotificationLevels, DesktopSound} from 'utils/constants';
|
||||||
|
import {getValueOfNotificationSoundsSelect, notificationSoundKeys, stopTryNotificationRing, tryNotificationSound} from 'utils/notification_sounds';
|
||||||
|
|
||||||
import type {ChannelMemberNotifyProps} from './utils';
|
import type {ChannelMemberNotifyProps} from './utils';
|
||||||
import utils from './utils';
|
import utils, {convertDesktopSoundNotifyPropFromUserToDesktop} from './utils';
|
||||||
|
|
||||||
import type {PropsFromRedux} from './index';
|
import type {PropsFromRedux} from './index';
|
||||||
|
|
||||||
@@ -63,9 +67,27 @@ function getStateFromNotifyProps(currentUserNotifyProps: UserNotifyProps, channe
|
|||||||
const desktop = channelMemberNotifyProps?.desktop === NotificationLevels.DEFAULT ? currentUserNotifyProps.desktop : (channelMemberNotifyProps?.desktop || currentUserNotifyProps.desktop);
|
const desktop = channelMemberNotifyProps?.desktop === NotificationLevels.DEFAULT ? currentUserNotifyProps.desktop : (channelMemberNotifyProps?.desktop || currentUserNotifyProps.desktop);
|
||||||
const push = channelMemberNotifyProps?.push === NotificationLevels.DEFAULT ? currentUserNotifyProps.desktop : (channelMemberNotifyProps?.push || currentUserNotifyProps.push);
|
const push = channelMemberNotifyProps?.push === NotificationLevels.DEFAULT ? currentUserNotifyProps.desktop : (channelMemberNotifyProps?.push || currentUserNotifyProps.push);
|
||||||
|
|
||||||
|
let desktopSound;
|
||||||
|
if (channelMemberNotifyProps && channelMemberNotifyProps.desktop_sound) {
|
||||||
|
desktopSound = channelMemberNotifyProps.desktop_sound;
|
||||||
|
} else {
|
||||||
|
desktopSound = convertDesktopSoundNotifyPropFromUserToDesktop(currentUserNotifyProps.desktop_sound);
|
||||||
|
}
|
||||||
|
|
||||||
|
let desktopNotificationSound;
|
||||||
|
if (channelMemberNotifyProps && channelMemberNotifyProps.desktop_notification_sound) {
|
||||||
|
desktopNotificationSound = channelMemberNotifyProps.desktop_notification_sound;
|
||||||
|
} else if (currentUserNotifyProps && currentUserNotifyProps.desktop_notification_sound) {
|
||||||
|
desktopNotificationSound = currentUserNotifyProps.desktop_notification_sound;
|
||||||
|
} else {
|
||||||
|
desktopNotificationSound = notificationSoundKeys[0] as ChannelNotifyProps['desktop_notification_sound'];
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
desktop,
|
desktop,
|
||||||
desktop_threads: channelMemberNotifyProps?.desktop_threads || NotificationLevels.ALL,
|
desktop_threads: channelMemberNotifyProps?.desktop_threads || NotificationLevels.ALL,
|
||||||
|
desktop_sound: desktopSound,
|
||||||
|
desktop_notification_sound: desktopNotificationSound,
|
||||||
mark_unread: channelMemberNotifyProps?.mark_unread || NotificationLevels.ALL,
|
mark_unread: channelMemberNotifyProps?.mark_unread || NotificationLevels.ALL,
|
||||||
push,
|
push,
|
||||||
push_threads: channelMemberNotifyProps?.push_threads || NotificationLevels.ALL,
|
push_threads: channelMemberNotifyProps?.push_threads || NotificationLevels.ALL,
|
||||||
@@ -77,6 +99,8 @@ function getStateFromNotifyProps(currentUserNotifyProps: UserNotifyProps, channe
|
|||||||
type SettingsType = {
|
type SettingsType = {
|
||||||
desktop: ChannelNotifyProps['desktop'];
|
desktop: ChannelNotifyProps['desktop'];
|
||||||
desktop_threads: ChannelNotifyProps['desktop_threads'];
|
desktop_threads: ChannelNotifyProps['desktop_threads'];
|
||||||
|
desktop_sound: ChannelNotifyProps['desktop_sound'];
|
||||||
|
desktop_notification_sound: ChannelNotifyProps['desktop_notification_sound'];
|
||||||
mark_unread: ChannelNotifyProps['mark_unread'];
|
mark_unread: ChannelNotifyProps['mark_unread'];
|
||||||
push: ChannelNotifyProps['push'];
|
push: ChannelNotifyProps['push'];
|
||||||
push_threads: ChannelNotifyProps['push_threads'];
|
push_threads: ChannelNotifyProps['push_threads'];
|
||||||
@@ -104,7 +128,7 @@ export default function ChannelNotificationsModal(props: Props) {
|
|||||||
setSettings((prevSettings) => ({...prevSettings, push: prevSettings.desktop, push_threads: prevSettings.desktop_threads}));
|
setSettings((prevSettings) => ({...prevSettings, push: prevSettings.desktop, push_threads: prevSettings.desktop_threads}));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const MuteOrIgnoreSectionContent = (
|
const muteOrIgnoreSectionContent = (
|
||||||
<>
|
<>
|
||||||
<CheckboxSettingItem
|
<CheckboxSettingItem
|
||||||
inputFieldTitle={
|
inputFieldTitle={
|
||||||
@@ -139,7 +163,16 @@ export default function ChannelNotificationsModal(props: Props) {
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
const DesktopNotificationsSectionContent = (
|
const handleChangeForMessageNotificationSoundSelect = (selectedOption: ValueType<Option>) => {
|
||||||
|
stopTryNotificationRing();
|
||||||
|
|
||||||
|
if (selectedOption && 'value' in selectedOption) {
|
||||||
|
handleChange({desktop_notification_sound: ((selectedOption as Option).value)});
|
||||||
|
tryNotificationSound(selectedOption.value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const desktopNotificationsSectionContent = (
|
||||||
<>
|
<>
|
||||||
<RadioSettingItem
|
<RadioSettingItem
|
||||||
title={formatMessage({
|
title={formatMessage({
|
||||||
@@ -165,11 +198,37 @@ export default function ChannelNotificationsModal(props: Props) {
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
handleChange={(e) => handleChange({desktop_threads: e ? 'all' : 'mention'})}
|
handleChange={(e) => handleChange({desktop_threads: e ? 'all' : 'mention'})}
|
||||||
/>}
|
/>
|
||||||
|
}
|
||||||
|
{settings.desktop !== 'none' && (
|
||||||
|
<CheckboxWithSelectSettingItem
|
||||||
|
title={formatMessage({
|
||||||
|
id: 'channel_notifications.desktopNotifications.title',
|
||||||
|
defaultMessage: 'Sounds',
|
||||||
|
})}
|
||||||
|
checkboxFieldTitle={
|
||||||
|
<FormattedMessage
|
||||||
|
id='channel_notifications.desktopNotifications.soundEnable'
|
||||||
|
defaultMessage='Message notification sounds'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
checkboxFieldValue={settings.desktop_sound === DesktopSound.ON}
|
||||||
|
checkboxFieldData={utils.desktopNotificationSoundsCheckboxFieldData}
|
||||||
|
handleCheckboxChange={(isChecked) => handleChange({desktop_sound: isChecked ? DesktopSound.ON : DesktopSound.OFF})}
|
||||||
|
selectFieldData={utils.desktopNotificationSoundsSelectFieldData}
|
||||||
|
selectFieldValue={getValueOfNotificationSoundsSelect(settings.desktop_notification_sound)}
|
||||||
|
isSelectDisabled={settings.desktop_sound !== 'on'}
|
||||||
|
selectPlaceholder={formatMessage({
|
||||||
|
id: 'channel_notifications.desktopNotifications.soundSelectPlaceholder',
|
||||||
|
defaultMessage: 'Select a sound',
|
||||||
|
})}
|
||||||
|
handleSelectChange={handleChangeForMessageNotificationSoundSelect}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
const MobileNotificationsSectionContent = (
|
const mobileNotificationsSectionContent = (
|
||||||
<>
|
<>
|
||||||
<CheckboxSettingItem
|
<CheckboxSettingItem
|
||||||
inputFieldTitle={
|
inputFieldTitle={
|
||||||
@@ -214,7 +273,7 @@ export default function ChannelNotificationsModal(props: Props) {
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
const AutoFollowThreadsSectionContent = (
|
const autoFollowThreadsSectionContent = (
|
||||||
<CheckboxSettingItem
|
<CheckboxSettingItem
|
||||||
inputFieldTitle={
|
inputFieldTitle={
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
@@ -245,39 +304,57 @@ export default function ChannelNotificationsModal(props: Props) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const resetToDefaultBtn = useCallback((settingName: string) => {
|
const resetToDefaultBtn = useCallback((sectionName: string) => {
|
||||||
const defaultSettings = props.currentUser.notify_props;
|
const userNotifyProps = {
|
||||||
|
...props.currentUser.notify_props,
|
||||||
const resetToDefault = (settingName: string) => {
|
desktop_notification_sound: props.currentUser.notify_props?.desktop_notification_sound ?? notificationSoundKeys[0] as ChannelNotifyProps['desktop_notification_sound'],
|
||||||
if (settingName === 'desktop') {
|
|
||||||
setSettings({...settings, desktop: defaultSettings.desktop, desktop_threads: defaultSettings.desktop_threads || settings.desktop_threads});
|
|
||||||
}
|
|
||||||
if (settingName === 'push') {
|
|
||||||
setSettings({...settings, push: defaultSettings.desktop, push_threads: defaultSettings.push_threads || settings.push_threads});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const isDesktopSameAsDefault = (defaultSettings.desktop === settings.desktop && defaultSettings.desktop_threads === settings.desktop_threads);
|
function resetToDefault(sectionName: string) {
|
||||||
const isPushSameAsDefault = (defaultSettings.push === settings.push && defaultSettings.push_threads === settings.push_threads);
|
if (sectionName === 'desktop') {
|
||||||
if ((settingName === 'desktop' && isDesktopSameAsDefault) || (settingName === 'push' && isPushSameAsDefault)) {
|
setSettings({
|
||||||
return <></>;
|
...settings,
|
||||||
|
desktop: userNotifyProps.desktop,
|
||||||
|
desktop_threads: userNotifyProps.desktop_threads || settings.desktop_threads,
|
||||||
|
desktop_sound: convertDesktopSoundNotifyPropFromUserToDesktop(userNotifyProps.desktop_sound),
|
||||||
|
desktop_notification_sound: userNotifyProps?.desktop_notification_sound ?? notificationSoundKeys[0] as ChannelNotifyProps['desktop_notification_sound'],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sectionName === 'push') {
|
||||||
|
setSettings({...settings, push: userNotifyProps.desktop, push_threads: userNotifyProps.push_threads || settings.push_threads});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isDesktopSameAsDefault =
|
||||||
|
userNotifyProps.desktop === settings.desktop &&
|
||||||
|
userNotifyProps.desktop_threads === settings.desktop_threads &&
|
||||||
|
userNotifyProps.desktop_notification_sound === settings.desktop_notification_sound &&
|
||||||
|
convertDesktopSoundNotifyPropFromUserToDesktop(userNotifyProps.desktop_sound) === settings.desktop_sound;
|
||||||
|
|
||||||
|
const isPushSameAsDefault = (userNotifyProps.push === settings.push && userNotifyProps.push_threads === settings.push_threads);
|
||||||
|
|
||||||
|
if ((sectionName === 'desktop' && isDesktopSameAsDefault) || (sectionName === 'push' && isPushSameAsDefault)) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
className='channel-notifications-settings-modal__reset-btn'
|
className='channel-notifications-settings-modal__reset-btn'
|
||||||
onClick={() => resetToDefault(settingName)}
|
onClick={() => resetToDefault(sectionName)}
|
||||||
|
data-testid={`resetToDefaultButton-${sectionName}`}
|
||||||
>
|
>
|
||||||
<RefreshIcon
|
<RefreshIcon
|
||||||
size={14}
|
size={14}
|
||||||
color={'currentColor'}
|
color={'currentColor'}
|
||||||
/>
|
/>
|
||||||
{formatMessage({
|
<FormattedMessage
|
||||||
id: 'channel_notifications.resetToDefault',
|
id='channel_notifications.resetToDefault'
|
||||||
defaultMessage: 'Reset to default',
|
defaultMessage='Reset to default'
|
||||||
})}
|
/>
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
}, [props.currentUser, settings]);
|
}, [props.currentUser.notify_props, settings]);
|
||||||
|
|
||||||
const desktopAndMobileNotificationSectionContent = settings.mark_unread === 'all' ? (
|
const desktopAndMobileNotificationSectionContent = settings.mark_unread === 'all' ? (
|
||||||
<>
|
<>
|
||||||
@@ -292,7 +369,7 @@ export default function ChannelNotificationsModal(props: Props) {
|
|||||||
id: 'channel_notifications.desktopNotificationsDesc',
|
id: 'channel_notifications.desktopNotificationsDesc',
|
||||||
defaultMessage: 'Available on Chrome, Edge, Firefox, and the Mattermost Desktop App.',
|
defaultMessage: 'Available on Chrome, Edge, Firefox, and the Mattermost Desktop App.',
|
||||||
})}
|
})}
|
||||||
content={DesktopNotificationsSectionContent}
|
content={desktopNotificationsSectionContent}
|
||||||
/>
|
/>
|
||||||
<div className='channel-notifications-settings-modal__divider'/>
|
<div className='channel-notifications-settings-modal__divider'/>
|
||||||
<ModalSection
|
<ModalSection
|
||||||
@@ -305,7 +382,7 @@ export default function ChannelNotificationsModal(props: Props) {
|
|||||||
id: 'channel_notifications.mobileNotificationsDesc',
|
id: 'channel_notifications.mobileNotificationsDesc',
|
||||||
defaultMessage: 'Notification alerts are pushed to your mobile device when there is activity in Mattermost.',
|
defaultMessage: 'Notification alerts are pushed to your mobile device when there is activity in Mattermost.',
|
||||||
})}
|
})}
|
||||||
content={MobileNotificationsSectionContent}
|
content={mobileNotificationsSectionContent}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
@@ -359,7 +436,7 @@ export default function ChannelNotificationsModal(props: Props) {
|
|||||||
id: 'channel_notifications.muteAndIgnore',
|
id: 'channel_notifications.muteAndIgnore',
|
||||||
defaultMessage: 'Mute or ignore',
|
defaultMessage: 'Mute or ignore',
|
||||||
})}
|
})}
|
||||||
content={MuteOrIgnoreSectionContent}
|
content={muteOrIgnoreSectionContent}
|
||||||
/>
|
/>
|
||||||
{desktopAndMobileNotificationSectionContent}
|
{desktopAndMobileNotificationSectionContent}
|
||||||
{props.collapsedReplyThreads &&
|
{props.collapsedReplyThreads &&
|
||||||
@@ -374,7 +451,7 @@ export default function ChannelNotificationsModal(props: Props) {
|
|||||||
id: 'channel_notifications.autoFollowThreadsDesc',
|
id: 'channel_notifications.autoFollowThreadsDesc',
|
||||||
defaultMessage: 'When enabled, all new replies in this channel will be automatically followed and will appear in your Threads view.',
|
defaultMessage: 'When enabled, all new replies in this channel will be automatically followed and will appear in your Threads view.',
|
||||||
})}
|
})}
|
||||||
content={AutoFollowThreadsSectionContent}
|
content={autoFollowThreadsSectionContent}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
@@ -407,3 +484,4 @@ export default function ChannelNotificationsModal(props: Props) {
|
|||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,8 +9,10 @@ import type {UserNotifyProps} from '@mattermost/types/users';
|
|||||||
|
|
||||||
import type {FieldsetCheckbox} from 'components/widgets/modals/components/checkbox_setting_item';
|
import type {FieldsetCheckbox} from 'components/widgets/modals/components/checkbox_setting_item';
|
||||||
import type {FieldsetRadio} from 'components/widgets/modals/components/radio_setting_item';
|
import type {FieldsetRadio} from 'components/widgets/modals/components/radio_setting_item';
|
||||||
|
import type {FieldsetReactSelect} from 'components/widgets/modals/components/react_select_item';
|
||||||
|
|
||||||
import {NotificationLevels} from 'utils/constants';
|
import {DesktopSound, NotificationLevels} from 'utils/constants';
|
||||||
|
import {optionsOfMessageNotificationSoundsSelect} from 'utils/notification_sounds';
|
||||||
|
|
||||||
export type ChannelMemberNotifyProps = Partial<ChannelNotifyProps> & Pick<UserNotifyProps, 'desktop_threads' | 'push_threads'>
|
export type ChannelMemberNotifyProps = Partial<ChannelNotifyProps> & Pick<UserNotifyProps, 'desktop_threads' | 'push_threads'>
|
||||||
|
|
||||||
@@ -102,6 +104,17 @@ export const desktopNotificationInputFieldData = (defaultOption: string): Fields
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const desktopNotificationSoundsCheckboxFieldData: FieldsetCheckbox = {
|
||||||
|
name: 'desktopNotificationSoundsCheckbox',
|
||||||
|
dataTestId: 'desktopNotificationSoundsCheckbox',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const desktopNotificationSoundsSelectFieldData: FieldsetReactSelect = {
|
||||||
|
id: 'desktopNotificationSoundsSelect',
|
||||||
|
inputId: 'desktopNotificationSoundsSelectInputId',
|
||||||
|
options: optionsOfMessageNotificationSoundsSelect,
|
||||||
|
};
|
||||||
|
|
||||||
export const mobileNotificationInputFieldData = (defaultOption: string): FieldsetRadio => {
|
export const mobileNotificationInputFieldData = (defaultOption: string): FieldsetRadio => {
|
||||||
return {
|
return {
|
||||||
options: [
|
options: [
|
||||||
@@ -160,8 +173,26 @@ export const mobileNotificationInputFieldData = (defaultOption: string): Fieldse
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This conversion is needed because User's preference for desktop sound is stored as either true or false. On the other hand,
|
||||||
|
* Channel's specific desktop sound is stored as either On or Off.
|
||||||
|
*/
|
||||||
|
export function convertDesktopSoundNotifyPropFromUserToDesktop(userNotifyDesktopSound?: UserNotifyProps['desktop_sound']) {
|
||||||
|
if (!userNotifyDesktopSound) {
|
||||||
|
return DesktopSound.OFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userNotifyDesktopSound === 'true') {
|
||||||
|
return DesktopSound.ON;
|
||||||
|
}
|
||||||
|
|
||||||
|
return DesktopSound.ON;
|
||||||
|
}
|
||||||
|
|
||||||
const utils = {
|
const utils = {
|
||||||
desktopNotificationInputFieldData,
|
desktopNotificationInputFieldData,
|
||||||
|
desktopNotificationSoundsCheckboxFieldData,
|
||||||
|
desktopNotificationSoundsSelectFieldData,
|
||||||
IgnoreMentionsInputFieldData,
|
IgnoreMentionsInputFieldData,
|
||||||
mobileNotificationInputFieldData,
|
mobileNotificationInputFieldData,
|
||||||
MuteChannelInputFieldData,
|
MuteChannelInputFieldData,
|
||||||
|
|||||||
@@ -15,11 +15,15 @@ import type SettingItemMinComponent from 'components/setting_item_min';
|
|||||||
|
|
||||||
import {UserSettingsNotificationSections} from 'utils/constants';
|
import {UserSettingsNotificationSections} from 'utils/constants';
|
||||||
import {
|
import {
|
||||||
callsNotificationSounds,
|
notificationSoundKeys,
|
||||||
notificationSounds,
|
|
||||||
stopTryNotificationRing,
|
stopTryNotificationRing,
|
||||||
tryNotificationSound,
|
tryNotificationSound,
|
||||||
tryNotificationRing,
|
tryNotificationRing,
|
||||||
|
getValueOfNotificationSoundsSelect,
|
||||||
|
getValueOfIncomingCallSoundsSelect,
|
||||||
|
optionsOfMessageNotificationSoundsSelect,
|
||||||
|
optionsOfIncomingCallSoundsSelect,
|
||||||
|
callNotificationSoundKeys,
|
||||||
} from 'utils/notification_sounds';
|
} from 'utils/notification_sounds';
|
||||||
|
|
||||||
import type {Props as UserSettingsNotificationsProps} from '../user_settings_notifications';
|
import type {Props as UserSettingsNotificationsProps} from '../user_settings_notifications';
|
||||||
@@ -143,7 +147,7 @@ function DesktopNotificationSoundsSettings({
|
|||||||
defaultMessage: 'Select a sound',
|
defaultMessage: 'Select a sound',
|
||||||
})}
|
})}
|
||||||
components={{IndicatorSeparator: NoIndicatorSeparatorComponent}}
|
components={{IndicatorSeparator: NoIndicatorSeparatorComponent}}
|
||||||
value={getValueOfMessageNotificationSoundsSelect(desktopNotificationSound)}
|
value={getValueOfNotificationSoundsSelect(desktopNotificationSound)}
|
||||||
onChange={handleChangeForMessageNotificationSoundSelect}
|
onChange={handleChangeForMessageNotificationSoundSelect}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -259,146 +263,6 @@ function NoIndicatorSeparatorComponent() {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const notificationSoundKeys = Array.from(notificationSounds.keys());
|
|
||||||
|
|
||||||
const optionsOfMessageNotificationSoundsSelect: SelectOption[] = notificationSoundKeys.map((soundName) => {
|
|
||||||
if (soundName === 'Bing') {
|
|
||||||
return {
|
|
||||||
value: soundName,
|
|
||||||
label: (
|
|
||||||
<FormattedMessage
|
|
||||||
id='user.settings.notifications.desktopNotificationSound.soundBing'
|
|
||||||
defaultMessage='Bing'
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
};
|
|
||||||
} else if (soundName === 'Crackle') {
|
|
||||||
return {
|
|
||||||
value: soundName,
|
|
||||||
label: (
|
|
||||||
<FormattedMessage
|
|
||||||
id='user.settings.notifications.desktopNotificationSound.soundCrackle'
|
|
||||||
defaultMessage='Crackle'
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
};
|
|
||||||
} else if (soundName === 'Down') {
|
|
||||||
return {
|
|
||||||
value: soundName,
|
|
||||||
label: (
|
|
||||||
<FormattedMessage
|
|
||||||
id='user.settings.notifications.desktopNotificationSound.soundDown'
|
|
||||||
defaultMessage='Down'
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
};
|
|
||||||
} else if (soundName === 'Hello') {
|
|
||||||
return {
|
|
||||||
value: soundName,
|
|
||||||
label: (
|
|
||||||
<FormattedMessage
|
|
||||||
id='user.settings.notifications.desktopNotificationSound.soundHello'
|
|
||||||
defaultMessage='Hello'
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
};
|
|
||||||
} else if (soundName === 'Ripple') {
|
|
||||||
return {
|
|
||||||
value: soundName,
|
|
||||||
label: (
|
|
||||||
<FormattedMessage
|
|
||||||
id='user.settings.notifications.desktopNotificationSound.soundRipple'
|
|
||||||
defaultMessage='Ripple'
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
};
|
|
||||||
} else if (soundName === 'Upstairs') {
|
|
||||||
return {
|
|
||||||
value: soundName,
|
|
||||||
label: (
|
|
||||||
<FormattedMessage
|
|
||||||
id='user.settings.notifications.desktopNotificationSound.soundUpstairs'
|
|
||||||
defaultMessage='Upstairs'
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
value: '',
|
|
||||||
label: '',
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
function getValueOfMessageNotificationSoundsSelect(soundName?: string) {
|
|
||||||
const soundOption = optionsOfMessageNotificationSoundsSelect.find((option) => option.value === soundName);
|
|
||||||
|
|
||||||
if (!soundOption) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return soundOption;
|
|
||||||
}
|
|
||||||
|
|
||||||
const callNotificationSoundKeys = Array.from(callsNotificationSounds.keys());
|
|
||||||
|
|
||||||
const optionsOfIncomingCallSoundsSelect: SelectOption[] = callNotificationSoundKeys.map((soundName) => {
|
|
||||||
if (soundName === 'Dynamic') {
|
|
||||||
return {
|
|
||||||
value: soundName,
|
|
||||||
label: (
|
|
||||||
<FormattedMessage
|
|
||||||
id='user.settings.notifications.desktopNotificationSound.soundDynamic'
|
|
||||||
defaultMessage='Dynamic'
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
};
|
|
||||||
} else if (soundName === 'Calm') {
|
|
||||||
return {
|
|
||||||
value: soundName,
|
|
||||||
label: (
|
|
||||||
<FormattedMessage
|
|
||||||
id='user.settings.notifications.desktopNotificationSound.soundCalm'
|
|
||||||
defaultMessage='Calm'
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
};
|
|
||||||
} else if (soundName === 'Urgent') {
|
|
||||||
return {
|
|
||||||
value: soundName,
|
|
||||||
label: (
|
|
||||||
<FormattedMessage
|
|
||||||
id='user.settings.notifications.desktopNotificationSound.soundUrgent'
|
|
||||||
defaultMessage='Urgent'
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
};
|
|
||||||
} else if (soundName === 'Cheerful') {
|
|
||||||
return {
|
|
||||||
value: soundName,
|
|
||||||
label: (
|
|
||||||
<FormattedMessage
|
|
||||||
id='user.settings.notifications.desktopNotificationSound.soundCheerful'
|
|
||||||
defaultMessage='Cheerful'
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
value: '',
|
|
||||||
label: '',
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
function getValueOfIncomingCallSoundsSelect(soundName?: string) {
|
|
||||||
const soundOption = optionsOfIncomingCallSoundsSelect.find((option) => option.value === soundName);
|
|
||||||
|
|
||||||
if (!soundOption) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return soundOption;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getCollapsedText(
|
function getCollapsedText(
|
||||||
isCallsRingingEnabled: UserSettingsNotificationsProps['isCallsRingingEnabled'],
|
isCallsRingingEnabled: UserSettingsNotificationsProps['isCallsRingingEnabled'],
|
||||||
desktopSound: UserNotifyProps['desktop_sound'],
|
desktopSound: UserNotifyProps['desktop_sound'],
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import SettingItemMax from 'components/setting_item_max';
|
|||||||
import RestrictedIndicator from 'components/widgets/menu/menu_items/restricted_indicator';
|
import RestrictedIndicator from 'components/widgets/menu/menu_items/restricted_indicator';
|
||||||
|
|
||||||
import Constants, {NotificationLevels, MattermostFeatures, LicenseSkus, UserSettingsNotificationSections} from 'utils/constants';
|
import Constants, {NotificationLevels, MattermostFeatures, LicenseSkus, UserSettingsNotificationSections} from 'utils/constants';
|
||||||
import {stopTryNotificationRing} from 'utils/notification_sounds';
|
import {notificationSoundKeys, stopTryNotificationRing} from 'utils/notification_sounds';
|
||||||
import {a11yFocus} from 'utils/utils';
|
import {a11yFocus} from 'utils/utils';
|
||||||
|
|
||||||
import DesktopAndMobileNotificationSettings from './desktop_and_mobile_notification_setting';
|
import DesktopAndMobileNotificationSettings from './desktop_and_mobile_notification_setting';
|
||||||
@@ -88,7 +88,7 @@ function getDefaultStateFromProps(props: Props): State {
|
|||||||
let emailThreads: UserNotifyProps['email_threads'] = NotificationLevels.ALL;
|
let emailThreads: UserNotifyProps['email_threads'] = NotificationLevels.ALL;
|
||||||
let sound: UserNotifyProps['desktop_sound'] = 'true';
|
let sound: UserNotifyProps['desktop_sound'] = 'true';
|
||||||
let callsSound: UserNotifyProps['calls_desktop_sound'] = 'true';
|
let callsSound: UserNotifyProps['calls_desktop_sound'] = 'true';
|
||||||
let desktopNotificationSound: UserNotifyProps['desktop_notification_sound'] = 'Bing';
|
let desktopNotificationSound: UserNotifyProps['desktop_notification_sound'] = notificationSoundKeys[0] as UserNotifyProps['desktop_notification_sound'];
|
||||||
let callsNotificationSound: UserNotifyProps['calls_notification_sound'] = 'Calm';
|
let callsNotificationSound: UserNotifyProps['calls_notification_sound'] = 'Calm';
|
||||||
let comments: UserNotifyProps['comments'] = 'never';
|
let comments: UserNotifyProps['comments'] = 'never';
|
||||||
let enableEmail: UserNotifyProps['email'] = 'true';
|
let enableEmail: UserNotifyProps['email'] = 'true';
|
||||||
|
|||||||
@@ -50,6 +50,11 @@
|
|||||||
&__content {
|
&__content {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
|
&.inline {
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 24px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__fieldset-checkbox-ctr {
|
&__fieldset-checkbox-ctr {
|
||||||
@@ -90,9 +95,10 @@
|
|||||||
line-height: 20px;
|
line-height: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Todo: add styling for react select
|
&__fieldset-react-select {
|
||||||
//&__fieldset-react-select{
|
max-width: 240px;
|
||||||
//}
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
&__input-checkbox {
|
&__input-checkbox {
|
||||||
width: 1.6rem;
|
width: 1.6rem;
|
||||||
|
|||||||
@@ -23,11 +23,12 @@ export type BaseSettingItemProps = {
|
|||||||
|
|
||||||
type Props = BaseSettingItemProps & {
|
type Props = BaseSettingItemProps & {
|
||||||
content: JSX.Element;
|
content: JSX.Element;
|
||||||
|
isContentInline?: boolean;
|
||||||
className?: string;
|
className?: string;
|
||||||
descriptionAboveContent?: boolean;
|
descriptionAboveContent?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
function BaseSettingItem({title, description, content, className, error, descriptionAboveContent = false}: Props): JSX.Element {
|
function BaseSettingItem({title, description, content, className, error, descriptionAboveContent = false, isContentInline = false}: Props): JSX.Element {
|
||||||
const {formatMessage} = useIntl();
|
const {formatMessage} = useIntl();
|
||||||
|
|
||||||
const titleComponent = title && (
|
const titleComponent = title && (
|
||||||
@@ -64,7 +65,9 @@ function BaseSettingItem({title, description, content, className, error, descrip
|
|||||||
{descriptionAboveContent ? descriptionComponent : undefined}
|
{descriptionAboveContent ? descriptionComponent : undefined}
|
||||||
<div
|
<div
|
||||||
data-testid='mm-modal-generic-section-item__content'
|
data-testid='mm-modal-generic-section-item__content'
|
||||||
className='mm-modal-generic-section-item__content'
|
className={classNames('mm-modal-generic-section-item__content', {
|
||||||
|
inline: isContentInline,
|
||||||
|
})}
|
||||||
>
|
>
|
||||||
{content}
|
{content}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,101 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
import type {ReactNode} from 'react';
|
||||||
|
import React from 'react';
|
||||||
|
import ReactSelect from 'react-select';
|
||||||
|
import type {ValueType} from 'react-select';
|
||||||
|
|
||||||
|
import type {BaseSettingItemProps} from './base_setting_item';
|
||||||
|
import BaseSettingItem from './base_setting_item';
|
||||||
|
import type {FieldsetCheckbox} from './checkbox_setting_item';
|
||||||
|
import type {FieldsetReactSelect, Option} from './react_select_item';
|
||||||
|
|
||||||
|
type Props = BaseSettingItemProps & {
|
||||||
|
containerClassName?: string;
|
||||||
|
descriptionAboveContent?: boolean;
|
||||||
|
checkboxFieldTitle: ReactNode;
|
||||||
|
checkboxFieldData: FieldsetCheckbox;
|
||||||
|
checkboxFieldValue: boolean;
|
||||||
|
handleCheckboxChange: (e: boolean) => void;
|
||||||
|
selectFieldData: FieldsetReactSelect;
|
||||||
|
selectFieldValue?: Option;
|
||||||
|
handleSelectChange: (selected: ValueType<Option>) => void;
|
||||||
|
isSelectDisabled?: boolean;
|
||||||
|
selectPlaceholder?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function CheckboxWithSelectSettingItem({
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
containerClassName,
|
||||||
|
descriptionAboveContent = false,
|
||||||
|
checkboxFieldTitle,
|
||||||
|
checkboxFieldData,
|
||||||
|
checkboxFieldValue,
|
||||||
|
handleCheckboxChange,
|
||||||
|
selectFieldData,
|
||||||
|
selectFieldValue,
|
||||||
|
handleSelectChange,
|
||||||
|
isSelectDisabled,
|
||||||
|
selectPlaceholder,
|
||||||
|
}: Props) {
|
||||||
|
const content = (
|
||||||
|
<>
|
||||||
|
<fieldset
|
||||||
|
key={checkboxFieldData.name}
|
||||||
|
className='mm-modal-generic-section-item__fieldset-checkbox-ctr'
|
||||||
|
>
|
||||||
|
<label className='mm-modal-generic-section-item__fieldset-checkbox'>
|
||||||
|
<input
|
||||||
|
className='mm-modal-generic-section-item__input-checkbox'
|
||||||
|
data-testid={checkboxFieldData.dataTestId}
|
||||||
|
type='checkbox'
|
||||||
|
name={checkboxFieldData.name}
|
||||||
|
checked={checkboxFieldValue}
|
||||||
|
onChange={(e) => handleCheckboxChange(e.target.checked)}
|
||||||
|
/>
|
||||||
|
{checkboxFieldTitle}
|
||||||
|
</label>
|
||||||
|
</fieldset>
|
||||||
|
<fieldset className='mm-modal-generic-section-item__fieldset-react-select'>
|
||||||
|
<ReactSelect
|
||||||
|
id={selectFieldData.id}
|
||||||
|
inputId={selectFieldData.inputId}
|
||||||
|
aria-labelledby={selectFieldData.ariaLabelledby}
|
||||||
|
className='react-select singleSelect react-select-top'
|
||||||
|
classNamePrefix='react-select'
|
||||||
|
options={selectFieldData.options}
|
||||||
|
clearable={selectFieldData.clearable}
|
||||||
|
isClearable={selectFieldData.clearable}
|
||||||
|
isDisabled={isSelectDisabled}
|
||||||
|
isSearchable={false}
|
||||||
|
placeholder={selectPlaceholder}
|
||||||
|
onChange={(value) => handleSelectChange(value)}
|
||||||
|
value={selectFieldValue}
|
||||||
|
components={{IndicatorSeparator: NoIndicatorSeparatorComponent}}
|
||||||
|
/>
|
||||||
|
</fieldset>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BaseSettingItem
|
||||||
|
title={title}
|
||||||
|
content={content}
|
||||||
|
isContentInline={true}
|
||||||
|
description={description}
|
||||||
|
className={containerClassName}
|
||||||
|
descriptionAboveContent={descriptionAboveContent}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function NoIndicatorSeparatorComponent() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SelectOption = {
|
||||||
|
value: string;
|
||||||
|
label: ReactNode;
|
||||||
|
};
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
import type {ReactNode} from 'react';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import type {ValueType} from 'react-select';
|
import type {ValueType} from 'react-select';
|
||||||
import ReactSelect from 'react-select';
|
import ReactSelect from 'react-select';
|
||||||
@@ -9,12 +10,17 @@ import type {BaseSettingItemProps} from './base_setting_item';
|
|||||||
import BaseSettingItem from './base_setting_item';
|
import BaseSettingItem from './base_setting_item';
|
||||||
|
|
||||||
export type Option = {
|
export type Option = {
|
||||||
value: number;
|
value: string;
|
||||||
label: string;
|
label: ReactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type FieldsetReactSelect = {
|
export type FieldsetReactSelect = {
|
||||||
|
id: string;
|
||||||
|
name?: string;
|
||||||
|
inputId?: string;
|
||||||
dataTestId?: string;
|
dataTestId?: string;
|
||||||
|
ariaLabelledby?: string;
|
||||||
|
clearable?: boolean;
|
||||||
options: Option[];
|
options: Option[];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -23,6 +29,7 @@ type Props = BaseSettingItemProps & {
|
|||||||
inputFieldValue: Option;
|
inputFieldValue: Option;
|
||||||
handleChange: (selected: ValueType<Option>) => void;
|
handleChange: (selected: ValueType<Option>) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
function ReactSelectItemCreator({
|
function ReactSelectItemCreator({
|
||||||
title,
|
title,
|
||||||
description,
|
description,
|
||||||
@@ -33,19 +40,23 @@ function ReactSelectItemCreator({
|
|||||||
const content = (
|
const content = (
|
||||||
<fieldset className='mm-modal-generic-section-item__fieldset-react-select'>
|
<fieldset className='mm-modal-generic-section-item__fieldset-react-select'>
|
||||||
<ReactSelect
|
<ReactSelect
|
||||||
className='react-select'
|
id={inputFieldData.id}
|
||||||
|
name={inputFieldData.name}
|
||||||
|
inputId={inputFieldData.inputId}
|
||||||
|
aria-labelledby={inputFieldData.ariaLabelledby}
|
||||||
|
className='react-select singleSelect react-select-top'
|
||||||
classNamePrefix='react-select'
|
classNamePrefix='react-select'
|
||||||
id='limitVisibleGMsDMs'
|
|
||||||
options={inputFieldData.options}
|
options={inputFieldData.options}
|
||||||
clearable={false}
|
clearable={inputFieldData.clearable}
|
||||||
|
isClearable={inputFieldData.clearable}
|
||||||
|
isSearchable={false}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
value={inputFieldValue}
|
value={inputFieldValue}
|
||||||
isSearchable={false}
|
components={{IndicatorSeparator: NoIndicatorSeparatorComponent}}
|
||||||
menuPortalTarget={document.body}
|
|
||||||
styles={reactStyles}
|
|
||||||
/>
|
/>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BaseSettingItem
|
<BaseSettingItem
|
||||||
content={content}
|
content={content}
|
||||||
@@ -57,9 +68,6 @@ function ReactSelectItemCreator({
|
|||||||
|
|
||||||
export default ReactSelectItemCreator;
|
export default ReactSelectItemCreator;
|
||||||
|
|
||||||
const reactStyles = {
|
function NoIndicatorSeparatorComponent() {
|
||||||
menuPortal: (provided: React.CSSProperties) => ({
|
return null;
|
||||||
...provided,
|
}
|
||||||
zIndex: 9999,
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -3202,6 +3202,9 @@
|
|||||||
"channel_notifications.desktopNotificationAllLabel": "All new messages",
|
"channel_notifications.desktopNotificationAllLabel": "All new messages",
|
||||||
"channel_notifications.desktopNotificationMentionLabel": "Mentions, direct messages, and keywords only",
|
"channel_notifications.desktopNotificationMentionLabel": "Mentions, direct messages, and keywords only",
|
||||||
"channel_notifications.desktopNotificationNothingLabel": "Nothing",
|
"channel_notifications.desktopNotificationNothingLabel": "Nothing",
|
||||||
|
"channel_notifications.desktopNotifications.soundEnable": "Message notification sounds",
|
||||||
|
"channel_notifications.desktopNotifications.soundSelectPlaceholder": "Select a sound",
|
||||||
|
"channel_notifications.desktopNotifications.title": "Sounds",
|
||||||
"channel_notifications.desktopNotificationsDesc": "Available on Chrome, Edge, Firefox, and the Mattermost Desktop App.",
|
"channel_notifications.desktopNotificationsDesc": "Available on Chrome, Edge, Firefox, and the Mattermost Desktop App.",
|
||||||
"channel_notifications.desktopNotificationsTitle": "Desktop Notifications",
|
"channel_notifications.desktopNotificationsTitle": "Desktop Notifications",
|
||||||
"channel_notifications.ignoreMentionsDesc": "When enabled, @channel, @here and @all will not trigger mentions or mention notifications in this channel",
|
"channel_notifications.ignoreMentionsDesc": "When enabled, @channel, @here and @all will not trigger mentions or mention notifications in this channel",
|
||||||
|
|||||||
@@ -1,107 +0,0 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
||||||
// See LICENSE.txt for license information.
|
|
||||||
|
|
||||||
import bing from 'sounds/bing.mp3';
|
|
||||||
import calls_calm from 'sounds/calls_calm.mp3';
|
|
||||||
import calls_cheerful from 'sounds/calls_cheerful.mp3';
|
|
||||||
import calls_dynamic from 'sounds/calls_dynamic.mp3';
|
|
||||||
import calls_urgent from 'sounds/calls_urgent.mp3';
|
|
||||||
import crackle from 'sounds/crackle.mp3';
|
|
||||||
import down from 'sounds/down.mp3';
|
|
||||||
import hello from 'sounds/hello.mp3';
|
|
||||||
import ripple from 'sounds/ripple.mp3';
|
|
||||||
import upstairs from 'sounds/upstairs.mp3';
|
|
||||||
import * as UserAgent from 'utils/user_agent';
|
|
||||||
|
|
||||||
export const notificationSounds = new Map([
|
|
||||||
['Bing', bing],
|
|
||||||
['Crackle', crackle],
|
|
||||||
['Down', down],
|
|
||||||
['Hello', hello],
|
|
||||||
['Ripple', ripple],
|
|
||||||
['Upstairs', upstairs],
|
|
||||||
]);
|
|
||||||
|
|
||||||
export const callsNotificationSounds = new Map([
|
|
||||||
['Dynamic', calls_dynamic],
|
|
||||||
['Calm', calls_calm],
|
|
||||||
['Urgent', calls_urgent],
|
|
||||||
['Cheerful', calls_cheerful],
|
|
||||||
]);
|
|
||||||
|
|
||||||
let canDing = true;
|
|
||||||
export function ding(name: string) {
|
|
||||||
if (hasSoundOptions() && canDing) {
|
|
||||||
tryNotificationSound(name);
|
|
||||||
canDing = false;
|
|
||||||
setTimeout(() => {
|
|
||||||
canDing = true;
|
|
||||||
}, 3000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function tryNotificationSound(name: string) {
|
|
||||||
const audio = new Audio(notificationSounds.get(name) ?? notificationSounds.get('Bing'));
|
|
||||||
audio.play();
|
|
||||||
}
|
|
||||||
|
|
||||||
let currentRing: HTMLAudioElement | null = null;
|
|
||||||
export function ring(name: string) {
|
|
||||||
if (!hasSoundOptions()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
stopRing();
|
|
||||||
|
|
||||||
currentRing = loopNotificationRing(name);
|
|
||||||
currentRing.addEventListener('pause', () => {
|
|
||||||
stopRing();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function stopRing() {
|
|
||||||
if (currentRing) {
|
|
||||||
currentRing.pause();
|
|
||||||
currentRing.src = '';
|
|
||||||
currentRing.remove();
|
|
||||||
currentRing = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let currentTryRing: HTMLAudioElement | null = null;
|
|
||||||
let currentTimer: NodeJS.Timeout;
|
|
||||||
export function tryNotificationRing(name: string) {
|
|
||||||
if (!hasSoundOptions()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
stopTryNotificationRing();
|
|
||||||
clearTimeout(currentTimer);
|
|
||||||
|
|
||||||
currentTryRing = loopNotificationRing(name);
|
|
||||||
currentTryRing.addEventListener('pause', () => {
|
|
||||||
stopTryNotificationRing();
|
|
||||||
});
|
|
||||||
|
|
||||||
currentTimer = setTimeout(() => {
|
|
||||||
stopTryNotificationRing();
|
|
||||||
}, 5000);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function stopTryNotificationRing() {
|
|
||||||
if (currentTryRing) {
|
|
||||||
currentTryRing.pause();
|
|
||||||
currentTryRing.src = '';
|
|
||||||
currentTryRing.remove();
|
|
||||||
currentTryRing = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function loopNotificationRing(name: string) {
|
|
||||||
const audio = new Audio(callsNotificationSounds.get(name) ?? callsNotificationSounds.get('Calm'));
|
|
||||||
audio.loop = true;
|
|
||||||
audio.play();
|
|
||||||
return audio;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function hasSoundOptions() {
|
|
||||||
return (!UserAgent.isEdge());
|
|
||||||
}
|
|
||||||
251
webapp/channels/src/utils/notification_sounds.tsx
Обычный файл
251
webapp/channels/src/utils/notification_sounds.tsx
Обычный файл
@@ -0,0 +1,251 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
import type {ReactNode} from 'react';
|
||||||
|
import React from 'react';
|
||||||
|
import {FormattedMessage} from 'react-intl';
|
||||||
|
|
||||||
|
import bing from 'sounds/bing.mp3';
|
||||||
|
import calls_calm from 'sounds/calls_calm.mp3';
|
||||||
|
import calls_cheerful from 'sounds/calls_cheerful.mp3';
|
||||||
|
import calls_dynamic from 'sounds/calls_dynamic.mp3';
|
||||||
|
import calls_urgent from 'sounds/calls_urgent.mp3';
|
||||||
|
import crackle from 'sounds/crackle.mp3';
|
||||||
|
import down from 'sounds/down.mp3';
|
||||||
|
import hello from 'sounds/hello.mp3';
|
||||||
|
import ripple from 'sounds/ripple.mp3';
|
||||||
|
import upstairs from 'sounds/upstairs.mp3';
|
||||||
|
import * as UserAgent from 'utils/user_agent';
|
||||||
|
|
||||||
|
export const notificationSounds = new Map([
|
||||||
|
['Bing', bing],
|
||||||
|
['Crackle', crackle],
|
||||||
|
['Down', down],
|
||||||
|
['Hello', hello],
|
||||||
|
['Ripple', ripple],
|
||||||
|
['Upstairs', upstairs],
|
||||||
|
]);
|
||||||
|
|
||||||
|
export const notificationSoundKeys = Array.from(notificationSounds.keys());
|
||||||
|
|
||||||
|
export const optionsOfMessageNotificationSoundsSelect: Array<{value: string; label: ReactNode}> = notificationSoundKeys.map((soundName) => {
|
||||||
|
if (soundName === 'Bing') {
|
||||||
|
return {
|
||||||
|
value: soundName,
|
||||||
|
label: (
|
||||||
|
<FormattedMessage
|
||||||
|
id='user.settings.notifications.desktopNotificationSound.soundBing'
|
||||||
|
defaultMessage='Bing'
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
} else if (soundName === 'Crackle') {
|
||||||
|
return {
|
||||||
|
value: soundName,
|
||||||
|
label: (
|
||||||
|
<FormattedMessage
|
||||||
|
id='user.settings.notifications.desktopNotificationSound.soundCrackle'
|
||||||
|
defaultMessage='Crackle'
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
} else if (soundName === 'Down') {
|
||||||
|
return {
|
||||||
|
value: soundName,
|
||||||
|
label: (
|
||||||
|
<FormattedMessage
|
||||||
|
id='user.settings.notifications.desktopNotificationSound.soundDown'
|
||||||
|
defaultMessage='Down'
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
} else if (soundName === 'Hello') {
|
||||||
|
return {
|
||||||
|
value: soundName,
|
||||||
|
label: (
|
||||||
|
<FormattedMessage
|
||||||
|
id='user.settings.notifications.desktopNotificationSound.soundHello'
|
||||||
|
defaultMessage='Hello'
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
} else if (soundName === 'Ripple') {
|
||||||
|
return {
|
||||||
|
value: soundName,
|
||||||
|
label: (
|
||||||
|
<FormattedMessage
|
||||||
|
id='user.settings.notifications.desktopNotificationSound.soundRipple'
|
||||||
|
defaultMessage='Ripple'
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
} else if (soundName === 'Upstairs') {
|
||||||
|
return {
|
||||||
|
value: soundName,
|
||||||
|
label: (
|
||||||
|
<FormattedMessage
|
||||||
|
id='user.settings.notifications.desktopNotificationSound.soundUpstairs'
|
||||||
|
defaultMessage='Upstairs'
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
value: '',
|
||||||
|
label: '',
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
export function getValueOfNotificationSoundsSelect(soundName?: string) {
|
||||||
|
const soundOption = optionsOfMessageNotificationSoundsSelect.find((option) => option.value === soundName);
|
||||||
|
|
||||||
|
if (!soundOption) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return soundOption;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const callsNotificationSounds = new Map([
|
||||||
|
['Dynamic', calls_dynamic],
|
||||||
|
['Calm', calls_calm],
|
||||||
|
['Urgent', calls_urgent],
|
||||||
|
['Cheerful', calls_cheerful],
|
||||||
|
]);
|
||||||
|
|
||||||
|
export const callNotificationSoundKeys = Array.from(callsNotificationSounds.keys());
|
||||||
|
|
||||||
|
export const optionsOfIncomingCallSoundsSelect: Array<{value: string; label: ReactNode}> = callNotificationSoundKeys.map((soundName) => {
|
||||||
|
if (soundName === 'Dynamic') {
|
||||||
|
return {
|
||||||
|
value: soundName,
|
||||||
|
label: (
|
||||||
|
<FormattedMessage
|
||||||
|
id='user.settings.notifications.desktopNotificationSound.soundDynamic'
|
||||||
|
defaultMessage='Dynamic'
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
} else if (soundName === 'Calm') {
|
||||||
|
return {
|
||||||
|
value: soundName,
|
||||||
|
label: (
|
||||||
|
<FormattedMessage
|
||||||
|
id='user.settings.notifications.desktopNotificationSound.soundCalm'
|
||||||
|
defaultMessage='Calm'
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
} else if (soundName === 'Urgent') {
|
||||||
|
return {
|
||||||
|
value: soundName,
|
||||||
|
label: (
|
||||||
|
<FormattedMessage
|
||||||
|
id='user.settings.notifications.desktopNotificationSound.soundUrgent'
|
||||||
|
defaultMessage='Urgent'
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
} else if (soundName === 'Cheerful') {
|
||||||
|
return {
|
||||||
|
value: soundName,
|
||||||
|
label: (
|
||||||
|
<FormattedMessage
|
||||||
|
id='user.settings.notifications.desktopNotificationSound.soundCheerful'
|
||||||
|
defaultMessage='Cheerful'
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
value: '',
|
||||||
|
label: '',
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
export function getValueOfIncomingCallSoundsSelect(soundName?: string) {
|
||||||
|
const soundOption = optionsOfIncomingCallSoundsSelect.find((option) => option.value === soundName);
|
||||||
|
|
||||||
|
if (!soundOption) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return soundOption;
|
||||||
|
}
|
||||||
|
|
||||||
|
let canDing = true;
|
||||||
|
export function ding(name: string) {
|
||||||
|
if (hasSoundOptions() && canDing) {
|
||||||
|
tryNotificationSound(name);
|
||||||
|
canDing = false;
|
||||||
|
setTimeout(() => {
|
||||||
|
canDing = true;
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function tryNotificationSound(name: string) {
|
||||||
|
const audio = new Audio(notificationSounds.get(name) ?? notificationSounds.get('Bing'));
|
||||||
|
audio.play();
|
||||||
|
}
|
||||||
|
|
||||||
|
let currentRing: HTMLAudioElement | null = null;
|
||||||
|
export function ring(name: string) {
|
||||||
|
if (!hasSoundOptions()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
stopRing();
|
||||||
|
|
||||||
|
currentRing = loopNotificationRing(name);
|
||||||
|
currentRing.addEventListener('pause', () => {
|
||||||
|
stopRing();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function stopRing() {
|
||||||
|
if (currentRing) {
|
||||||
|
currentRing.pause();
|
||||||
|
currentRing.src = '';
|
||||||
|
currentRing.remove();
|
||||||
|
currentRing = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let currentTryRing: HTMLAudioElement | null = null;
|
||||||
|
let currentTimer: NodeJS.Timeout;
|
||||||
|
export function tryNotificationRing(name: string) {
|
||||||
|
if (!hasSoundOptions()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
stopTryNotificationRing();
|
||||||
|
clearTimeout(currentTimer);
|
||||||
|
|
||||||
|
currentTryRing = loopNotificationRing(name);
|
||||||
|
currentTryRing.addEventListener('pause', () => {
|
||||||
|
stopTryNotificationRing();
|
||||||
|
});
|
||||||
|
|
||||||
|
currentTimer = setTimeout(() => {
|
||||||
|
stopTryNotificationRing();
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function stopTryNotificationRing() {
|
||||||
|
if (currentTryRing) {
|
||||||
|
currentTryRing.pause();
|
||||||
|
currentTryRing.src = '';
|
||||||
|
currentTryRing.remove();
|
||||||
|
currentTryRing = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function loopNotificationRing(name: string) {
|
||||||
|
const audio = new Audio(callsNotificationSounds.get(name) ?? callsNotificationSounds.get('Calm'));
|
||||||
|
audio.loop = true;
|
||||||
|
audio.play();
|
||||||
|
return audio;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hasSoundOptions() {
|
||||||
|
return (!UserAgent.isEdge());
|
||||||
|
}
|
||||||
Ссылка в новой задаче
Block a user