Skip to content

Commit

Permalink
Merge branch 'feature/voice-messages' into 'master'
Browse files Browse the repository at this point in the history
MM#11162 Contribute : Voice Message

See merge request kchat/webapp!596
  • Loading branch information
antonbuks committed Feb 5, 2024
2 parents c0d3d26 + 268bf5e commit 64f6563
Show file tree
Hide file tree
Showing 43 changed files with 1,692 additions and 40 deletions.
1 change: 1 addition & 0 deletions webapp/channels/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@
"tinycolor2": "1.4.2",
"turndown": "7.1.1",
"typescript": "4.7.4",
"wasm-media-encoders": "0.6.4",
"zen-observable": "0.9.0"
},
"devDependencies": {
Expand Down
1 change: 1 addition & 0 deletions webapp/channels/src/actions/views/create_comment.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ export function submitPost(channelId: string, rootId: string, draft: PostDraft)
user_id: userId,
create_at: time,
metadata: {},
type: draft?.postType ?? '',
props: {...draft.props},
} as unknown as Post;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import Constants, {ModalIdentifiers} from 'utils/constants';

import AdvanceTextEditor from '../advanced_text_editor/advanced_text_editor';

jest.mock('components/advanced_text_editor/voice_message_attachment', () => () => <div/>);

