Skip to content

Commit

Permalink
feat(Message): new audio file preview [WPB-15809] (#18711)
Browse files Browse the repository at this point in the history
* feat: draft

* feat(Message): new audio file asset

* refactor(AudioAsset): adjust PlayIcon size and import styles

* refactor(AudioAsset): improve audio asset placeholder and card component

* refactor(AudioAsset): consolidate audio metadata into single prop

* refactor(AudioAsset): create AudioAssetRestricted component and update imports

* feat(AudioAsset):  bring back the old version

* test(ContentMessage): add isFileShareRestricted prop to test setup

* style(AudioAsset): set placeholder time text color

* test(CollectionDetails): update AudioAsset import path

* fix(AudioAsset): reorder file share restriction check

* refactor(AudioAsset): improve type safety for getAssetUrl parameter

* feat(i18n): add localization for audio asset interactions

* chore: bump @wireapp/core

* refactor(AudioAsset): use "V2" instead of "New" for the new version of components

* refactor(AudioAssets): use import aliases instead of relative paths

* refactor(AudioAssetPlaceholder): simplify loading state logic

* refactor(AudioPlayButton): simplify event listener management

* refactor(useAudioMetadata): simplify transfer state name handling

* fix(AudioPlayButton):  not all code paths return a value
  • Loading branch information
olafsulich authored Feb 6, 2025
1 parent d57b497 commit ab2adc3
Show file tree
Hide file tree
Showing 44 changed files with 1,392 additions and 37 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"@wireapp/avs-debugger": "0.0.7",
"@wireapp/commons": "5.4.1",
"@wireapp/core": "46.17.0",
"@wireapp/react-ui-kit": "9.32.0",
"@wireapp/react-ui-kit": "9.35.0",
"@wireapp/store-engine-dexie": "2.1.15",
"@wireapp/telemetry": "0.3.1",
"@wireapp/webapp-events": "0.28.0",
Expand Down Expand Up @@ -79,6 +79,7 @@
"@formatjs/cli": "6.5.1",
"@roamhq/wrtc": "0.8.0",
"@testing-library/dom": "^10.4.0",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "16.2.0",
"@types/dexie-batch": "0.4.7",
"@types/eslint": "8.56.5",
Expand Down
1 change: 1 addition & 0 deletions setupTests.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import 'core-js/full/reflect';
import 'intersection-observer';
import 'core-js/stable/structured-clone';
import 'fake-indexeddb/auto';
import '@testing-library/jest-dom';

import 'src/script/util/test/mock/createObjectURLMock';
import 'src/script/util/test/mock/cryptoMock';
Expand Down
5 changes: 5 additions & 0 deletions src/i18n/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,11 @@
"conversationAssetUploadFailed": "Upload Failed",
"conversationAssetUploading": "Uploading…",
"conversationAudioAssetRestricted": "Receiving audio messages is prohibited",
"conversationAudioAssetUploading": "Uploading: {name}",
"conversationAudioAssetUploadFailed": "Upload failed: {name}",
"conversationAudioAssetPlay": "Play",
"conversationAudioAssetPause": "Pause",
"conversationAudioAssetCancel": "Cancel",
"conversationButtonSeparator": "or",
"conversationClassified": "Security level: VS-NfD",
"conversationConnectWithNewUsers": "Connect with People",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,6 @@
import {CSSObject} from '@emotion/react';

export const wrapperStyles: CSSObject = {
width: '100%',
marginTop: '8px',
};
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,9 @@ interface FileCardHeaderProps {
}

export const FileCardHeader = ({children}: FileCardHeaderProps) => {
return <header css={wrapperStyles}>{children}</header>;
return (
<header css={wrapperStyles} data-file-card="header">
{children}
</header>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,9 @@ export const textStyles: CSSObject = {
WebkitLineClamp: 'var(--truncate-after-lines)',
WebkitBoxOrient: 'vertical',
overflow: 'hidden',
marginTop: '4px',

'[data-file-card="header"] &': {
marginTop: '0',
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ const wrapperStyles: CSSObject = {
borderRadius: '10px',
padding: '8px',
position: 'relative',

'body.theme-dark &': {
backgroundColor: 'var(--foreground-fade-8)',
border: '1px solid transparent',
},
};

export const wrapperStylesSmall: CSSObject = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ interface FileCardOptions {
/** Formatted file size
* @example '1.2 MB'
*/
size: string;
size?: string;
}

interface FileCardRootProps extends Pick<FileCardOptions, 'variant'> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import {CSSObject} from '@emotion/react';

export const wrapperStyles: CSSObject = {
alignItems: 'center',
color: 'var(--gray-70)',
color: 'var(--foreground)',
display: 'flex',
fontSize: 'var(--font-size-small)',
height: '16px',
Expand Down
3 changes: 2 additions & 1 deletion src/script/components/FileCard/FileCardType/FileCardType.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ export const FileCardType = () => {
const {extension, size} = useFileCardContext();
return (
<p css={wrapperStyles}>
<span css={categoryStyles}>{extension}</span> ({size})
<span css={categoryStyles}>{extension}</span>
{size && ` (${size})`}
</p>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ interface FileCardContextValue {
variant: 'small' | 'large';
extension: string;
name: string;
size: string;
size?: string;
}

const FileCardContext = createContext<FileCardContextValue | null>(null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ describe('message', () => {
onRetry: jest.fn(),
selfId: {domain: '', id: createUuid()},
isMsgElementsFocusable: true,
isFileShareRestricted: false,
};
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ export interface ContentMessageProps extends Omit<MessageActions, 'onClickResetS
isMsgElementsFocusable: boolean;
onClickReaction: (emoji: string) => void;
is1to1?: boolean;
isFileShareRestricted: boolean;
}

export const ContentMessageComponent = ({
Expand All @@ -93,6 +94,7 @@ export const ContentMessageComponent = ({
onClickReaction,
onClickDetails,
is1to1,
isFileShareRestricted,
}: ContentMessageProps) => {
const messageRef = useRef<HTMLDivElement | null>(null);

Expand Down Expand Up @@ -247,6 +249,7 @@ export const ContentMessageComponent = ({
onClickMessage={onClickMessage}
isMessageFocused={msgFocusState}
is1to1Conversation={conversation.is1to1()}
isFileShareRestricted={isFileShareRestricted}
onClickDetails={() => onClickDetails(message)}
/>
))}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import {includesOnlyEmojis} from 'Util/EmojiUtil';
import {t} from 'Util/LocalizerUtil';
import {formatDateNumeral, formatTimeShort, isBeforeToday} from 'Util/TimeUtil';

import {AudioAsset} from './asset/AudioAsset';
import {AudioAsset} from './asset/AudioAsset/AudioAsset';
import {FileAsset} from './asset/FileAssetComponent';
import {LocationAsset} from './asset/LocationAsset';
import {TextMessageRenderer} from './asset/TextMessageRenderer';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,23 +22,23 @@ import React, {useEffect, useState} from 'react';
import cx from 'classnames';
import {container} from 'tsyringe';

import {RestrictedAudio} from 'Components/asset/RestrictedAudio';
import * as Icon from 'Components/Icon';
import {AssetTransferState} from 'src/script/assets/AssetTransferState';
import type {ContentMessage} from 'src/script/entity/message/ContentMessage';
import type {FileAsset} from 'src/script/entity/message/FileAsset';
import {TeamState} from 'src/script/team/TeamState';
import {useKoSubscribableChildren} from 'Util/ComponentUtil';
import {getLogger} from 'Util/Logger';
import {formatSeconds} from 'Util/TimeUtil';
import {useEffectRef} from 'Util/useEffectRef';

import {AssetHeader} from './AssetHeader';
import {AudioSeekBar} from './controls/AudioSeekBar';
import {MediaButton} from './controls/MediaButton';
import {SeekBar} from './controls/SeekBar';
import {AssetUrl, useAssetTransfer} from './useAssetTransfer';
import {RestrictedAudio} from './RestrictedAudio/RestrictedAudio';

import {AssetTransferState} from '../../../../../assets/AssetTransferState';
import type {ContentMessage} from '../../../../../entity/message/ContentMessage';
import type {FileAsset} from '../../../../../entity/message/FileAsset';
import {TeamState} from '../../../../../team/TeamState';
import {AssetHeader} from '../AssetHeader';
import {AudioSeekBar} from '../controls/AudioSeekBar';
import {MediaButton} from '../controls/MediaButton';
import {SeekBar} from '../controls/SeekBar';
import {AssetUrl, useAssetTransfer} from '../useAssetTransfer';

const logger = getLogger('AudioAssetComponent');

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* Wire
* Copyright (C) 2025 Wire Swiss GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*
*/

import {CSSObject} from '@emotion/react';

export const contentStyles: CSSObject = {
height: '48px',
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* Wire
* Copyright (C) 2025 Wire Swiss GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*
*/

import {ReactNode, SyntheticEvent} from 'react';

import {FileCard} from 'Components/FileCard/FileCard';

import {contentStyles} from './AudioAssetCard.styles';

interface AudioMetadata {
name: string;
extension: string;
size: string;
duration: number;
loudnessPreview: boolean;
}

interface AudioAssetCardProps {
src?: string;
getAudioElementRef: (element: HTMLMediaElement) => void;
metadata: AudioMetadata;
isError?: boolean;
isLoading?: boolean;
loadingProgress?: number;
onTimeUpdate: (event: SyntheticEvent<HTMLAudioElement>) => void;
children: ReactNode;
}

export const AudioAssetCard = ({
src,
metadata,
children,
isError,
isLoading,
loadingProgress,
getAudioElementRef,
onTimeUpdate,
}: AudioAssetCardProps) => {
return (
<FileCard.Root variant="large" extension={metadata.extension} name={metadata.name} size={metadata.size}>
<FileCard.Header>
<FileCard.Icon />
<FileCard.Type />
</FileCard.Header>
<FileCard.Name />
<FileCard.Content>
{/* eslint-disable-next-line jsx-a11y/media-has-caption */}
<audio ref={getAudioElementRef} src={src} onTimeUpdate={onTimeUpdate} />
<div css={contentStyles}>{children}</div>
</FileCard.Content>
{isError && <FileCard.Error />}
{isLoading && <FileCard.Loading progress={loadingProgress} />}
</FileCard.Root>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
* Wire
* Copyright (C) 2025 Wire Swiss GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*
*/

import {CSSObject} from '@emotion/react';

export const wrapperStyles: CSSObject = {
display: 'flex',
alignItems: 'flex-start',
justifyContent: 'flex-start',
overflow: 'hidden',
gap: '8px',
};

export const buttonStyles: CSSObject = {
width: '32px',
height: '32px',
borderRadius: '50%',
backgroundColor: 'var(--icon-button-primary-disabled-bg)',
border: '1px solid var(--icon-button-primary-disabled-border)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
flexShrink: 0,
cursor: 'not-allowed',

'& svg': {
fill: 'var(--gray-70)',
},

'body.dark &': {
'& svg': {
fill: 'var(--gray-60)',
},
},
};

export const seekBarStyles: CSSObject = {
height: '32px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
};

export const timerWrapperStyles: CSSObject = {
width: '100%',
display: 'flex',
justifyContent: 'space-between',
};

export const timerWrapperStylesWithLoading: CSSObject = {
...timerWrapperStyles,
paddingRight: '16px',
};

export const timeStyles: CSSObject = {
margin: 0,
fontSize: 'var(--font-size-xsmall)',
fontWeight: 'var(--font-weight-regular)',
color: 'var(--foreground)',
};
Loading

0 comments on commit ab2adc3

Please sign in to comment.