Skip to content

Commit c18a75b

Browse files
Merge pull request #357 from BinaryStudioAcademy/task/OV-352-add-logic-for-edit-video-button
OV-352: Add logic for edit video button
2 parents 5b87517 + bb7dcc6 commit c18a75b

File tree

10 files changed

+97
-29
lines changed

10 files changed

+97
-29
lines changed

backend/src/bundles/videos/video.entity.ts

+13-17
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,14 @@
1+
import { type Composition } from 'shared';
2+
13
import { type Entity } from '~/common/types/types.js';
24

35
class VideoEntity implements Entity {
46
private 'id': string | null;
5-
67
private 'userId': string;
7-
88
private 'name': string;
9-
109
private 'url': string | null;
11-
1210
public 'previewUrl': string;
13-
14-
public 'composition': string;
11+
public 'composition': Composition;
1512

1613
public 'createdAt': string;
1714

@@ -28,7 +25,7 @@ class VideoEntity implements Entity {
2825
userId: string;
2926
name: string;
3027
previewUrl: string;
31-
composition: string;
28+
composition: Composition;
3229
url: string | null;
3330
createdAt: string;
3431
}) {
@@ -45,24 +42,24 @@ class VideoEntity implements Entity {
4542
id,
4643
userId,
4744
name,
48-
url,
49-
composition,
5045
previewUrl,
46+
composition,
47+
url,
5148
createdAt,
5249
}: {
53-
id: string;
50+
id: string | null;
5451
userId: string;
5552
name: string;
5653
previewUrl: string;
57-
composition: string;
54+
composition: Composition;
5855
url: string | null;
5956
createdAt: string;
6057
}): VideoEntity {
6158
return new VideoEntity({
6259
id,
6360
userId,
6461
name,
65-
composition,
62+
composition: composition,
6663
previewUrl,
6764
url,
6865
createdAt,
@@ -72,16 +69,15 @@ class VideoEntity implements Entity {
7269
public static initializeNew({
7370
userId,
7471
name,
75-
composition,
7672
previewUrl,
73+
composition,
7774
url,
7875
createdAt = new Date().toISOString(),
7976
}: {
8077
userId: string;
8178
name: string;
8279
previewUrl: string;
83-
composition: string;
84-
80+
composition: Composition;
8581
url?: string;
8682
createdAt?: string;
8783
}): VideoEntity {
@@ -102,7 +98,7 @@ class VideoEntity implements Entity {
10298
name: string;
10399
url: string | null;
104100
previewUrl: string;
105-
composition: string;
101+
composition: Composition;
106102
createdAt: string;
107103
} {
108104
return {
@@ -120,7 +116,7 @@ class VideoEntity implements Entity {
120116
userId: string;
121117
name: string;
122118
previewUrl: string;
123-
composition: string;
119+
composition: Composition;
124120
url: string | null;
125121
createdAt: string;
126122
} {

backend/src/bundles/videos/video.model.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { type Composition } from 'shared';
2+
13
import {
24
AbstractModel,
35
DatabaseTableName,
@@ -10,7 +12,7 @@ class VideoModel extends AbstractModel {
1012

1113
public 'previewUrl': string;
1214

13-
public 'composition': string;
15+
public 'composition': Composition;
1416

1517
public 'url': string | null;
1618

backend/src/bundles/videos/video.repository.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ class VideoRepository implements Repository {
3434
public async create(entity: VideoEntity): Promise<VideoEntity> {
3535
const { userId, name, url, composition, previewUrl } =
3636
entity.toNewObject();
37+
3738
const item = await this.videoModel
3839
.query()
3940
.insert({
@@ -56,7 +57,7 @@ class VideoRepository implements Repository {
5657
const data: Partial<VideoModel> = {};
5758

5859
if (payload.composition) {
59-
data.composition = JSON.stringify(payload.composition);
60+
data.composition = payload.composition;
6061
data.previewUrl = payload.composition.scenes[0]?.avatar?.url ?? '';
6162
}
6263

@@ -67,6 +68,7 @@ class VideoRepository implements Repository {
6768
if (payload.url) {
6869
data.url = payload.url;
6970
}
71+
7072
const updatedItem = await this.videoModel
7173
.query()
7274
.patchAndFetchById(id, data)

backend/src/bundles/videos/video.service.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,8 @@ class VideoService implements Service {
5959
const video = await this.videoRepository.create(
6060
VideoEntity.initializeNew({
6161
name: payload.name,
62-
composition: JSON.stringify(payload.composition),
63-
previewUrl: payload.composition?.scenes[0]?.avatar?.url || '',
62+
composition: payload.composition,
63+
previewUrl: payload.composition?.scenes[0]?.avatar?.url ?? '',
6464
userId: payload.userId,
6565
}),
6666
);

frontend/src/bundles/home/components/video-card/video-card.tsx

+11-2
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,12 @@ import {
1515
MenuList,
1616
Text,
1717
} from '~/bundles/common/components/components.js';
18+
import { AppRoute } from '~/bundles/common/enums/enums.js';
1819
import {
1920
useAppDispatch,
2021
useCallback,
2122
useEffect,
23+
useNavigate,
2224
useState,
2325
} from '~/bundles/common/hooks/hooks.js';
2426
import { IconName, IconSize } from '~/bundles/common/icons/icons.js';
@@ -44,6 +46,7 @@ const VideoCard: React.FC<Properties> = ({
4446
previewUrl,
4547
}) => {
4648
const dispatch = useAppDispatch();
49+
const navigate = useNavigate();
4750

4851
const [isVideoModalOpen, setIsVideoModalOpen] = useState(false);
4952
const [isWarningModalOpen, setIsWarningModalOpen] = useState(false);
@@ -61,9 +64,15 @@ const VideoCard: React.FC<Properties> = ({
6164

6265
const handleIconClick = useCallback(() => {
6366
if (url) {
64-
setIsVideoModalOpen(true);
67+
return setIsVideoModalOpen(true);
6568
}
66-
}, [url]);
69+
70+
navigate(AppRoute.STUDIO, {
71+
state: {
72+
id,
73+
},
74+
});
75+
}, [url, navigate, id]);
6776

6877
const date = new Date(createdAt);
6978
const formattedDate = format(date, 'MMM d, yyyy, h:mm a');

frontend/src/bundles/studio/pages/studio.tsx

+18-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { type PlayerRef } from '@remotion/player';
2-
import { useNavigate } from 'react-router-dom';
32

43
import {
54
Box,
@@ -21,6 +20,8 @@ import {
2120
useAppSelector,
2221
useCallback,
2322
useEffect,
23+
useLocation,
24+
useNavigate,
2425
useRef,
2526
} from '~/bundles/common/hooks/hooks.js';
2627
import { IconName } from '~/bundles/common/icons/icons.js';
@@ -42,16 +43,29 @@ import {
4243
} from '../constants/constants.js';
4344
import { NotificationMessage, NotificationTitle } from '../enums/enums.js';
4445
import { getVoicesConfigs } from '../helpers/helpers.js';
46+
import { selectVideoDataById } from '../store/selectors.js';
4547
import { actions as studioActions } from '../store/studio.js';
4648

4749
const Studio: React.FC = () => {
50+
const { state: locationState } = useLocation();
51+
52+
const videoData = useAppSelector((state) =>
53+
selectVideoDataById(state, locationState?.id),
54+
);
55+
4856
const { scenes, scripts, videoName, videoId, scriptPlayer } =
4957
useAppSelector(({ studio }) => studio);
5058

5159
const playerReference = useRef<PlayerRef>(null);
5260
const dispatch = useAppDispatch();
5361
const navigate = useNavigate();
5462

63+
useEffect((): void => {
64+
if (videoData) {
65+
void dispatch(studioActions.loadVideoData(videoData));
66+
}
67+
}, [dispatch, videoData]);
68+
5569
const handleResize = useCallback(() => {
5670
dispatch(studioActions.changeVideoSize());
5771
}, [dispatch]);
@@ -98,7 +112,9 @@ const Studio: React.FC = () => {
98112
}, [dispatch, navigate, scenes, scripts, videoId, videoName]);
99113

100114
useEffect(() => {
101-
return () => void dispatch(studioActions.resetStudio());
115+
return () => {
116+
dispatch(studioActions.resetStudio());
117+
};
102118
}, [dispatch]);
103119

104120
const handleSaveDraft = useCallback((): void => {

frontend/src/bundles/studio/store/selectors.ts

+15-2
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,16 @@ import { secondsToMilliseconds } from 'date-fns';
33

44
import { type RootState } from '~/bundles/common/types/types.js';
55

6-
import { type Script } from '../types/types.js';
6+
import {
7+
type Script,
8+
type VideoGetAllItemResponseDto,
9+
} from '../types/types.js';
710

811
const selectScrips = (state: RootState): Script[] => state.studio.scripts;
912

13+
const selectVideos = (state: RootState): VideoGetAllItemResponseDto[] =>
14+
state.home.videos;
15+
1016
const selectTotalDuration = createSelector([selectScrips], (scripts) => {
1117
const totalDuration = scripts.reduce(
1218
(total, script) => total + script.duration,
@@ -16,4 +22,11 @@ const selectTotalDuration = createSelector([selectScrips], (scripts) => {
1622
return secondsToMilliseconds(totalDuration);
1723
});
1824

19-
export { selectTotalDuration };
25+
const selectVideoDataById = createSelector(
26+
[selectVideos, (_, id: string): string => id],
27+
(videos, id) => {
28+
return videos.find((video) => video.id === id);
29+
},
30+
);
31+
32+
export { selectTotalDuration, selectVideoDataById };

frontend/src/bundles/studio/store/slice.ts

+27-1
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,13 @@ import {
2525
import { type Script } from '../types/script.type.js';
2626
import {
2727
type AvatarGetResponseDto,
28+
type CompositionScript,
2829
type DestinationPointer,
2930
type RowType,
3031
type Scene,
3132
type SceneAvatar,
3233
type TimelineItemWithSpan,
34+
type VideoGetAllItemResponseDto,
3335
type Voice,
3436
} from '../types/types.js';
3537
import {
@@ -100,7 +102,7 @@ const initialState: State = {
100102
selectedScriptId: null,
101103
videoSize: VideoPreview.LANDSCAPE,
102104
videoName: 'Untitled Video',
103-
isDraftSaved: false,
105+
isDraftSaved: true,
104106
videoId: null,
105107
voices: [],
106108
ui: {
@@ -332,6 +334,30 @@ const { reducer, actions, name } = createSlice({
332334
avatars: state.avatars,
333335
};
334336
},
337+
loadVideoData(
338+
state,
339+
action: PayloadAction<VideoGetAllItemResponseDto>,
340+
) {
341+
const { id, name, composition } = action.payload;
342+
343+
state.videoName = name;
344+
state.videoId = id;
345+
state.scenes = composition.scenes;
346+
state.scripts = composition.scripts.map(
347+
(script: CompositionScript) => {
348+
const voice = state.voices.find(
349+
(voice) => voice.name === script.voiceName,
350+
);
351+
352+
return {
353+
...script,
354+
iconName: PlayIconNames.READY,
355+
voice: voice ?? DEFAULT_VOICE,
356+
url: null,
357+
};
358+
},
359+
);
360+
},
335361
},
336362
extraReducers(builder) {
337363
builder.addCase(loadAvatars.pending, (state) => {

frontend/src/bundles/studio/types/types.ts

+2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ export {
1111
type TimelineItemWithSpan,
1212
} from './timeline-item.type.js';
1313
export {
14+
type Composition,
15+
type Script as CompositionScript,
1416
type CreateVideoRequestDto,
1517
type GenerateSpeechRequestDto,
1618
type GenerateSpeechResponseDto,

shared/src/bundles/videos/types/video-get-all-item-response-dto.type.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1+
import { type Composition } from '../../avatar-videos/types/types.js';
2+
13
type VideoGetAllItemResponseDto = {
24
id: string;
35
userId: string;
46
name: string;
57
url: string | null;
68
previewUrl: string;
7-
composition: string;
9+
composition: Composition;
810
createdAt: string;
911
};
1012

0 commit comments

Comments
 (0)