describe('components/AdvancedCreateComment', () => {
jest.useFakeTimers();
beforeEach(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import type {ServerError} from '@mattermost/types/errors';
import type {FileInfo} from '@mattermost/types/files';
import type {Group} from '@mattermost/types/groups';
import {GroupSource} from '@mattermost/types/groups';
import type {Post} from '@mattermost/types/posts';
import type {PreferenceType} from '@mattermost/types/preferences';

import type {ActionResult} from 'mattermost-redux/types/actions';
Expand Down Expand Up @@ -40,6 +41,8 @@ import {
splitMessageBasedOnCaretPosition,
groupsMentionedInText,
mentionsMinusSpecialMentionsInText,
getVoiceMessageStateFromDraft,
VoiceMessageStates,
} from 'utils/post_utils';
import * as UserAgent from 'utils/user_agent';
import * as Utils from 'utils/utils';
Expand Down Expand Up @@ -206,6 +209,7 @@ type State = {
serverError: (ServerError & {submittedMessage?: string}) | null;
showFormat: boolean;
isFormattingBarHidden: boolean;
voiceMessageClientId: string;
};

function isDraftEmpty(draft: PostDraft): boolean {
Expand Down Expand Up @@ -267,6 +271,7 @@ class AdvancedCreateComment extends React.PureComponent<Props, State> {
serverError: null,
showFormat: false,
isFormattingBarHidden: props.isFormattingBarHidden,
voiceMessageClientId: '',
caretPosition: props.draft.caretPosition,
};

Expand Down Expand Up @@ -303,6 +308,14 @@ class AdvancedCreateComment extends React.PureComponent<Props, State> {
document.removeEventListener('keydown', this.focusTextboxIfNecessary);
window.removeEventListener('beforeunload', this.saveDraftWithShow);
this.saveDraftOnUnmount();

// Remove voice message recorder on thread/comment switch or another thread opened
if (this.props.rootId) {
const previousChannelVoiceMessageState = getVoiceMessageStateFromDraft(this.props.draft);
if (previousChannelVoiceMessageState === VoiceMessageStates.RECORDING) {
this.setDraftAsPostType(this.props.rootId, this.props.draft);
}
}
}

componentDidUpdate(prevProps: Props, prevState: State) {
Expand Down Expand Up @@ -1024,6 +1037,32 @@ class AdvancedCreateComment extends React.PureComponent<Props, State> {
});
};

setDraftAsPostType = (rootId: Post['id'], draft: PostDraft, postType?: PostDraft['postType']) => {
const updatedDraft: PostDraft = {...draft};

if (postType) {
updatedDraft.postType = Constants.PostTypes.VOICE;
} else {
Reflect.deleteProperty(updatedDraft, 'postType');
}

this.props.updateCommentDraftWithRootId(rootId, updatedDraft);
this.draftsForPost[rootId] = updatedDraft;
};

handleVoiceMessageUploadStart = (clientId: string, rootId: Post['id']) => {
const uploadsInProgress = [...this.props.draft.uploadsInProgress, clientId];
const draft = {
...this.props.draft,
uploadsInProgress,
postType: Constants.PostTypes.VOICE,
};

this.props.updateCommentDraftWithRootId(rootId, draft);
this.setState({voiceMessageClientId: clientId});
this.draftsForPost[rootId] = draft;
};

handleFileUploadChange = () => {
this.isDraftEdited = true;
this.focusTextbox();
Expand All @@ -1044,6 +1083,8 @@ class AdvancedCreateComment extends React.PureComponent<Props, State> {
// this is a bit redundant with the code that sets focus when the file input is clicked,
// but this also resets the focus after a drag and drop
this.focusTextbox();

this.setState({voiceMessageClientId: ''});
};

handleUploadProgress = (filePreviewInfo: FilePreviewInfo) => {
Expand Down Expand Up @@ -1102,7 +1143,7 @@ class AdvancedCreateComment extends React.PureComponent<Props, State> {
serverError = new Error(serverError);
}

this.setState({serverError}, () => {
this.setState({serverError, voiceMessageClientId: ''}, () => {
if (serverError && this.props.scrollToBottom) {
this.props.scrollToBottom();
}
Expand All @@ -1114,6 +1155,11 @@ class AdvancedCreateComment extends React.PureComponent<Props, State> {
const fileInfos = [...draft.fileInfos];
const uploadsInProgress = [...draft.uploadsInProgress];

if (draft.postType === Constants.PostTypes.VOICE) {
Reflect.deleteProperty(draft, 'postType');
this.setState({voiceMessageClientId: ''});
}

// Clear previous errors
this.handleUploadError(null);

Expand Down Expand Up @@ -1268,6 +1314,9 @@ class AdvancedCreateComment extends React.PureComponent<Props, State> {
getFileUploadTarget={this.getFileUploadTarget}
fileUploadRef={this.fileUploadRef}
isThreadView={this.props.isThreadView}
voiceMessageClientId={this.state.voiceMessageClientId}
handleVoiceMessageUploadStart={this.handleVoiceMessageUploadStart}
setDraftAsPostType={this.setDraftAsPostType}
isSchedulable={true}
handleSchedulePost={this.handleSchedulePost}
caretPosition={this.state.caretPosition}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ jest.mock('actions/post_actions', () => ({
}),
}));

jest.mock('components/advanced_text_editor/voice_message_attachment', () => () => <div/>);

const currentTeamIdProp = 'r7rws4y7ppgszym3pdd5kaibfa';
const currentUserIdProp = 'zaktnt8bpbgu8mb6ez9k64r7sa';
const showTutorialTipProp = false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ import {
splitMessageBasedOnCaretPosition,
groupsMentionedInText,
mentionsMinusSpecialMentionsInText,
getVoiceMessageStateFromDraft,
VoiceMessageStates,
} from 'utils/post_utils';
import * as UserAgent from 'utils/user_agent';
import * as Utils from 'utils/utils';
Expand Down Expand Up @@ -263,6 +265,7 @@ type State = {
showFormat: boolean;
isFormattingBarHidden: boolean;
showPostPriorityPicker: boolean;
voiceMessageClientId: string;
};

class AdvancedCreatePost extends React.PureComponent<Props, State> {
Expand Down Expand Up @@ -316,6 +319,7 @@ class AdvancedCreatePost extends React.PureComponent<Props, State> {
showFormat: false,
isFormattingBarHidden: props.isFormattingBarHidden,
showPostPriorityPicker: false,
voiceMessageClientId: '',
};

this.topDiv = React.createRef<HTMLFormElement>();
Expand Down Expand Up @@ -359,6 +363,14 @@ class AdvancedCreatePost extends React.PureComponent<Props, State> {
if (prevProps.shouldShowPreview && !this.props.shouldShowPreview) {
this.focusTextbox();
}

// Remove voice message recorder on channel/team switch
if (this.props.currentChannel.id !== prevProps.currentChannel.id || this.props.currentTeamId !== prevProps.currentTeamId) {
const previousChannelVoiceMessageState = getVoiceMessageStateFromDraft(prevProps.draft);
if (previousChannelVoiceMessageState === VoiceMessageStates.RECORDING) {
this.setDraftAsPostType(prevProps.currentChannel.id, prevProps.draft);
}
}
}

componentWillUnmount() {
Expand All @@ -381,6 +393,14 @@ class AdvancedCreatePost extends React.PureComponent<Props, State> {
actions.getChannelMemberCountsByGroup(currentChannel.id, isTimezoneEnabled);
}
}

// If the draft is of type voice message, but has no recording then remove the voice message type draft on component mount.
if (this.props.currentChannel.id) {
const previousChannelVoiceMessageState = getVoiceMessageStateFromDraft(this.props.draft);
if (previousChannelVoiceMessageState === VoiceMessageStates.RECORDING) {
this.setDraftAsPostType(this.props.currentChannel.id, this.props.draft);
}
}
};

unloadHandler = () => {
Expand Down Expand Up @@ -768,6 +788,7 @@ class AdvancedCreatePost extends React.PureComponent<Props, State> {
post.metadata = {
...originalPost.metadata,
} as PostMetadata;
post.type = draft?.postType ?? '';

post.props = {
...originalPost.props,
Expand Down Expand Up @@ -878,6 +899,30 @@ class AdvancedCreatePost extends React.PureComponent<Props, State> {
GlobalActions.emitLocalUserTypingEvent(channelId, '');
};

setDraftAsPostType = (channelId: Channel['id'], draft: PostDraft, postType?: PostDraft['postType']) => {
if (postType) {
const updatedDraft: PostDraft = {...draft, postType: Constants.PostTypes.VOICE};
this.props.actions.setDraft(StoragePrefixes.DRAFT + channelId, updatedDraft, channelId);
this.draftsForChannel[channelId] = updatedDraft;
} else {
this.props.actions.setDraft(StoragePrefixes.DRAFT + channelId, null, channelId);
this.draftsForChannel[channelId] = null;
}
};

handleVoiceMessageUploadStart = (clientId: string, channelId: Channel['id']) => {
const uploadsInProgress = [...this.props.draft.uploadsInProgress, clientId];
const draft = {
...this.props.draft,
uploadsInProgress,
postType: Constants.PostTypes.VOICE,
};

this.props.actions.setDraft(StoragePrefixes.DRAFT + channelId, draft, channelId);
this.setState({voiceMessageClientId: clientId});
this.draftsForChannel[channelId] = draft;
};

handleChange = (e: React.ChangeEvent<TextboxElement>) => {
const message = e.target.value;

Expand Down Expand Up @@ -1037,6 +1082,7 @@ class AdvancedCreatePost extends React.PureComponent<Props, State> {
}

this.handleDraftChange(draft, channelId, true);
this.setState({voiceMessageClientId: ''});
};

handleUploadError = (err: string | ServerError, clientId?: string, channelId?: string) => {
Expand All @@ -1046,7 +1092,7 @@ class AdvancedCreatePost extends React.PureComponent<Props, State> {
}

if (!channelId || !clientId) {
this.setState({serverError});
this.setState({serverError, voiceMessageClientId: ''});
return;
}

Expand All @@ -1072,6 +1118,11 @@ class AdvancedCreatePost extends React.PureComponent<Props, State> {
let modifiedDraft = {} as PostDraft;
const draft = {...this.props.draft};

if (draft.postType === Constants.PostTypes.VOICE) {
Reflect.deleteProperty(draft, 'postType');
this.setState({voiceMessageClientId: ''});
}

// Clear previous errors
this.setState({serverError: null});

Expand Down Expand Up @@ -1713,6 +1764,9 @@ class AdvancedCreatePost extends React.PureComponent<Props, State> {
fileUploadRef={this.fileUploadRef}
prefillMessage={this.prefillMessage}
textboxRef={this.textboxRef}
voiceMessageClientId={this.state.voiceMessageClientId}
handleVoiceMessageUploadStart={this.handleVoiceMessageUploadStart}
setDraftAsPostType={this.setDraftAsPostType}
labels={priorityLabels}
isSchedulable={true}
handleSchedulePost={this.handleSchedulePost}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,13 +144,28 @@
cursor: pointer;
fill: currentColor;

&:hover {
&:hover:not(:disabled) {
background: rgba(var(--center-channel-color-rgb), 0.08);
color: rgba(var(--center-channel-color-rgb), 0.72);
fill: currentColor;
text-decoration: none;
}

&:disabled {
color: rgba(var(--center-channel-color-rgb), 0.32);
cursor: not-allowed;
pointer-events: none;

&:hover,
&:active,
&.active,
&.active:hover {
background: inherit;
color: inherit;
fill: inherit;
}
}

&.hidden {
visibility: hidden;
}
Expand Down
Loading

0 comments on commit 64f6563

Please sign in to comment.