Skip to content

Commit

Permalink
Merge pull request #438 from BinaryStudioAcademy/task/OV-403-create-v…
Browse files Browse the repository at this point in the history
…oices-page

OV-403: Create voices page
  • Loading branch information
nikita-remeslov authored Sep 27, 2024
2 parents 7bb52f9 + dfde8e1 commit 5d80b99
Show file tree
Hide file tree
Showing 32 changed files with 481 additions and 12 deletions.
1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
"@dnd-kit/core": "6.1.0",
"@emotion/react": "11.13.0",
"@emotion/styled": "11.13.0",
"@fortawesome/free-regular-svg-icons": "6.6.0",
"@fortawesome/free-solid-svg-icons": "6.6.0",
"@fortawesome/react-fontawesome": "0.2.2",
"@reduxjs/toolkit": "2.2.7",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ type Properties = {
isPlaying: boolean;
audioUrl: string;
onAudioEnd: () => void;
onSetDuration: (duration: number) => void;
onSetDuration?: (duration: number) => void;
};

const AudioPlayer: React.FC<Properties> = ({
Expand All @@ -41,7 +41,9 @@ const AudioPlayer: React.FC<Properties> = ({
getAudioData(audioUrl)
.then(({ durationInSeconds }) => {
setDurationInFrames(Math.round(durationInSeconds * FPS));
onSetDuration(durationInSeconds);
if (onSetDuration) {
onSetDuration(durationInSeconds);
}
})
.catch(() => {
setDurationInFrames(1);
Expand Down
1 change: 1 addition & 0 deletions frontend/src/bundles/common/components/components.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export { AudioPlayer } from './audio-player/audio-player.js';
export { Button } from './button/button.js';
export { ComponentsProvider } from './components-provider/components-provider.js';
export { Header } from './header/header.js';
Expand Down
24 changes: 24 additions & 0 deletions frontend/src/bundles/common/components/sidebar/sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
IconButton,
Link,
Spacer,
Text,
} from '~/bundles/common/components/components.js';
import { AppRoute } from '~/bundles/common/enums/enums.js';
import {
Expand Down Expand Up @@ -119,6 +120,29 @@ const Sidebar: React.FC<Properties> = ({ children }) => {
label="Templates"
/>
</Link>
{isCollapsed ? (
<Text color="background.600" variant="caption">
Assets
</Text>
) : (
<Text color="background.600" variant="bodySmall">
Assets
</Text>
)}
<Link to={AppRoute.VOICES}>
<SidebarItem
bg={activeButtonPage(AppRoute.VOICES)}
icon={
<Icon
as={IconName.VOICE}
boxSize={5}
color={activeIconPage(AppRoute.VOICES)}
/>
}
isCollapsed={isCollapsed}
label="AI Voices"
/>
</Link>
</Box>
<Spacer />
<SidebarItem
Expand Down
1 change: 1 addition & 0 deletions frontend/src/bundles/common/enums/app-route.enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const AppRoute = {
MY_AVATAR: '/my-avatar',
ANY: '*',
CREATE_AVATAR: '/create-avatar',
VOICES: '/voices',
TEMPLATES: '/templates',
} as const;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export { Logo } from './logo.js';
export { LogoText } from './logo-text.js';
export { OpenAi } from './open-ai.js';
export { Voice } from './voice.js';
14 changes: 14 additions & 0 deletions frontend/src/bundles/common/icons/custom-icons/voice.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { createIcon } from '@chakra-ui/react';

const Voice = createIcon({
displayName: 'Voice',
viewBox: '0 0 22 19',
path: (
<path
d="M18.95 13.9249L17.4 12.3749C18.1333 11.6916 18.7083 10.8832 19.125 9.9499C19.5417 9.01657 19.75 8.0249 19.75 6.9749C19.75 5.9249 19.5417 4.94157 19.125 4.0249C18.7083 3.10824 18.1333 2.30824 17.4 1.6249L18.95 0.0249023C19.8833 0.908236 20.625 1.9499 21.175 3.1499C21.725 4.3499 22 5.6249 22 6.9749C22 8.3249 21.725 9.5999 21.175 10.7999C20.625 11.9999 19.8833 13.0416 18.95 13.9249ZM15.75 10.7249L14.15 9.1249C14.45 8.84157 14.6917 8.52074 14.875 8.1624C15.0583 7.80407 15.15 7.40824 15.15 6.9749C15.15 6.54157 15.0583 6.14574 14.875 5.7874C14.6917 5.42907 14.45 5.10824 14.15 4.8249L15.75 3.2249C16.2833 3.70824 16.7 4.27074 17 4.9124C17.3 5.55407 17.45 6.24157 17.45 6.9749C17.45 7.70824 17.3 8.39574 17 9.0374C16.7 9.67907 16.2833 10.2416 15.75 10.7249ZM8 10.9749C6.9 10.9749 5.95833 10.5832 5.175 9.7999C4.39167 9.01657 4 8.0749 4 6.9749C4 5.8749 4.39167 4.93324 5.175 4.1499C5.95833 3.36657 6.9 2.9749 8 2.9749C9.1 2.9749 10.0417 3.36657 10.825 4.1499C11.6083 4.93324 12 5.8749 12 6.9749C12 8.0749 11.6083 9.01657 10.825 9.7999C10.0417 10.5832 9.1 10.9749 8 10.9749ZM0 18.9749V16.1749C0 15.6249 0.141667 15.1082 0.425 14.6249C0.708333 14.1416 1.1 13.7749 1.6 13.5249C2.45 13.0916 3.40833 12.7249 4.475 12.4249C5.54167 12.1249 6.71667 11.9749 8 11.9749C9.28333 11.9749 10.4583 12.1249 11.525 12.4249C12.5917 12.7249 13.55 13.0916 14.4 13.5249C14.9 13.7749 15.2917 14.1416 15.575 14.6249C15.8583 15.1082 16 15.6249 16 16.1749V18.9749H0ZM2 16.9749H14V16.1749C14 15.9916 13.9542 15.8249 13.8625 15.6749C13.7708 15.5249 13.65 15.4082 13.5 15.3249C12.9 15.0249 12.1292 14.7249 11.1875 14.4249C10.2458 14.1249 9.18333 13.9749 8 13.9749C6.81667 13.9749 5.75417 14.1249 4.8125 14.4249C3.87083 14.7249 3.1 15.0249 2.5 15.3249C2.35 15.4082 2.22917 15.5249 2.1375 15.6749C2.04583 15.8249 2 15.9916 2 16.1749V16.9749ZM8 8.9749C8.55 8.9749 9.02083 8.77907 9.4125 8.3874C9.80417 7.99574 10 7.5249 10 6.9749C10 6.4249 9.80417 5.95407 9.4125 5.5624C9.02083 5.17074 8.55 4.9749 8 4.9749C7.45 4.9749 6.97917 5.17074 6.5875 5.5624C6.19583 5.95407 6 6.4249 6 6.9749C6 7.5249 6.19583 7.99574 6.5875 8.3874C6.97917 8.77907 7.45 8.9749 8 8.9749Z"
fill="currentColor"
/>
),
});

export { Voice };
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { faHeart as faHeartRegular } from '@fortawesome/free-regular-svg-icons';
import {
faBackwardStep,
faCircle,
Expand All @@ -8,6 +9,7 @@ import {
faFileLines,
faFont,
faForwardStep,
faHeart,
faHouse,
faImage,
faMagnifyingGlass,
Expand Down Expand Up @@ -48,6 +50,8 @@ const Stop = convertIcon(faStop);
const VideoCamera = convertIcon(faVideoCamera);
const Image = convertIcon(faImage);
const Circle = convertIcon(faCircle);
const HeartFill = convertIcon(faHeart);
const HeartOutline = convertIcon(faHeartRegular);
const Magnifying = convertIcon(faMagnifyingGlass);

export {
Expand All @@ -60,6 +64,8 @@ export {
FileLines,
Font,
ForwardStep,
HeartFill,
HeartOutline,
House,
Image,
Magnifying,
Expand Down
7 changes: 6 additions & 1 deletion frontend/src/bundles/common/icons/icon-name.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
WarningIcon,
} from '@chakra-ui/icons';

import { Logo, LogoText, OpenAi } from './custom-icons/custom-icons.js';
import { Logo, LogoText, OpenAi, Voice } from './custom-icons/custom-icons.js';
import {
BackwardStep,
Circle,
Expand All @@ -24,6 +24,8 @@ import {
FileLines,
Font,
ForwardStep,
HeartFill,
HeartOutline,
House,
Image,
Magnifying,
Expand Down Expand Up @@ -78,6 +80,9 @@ const IconName = {
IMAGE: Image,
CIRCLE: Circle,
ARROW_BACK: ArrowBackIcon,
VOICE: Voice,
HEART_FILL: HeartFill,
HEART_OUTLINE: HeartOutline,
MAGNIFYING: Magnifying,
} as const;

Expand Down
29 changes: 27 additions & 2 deletions frontend/src/bundles/home/store/actions.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import { createAsyncThunk } from '@reduxjs/toolkit';

import { type AsyncThunkConfig } from '~/bundles/common/types/types.js';
import { type VideoGetAllResponseDto } from '~/bundles/home/types/types.js';
import {
type GenerateSpeechRequestDto,
type GenerateSpeechResponseDto,
type GetVoicesResponseDto,
type VideoGetAllResponseDto,
} from '~/bundles/home/types/types.js';

import { name as sliceName } from './slice.js';

Expand All @@ -24,4 +29,24 @@ const deleteVideo = createAsyncThunk<Promise<void>, string, AsyncThunkConfig>(
},
);

export { deleteVideo, loadUserVideos };
const loadVoices = createAsyncThunk<
GetVoicesResponseDto,
undefined,
AsyncThunkConfig
>(`${sliceName}/load-voices`, (_, { extra }) => {
const { speechApi } = extra;

return speechApi.loadVoices();
});

const generateScriptSpeechPreview = createAsyncThunk<
GenerateSpeechResponseDto,
GenerateSpeechRequestDto,
AsyncThunkConfig
>(`${sliceName}/generate-script-speech-preview`, (payload, { extra }) => {
const { speechApi } = extra;

return speechApi.generateScriptSpeech(payload);
});

export { deleteVideo, generateScriptSpeechPreview, loadUserVideos, loadVoices };
7 changes: 6 additions & 1 deletion frontend/src/bundles/home/store/home.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import { deleteVideo, loadUserVideos } from './actions.js';
import {
deleteVideo,
generateScriptSpeechPreview,
loadUserVideos,
} from './actions.js';
import { actions } from './slice.js';

const allActions = {
...actions,
deleteVideo,
loadUserVideos,
generateScriptSpeechPreview,
};

export { reducer } from './slice.js';
Expand Down
48 changes: 45 additions & 3 deletions frontend/src/bundles/home/store/slice.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,53 @@
import { type PayloadAction } from '@reduxjs/toolkit';
import { createSlice } from '@reduxjs/toolkit';

import { DataStatus } from '~/bundles/common/enums/enums.js';
import { type ValueOf } from '~/bundles/common/types/types.js';
import { type VideoGetAllItemResponseDto } from '~/bundles/home/types/types.js';
import {
type VideoGetAllItemResponseDto,
type Voice,
} from '~/bundles/home/types/types.js';

import { deleteVideo, loadUserVideos } from './actions.js';
import { deleteVideo, loadUserVideos, loadVoices } from './actions.js';

type VoicePlayer = {
isPlaying: boolean;
url: string | null;
};

type State = {
dataStatus: ValueOf<typeof DataStatus>;
videos: Array<VideoGetAllItemResponseDto> | [];
voices: Voice[];
voicePlayer: VoicePlayer;
};

const initialState: State = {
dataStatus: DataStatus.IDLE,
videos: [],
voices: [],
voicePlayer: {
isPlaying: false,
url: null,
},
};

const { reducer, actions, name } = createSlice({
initialState,
name: 'home',
reducers: {},
reducers: {
toogleVoiceLike(state, action: PayloadAction<string>) {
state.voices = state.voices.map((voice) => {
const { shortName, isLiked } = voice;
return shortName === action.payload
? { ...voice, isLiked: !isLiked }
: voice;
});
},
playVoice(state, action: PayloadAction<Partial<VoicePlayer>>) {
state.voicePlayer = { ...state.voicePlayer, ...action.payload };
},
},
extraReducers(builder) {
builder.addCase(loadUserVideos.pending, (state) => {
state.dataStatus = DataStatus.PENDING;
Expand All @@ -44,6 +72,20 @@ const { reducer, actions, name } = createSlice({
builder.addCase(deleteVideo.rejected, (state) => {
state.dataStatus = DataStatus.REJECTED;
});
builder.addCase(loadVoices.pending, (state) => {
state.dataStatus = DataStatus.PENDING;
});
builder.addCase(loadVoices.fulfilled, (state, action) => {
state.voices = action.payload.items.map((voice) => ({
...voice,
isLiked: false,
}));
state.dataStatus = DataStatus.FULFILLED;
});
builder.addCase(loadVoices.rejected, (state) => {
state.voices = [];
state.dataStatus = DataStatus.REJECTED;
});
},
});

Expand Down
4 changes: 4 additions & 0 deletions frontend/src/bundles/home/types/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
export { type Voice } from '~/bundles/home/types/voice.type.js';
export {
type GenerateSpeechRequestDto,
type GenerateSpeechResponseDto,
type GetVoicesResponseDto,
type VideoGetAllItemResponseDto,
type VideoGetAllResponseDto,
} from 'shared';
7 changes: 7 additions & 0 deletions frontend/src/bundles/home/types/voice.type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { type Voice as SharedVoice } from 'shared';

type Voice = SharedVoice & {
isLiked: boolean;
};

export { type Voice };
6 changes: 5 additions & 1 deletion frontend/src/bundles/studio/components/control/control.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ type Properties = {
label: string;
size: IconSizeT;
icon: ElementType;
iconColor?: string;
onClick?: (event: React.MouseEvent) => void;
width?: string;
height?: string;
Expand All @@ -22,6 +23,7 @@ const Control: React.FC<Properties> = ({
label,
size,
icon,
iconColor,
onClick = (): void => {},
width,
height,
Expand All @@ -37,7 +39,9 @@ const Control: React.FC<Properties> = ({
{...(height && { height })}
size={size}
variant={variant}
icon={<Icon as={icon} />}
icon={
<Icon as={icon} {...(iconColor && { color: iconColor })} />
}
onClick={onClick}
/>
</Tooltip>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { type PlayerRef } from '@remotion/player';
import { secondsToMilliseconds } from 'date-fns';
import { type RefObject } from 'react';

import { FPS } from '~/bundles/common/components/audio-player/constants/constants.js';
import { Flex, Spinner } from '~/bundles/common/components/components.js';
import {
useAppDispatch,
Expand All @@ -16,7 +17,6 @@ import { setItemsSpan } from '~/bundles/studio/helpers/set-items-span.js';
import { selectTotalDuration } from '~/bundles/studio/store/selectors.js';
import { actions as studioActions } from '~/bundles/studio/store/studio.js';

import { FPS } from '../audio-player/constants/constants.js';
import { Control } from '../components.js';
import { TimeDisplay } from './components/components.js';

Expand Down
2 changes: 1 addition & 1 deletion frontend/src/bundles/studio/pages/studio.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { type PlayerRef } from '@remotion/player';

import { AudioPlayer } from '~/bundles/common/components/audio-player/audio-player.js';
import {
Box,
Button,
Expand Down Expand Up @@ -30,7 +31,6 @@ import {
import { IconName } from '~/bundles/common/icons/icons.js';
import { notificationService } from '~/bundles/common/services/services.js';

import { AudioPlayer } from '../components/audio-player/audio-player.js';
import {
PlayerControls,
Timeline,
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/bundles/voices/components/components.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export { MainContent } from './main-content/main-content.js';
export { VoiceCard } from './voice-card/voice-card.js';
export { VoiceSection } from './voice-section/voice-section.js';
Loading

0 comments on commit 5d80b99

Please sign in to comment.