Skip to content

Commit

Permalink
Merge branch 'transcript-show-by-default' into 'master'
Browse files Browse the repository at this point in the history
show transcript by default for vocal messages

See merge request kchat/webapp!950
  • Loading branch information
antonbuks committed Oct 22, 2024
2 parents 61cd668 + 7903472 commit 0dba5b5
Show file tree
Hide file tree
Showing 7 changed files with 154 additions and 60 deletions.
181 changes: 121 additions & 60 deletions webapp/channels/src/components/voice_message_attachment_player/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
CloseIcon,
DownloadOutlineIcon,
} from '@infomaniak/compass-icons/components';
import React, {useState} from 'react';
import React, {useEffect, useState} from 'react';
import {FormattedMessage, useIntl} from 'react-intl';

import type {FileInfo} from '@mattermost/types/files';
Expand All @@ -24,41 +24,37 @@ import {
} from 'components/common/hooks/useAudioPlayer';
import * as Menu from 'components/menu';
import TranscriptComponent from 'components/transcript/transcript';
import LoadingSpinner from 'components/widgets/loading/loading_spinner';
import TranscriptSpinner from 'components/widgets/loading/loading_transcript_spinner';

import Constants from 'utils/constants';
import {convertSecondsToMSS} from 'utils/datetime';

export interface Props {
postId?: Post['id'];
fileId: FileInfo['id'];
inPost?: boolean;
onCancel?: () => void;
isPreview?: boolean;
}

function VoiceMessageAttachmentPlayer(props: Props) {
const {formatMessage} = useIntl();
const {playerState, duration, elapsed, togglePlayPause} = useAudioPlayer(props.fileId ? `/api/v4/files/${props.fileId}` : '');

const progressValue = elapsed === 0 || duration === 0 ? '0' : (elapsed / duration).toFixed(2);
const [transcript, setTranscript] = useState('');
const [transcriptDatas, setTranscriptDatas] = useState<TranscriptData>();
const [anchorEl, setAnchorEl] = React.useState<HTMLAnchorElement | null>(null);
const [error, setError] = useState<JSX.Element | null>(null);
const [hasFetchedTranscript, setHasFetchedTranscript] = useState(false);
const [isLoadingTranscript, setIsLoadingTranscript] = useState(false);
const [error, setError] = useState<JSX.Element | null>(null);
const [anchorEl, setAnchorEl] = React.useState<HTMLAnchorElement | null>(null);
const [isTranscriptModalOpen, setIsTranscriptModalOpen] = useState(false);
const [isTranscriptModalOpen, setIsTranscriptModalOpen] = useState(true);
const [transcript, setTranscript] = useState('');
const [transcriptDatas, setTranscriptDatas] = useState<TranscriptData>();

const open = Boolean(anchorEl);

const handleClick = (event: React.MouseEvent<HTMLAnchorElement, MouseEvent>) => {
setAnchorEl(event.currentTarget);
};

const handleClose = () => {
setAnchorEl(null);
};
useEffect(() => {
if (!props.isPreview) {
fetchTranscript();
}
}, []);

const fetchTranscript = async () => {
if (!hasFetchedTranscript) {
Expand All @@ -83,15 +79,85 @@ function VoiceMessageAttachmentPlayer(props: Props) {
}
};

const transcriptIcon = () => Constants.TRANSCRIPT_ICON;

function downloadFile() {
window.location.assign(getFileDownloadUrl(props.fileId));
}

const toggleTranscriptModal = () => {
setIsTranscriptModalOpen(!isTranscriptModalOpen);
};

const handleClick = (event: React.MouseEvent<HTMLAnchorElement, MouseEvent>) => {
setAnchorEl(event.currentTarget);
};

const handleClose = () => {
setAnchorEl(null);
};

const handleTranscriptClick = () => {
if (transcript && transcriptDatas) {
toggleTranscriptModal();
}
};

const transcriptReady = (
<FormattedMessage
id='vocals.transcript_title'
defaultMessage='Audio Transcript (auto-generated)'
/>
);

const loadingMessage = (
<FormattedMessage
id='vocals.transcript_loading'
defaultMessage='Audio transcription in progress..'
/>
);

const toggle = (
<button
key='toggle'
className='style--none single-image-view__toggle'
aria-label='Toggle Embed Visibility'
onClick={toggleTranscriptModal}
>
{isLoadingTranscript ? (
<div style={{paddingRight: '3px'}}>
<TranscriptSpinner/>
</div>
) : (
<span className={`icon ${(isTranscriptModalOpen) ? 'icon-menu-down' : 'icon-menu-right'}`}/>
)}
</button>
);

const transcriptHeader = (
<div
className='image-header'
style={{cursor: 'pointer', display: 'flex'}}
onClick={handleTranscriptClick}
>
{toggle}
<div
data-testid='image-name'
className='image-name'
>
<div id='image-name-text'>
{props.isPreview ? null : (
<>
{isLoadingTranscript ? (
loadingMessage
) : (
transcriptReady
)}
</>
)}
</div>
</div>
</div>
);

return (
<>
<div className='post-image__column post-image__column--audio'>
Expand Down Expand Up @@ -167,22 +233,6 @@ function VoiceMessageAttachmentPlayer(props: Props) {
/>
</Menu.Container>
)}
{!props.isPreview && (
<div
onClick={() => {
fetchTranscript();
toggleTranscriptModal();
}}
className='post-image__end-button'
>
{isLoadingTranscript ? (
<LoadingSpinner/>
) : (
<div >
<img src={transcriptIcon()}/>
</div>
)}
</div>)}
{!props.inPost && (
<button
className='post-image__end-button'
Expand All @@ -196,33 +246,44 @@ function VoiceMessageAttachmentPlayer(props: Props) {
)}
</div>
</div>
<div >
<div>
{transcript && transcriptDatas && isTranscriptModalOpen && (
<div>
{transcript.length > 300 ? transcript.substring(0, 300) + '... ' : transcript + ' '}
<a
className='transcript-link-view'
onClick={(event) => {
handleClick(event);
}}
>
<FormattedMessage
id={'vocals.loading_transcript'}
defaultMessage={'View the transcript'}
/>
</a>
<div>
{props.isPreview ? null : (
<div>
<div className='file-view--single'>
<div className='file__image'>
{transcriptHeader}
</div>
</div>
)}
{transcriptDatas &&
<TranscriptComponent
transcriptDatas={transcriptDatas}
handleClose={handleClose}
open={open}
anchorEl={anchorEl}
/>
}
</div>
<div >
<>
{!isLoadingTranscript && transcript && transcriptDatas && isTranscriptModalOpen && (
<div style={{paddingTop: '5px'}}>
{transcript.length > 300 ? transcript.substring(0, 300) + '... ' : transcript + ' '}
<a
className='transcript-link-view'
onClick={(event) => {
handleClick(event);
}}
>
<FormattedMessage
id={'vocals.loading_transcript'}
defaultMessage={'View the transcript'}
/>
</a>
</div>
)}
{transcriptDatas && !isLoadingTranscript && (
<TranscriptComponent
transcriptDatas={transcriptDatas}
handleClose={handleClose}
open={open}
anchorEl={anchorEl}
/>
)}
</>
</div>
</div>)
}
{error && <div className='transcript-error'>{error}</div>}
</div>
</>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.

import classNames from 'classnames';
import React from 'react';

type Props = {
text?: React.ReactNode;
}

const TranscriptSpinner = ({text = null}: Props) => {
return (
<span
id='loadingSpinner'
className={classNames('LoadingSpinner', {'with-text': Boolean(text)})}
style={{fontSize: '1.5em', color: '#0098FF'}}
data-testid='loadingSpinner'
>
<i
className='fa fa-circle-o-notch fa-spin fa-3x fa-fw'
style={{fontSize: '0.5em'}}
/>
{text}
</span>
);
};

export default React.memo(TranscriptSpinner);
1 change: 1 addition & 0 deletions webapp/channels/src/i18n/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -6084,6 +6084,7 @@
"view_user_group_modal.memberCount": "{member_count} {member_count, plural, one {Mitglied} other {Mitglieder}}",
"vocals.loading_transcript": "Transkript anzeigen...",
"vocals.transcript_title": "Audio-Transkription (automatisch generiert)",
"vocals.transcript_loading": "Audio-Transkription läuft...",
"vocals.transcript.error": "Die Audio ist leer.",
"web.footer.about": "Über",
"web.footer.help": "Hilfe",
Expand Down
1 change: 1 addition & 0 deletions webapp/channels/src/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -6051,6 +6051,7 @@
"view_image.zoom_reset": "Reset Zoom",
"vocals.loading_transcript": "View the transcript...",
"vocals.transcript_title": "Audio transcription (automatically generated)",
"vocals.transcript_loading": "Audio transcription in progress...",
"vocals.transcript.error": "The audio is empty.",
"view_user_group_modal.ldapSynced": "AD/LDAP SYNCED",
"view_user_group_modal.memberCount": "{member_count} {member_count, plural, one {Member} other {Members}}",
Expand Down
1 change: 1 addition & 0 deletions webapp/channels/src/i18n/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -5033,6 +5033,7 @@
"view_user_group_modal.memberCount": "{member_count} {member_count, plural, uno {Member} otros {Members}}",
"vocals.loading_transcript": "Ver la transcripción...",
"vocals.transcript_title": "Transcripción de audio (generada automáticamente)",
"vocals.transcript_loading": "Transcripción de audio en curso...",
"vocals.transcript.error": "El audio está vacío.",
"web.footer.about": "Acerca",
"web.footer.help": "Ayuda",
Expand Down
1 change: 1 addition & 0 deletions webapp/channels/src/i18n/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -4971,6 +4971,7 @@
"view_user_group_modal.memberCount": "{member_count} {member_count, plural, one {Membre} other {Membres}}",
"vocals.loading_transcript": "Voir la transcription...",
"vocals.transcript_title": "Transcription audio (générée automatiquement)",
"vocals.transcript_loading": "Transcription de l'audio en cours...",
"vocals.transcript.error": "L'audio est vide.",
"web.footer.about": "À propos",
"web.footer.help": "Aide",
Expand Down
1 change: 1 addition & 0 deletions webapp/channels/src/i18n/it.json
Original file line number Diff line number Diff line change
Expand Up @@ -3814,6 +3814,7 @@
"view_image_popover.publicLink": "Ottieni collegamento pubblico",
"vocals.loading_transcript": "Visualizza la trascrizione...",
"vocals.transcript_title": "Trascrizione audio (generata automaticamente)",
"vocals.transcript_loading": "Trascrizione audio in corso...",
"vocals.transcript.error": "L'audio è vuoto.",
"web.footer.about": "Riguardo a",
"web.footer.help": "Aiuto",
Expand Down

0 comments on commit 0dba5b5

Please sign in to comment.