Skip to content

Commit

Permalink
Merge pull request #106 from teamViNO/feature-074
Browse files Browse the repository at this point in the history
Feature 074
  • Loading branch information
whistleJs authored Feb 18, 2024
2 parents f910a81 + 1f97077 commit b50a2e0
Show file tree
Hide file tree
Showing 16 changed files with 292 additions and 49 deletions.
7 changes: 6 additions & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@
<div id="root"></div>

<script type="module" src="/src/main.tsx"></script>
<script src="https://developers.kakao.com/sdk/js/kakao.js"></script>
<script
src="https://developers.kakao.com/sdk/js/kakao.js"
async
defer
></script>
<script src="https://www.youtube.com/iframe_api" async defer></script>
</body>
</html>
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"@types/jest": "^29.5.11",
"@types/node": "^20.11.17",
"@types/react-modal": "^3.16.3",
"@types/youtube": "^0.0.50",
"axios": "^1.6.4",
"lodash": "^4.17.21",
"react": "^18.2.0",
Expand Down
5 changes: 5 additions & 0 deletions src/assets/icons/pause.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 8 additions & 1 deletion src/components/Home/SearchYoutube.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,13 @@ const SearchYoutube = ({ searchRef }: Props) => {
}
};

const handleChangeInput: React.ChangeEventHandler<HTMLInputElement> = (e) => {
setInputLink(e.target.value);
setVideoLink(null);
setStatus('NONE');
setProgress(0);
};

const handleClickCreateVideoButton = async () => {
if (!modelingData) return;

Expand Down Expand Up @@ -153,7 +160,7 @@ const SearchYoutube = ({ searchRef }: Props) => {
type="text"
value={inputLink}
disabled={status === 'CONTINUE'}
onChange={(e) => setInputLink(e.target.value)}
onChange={handleChangeInput}
placeholder="https://youtube.com/..."
/>
</div>
Expand Down
104 changes: 89 additions & 15 deletions src/components/SummaryPage/SummaryDetailBox/SummaryDetailBox.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import { useRecoilState, useRecoilValue } from 'recoil';
import { useEffect, useRef } from 'react';
import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil';

import { updateVideoCategoryIdAPI } from '@/apis/videos';

import { IVideo } from '@/models/video';

import {
summaryIsEditingViewState,
summaryPlaySubHeadingIdState,
summaryUpdateVideoState,
summaryVideoState,
summaryVideoTimeState,
} from '@/stores/summary';
import { toastListState } from '@/stores/toast';

Expand All @@ -24,9 +27,15 @@ type Props = {
};

const SummaryDetailBox = ({ onRefresh }: Props) => {
const player = useRef<YT.Player>();

const summaryVideo = useRecoilValue(summaryVideoState) as IVideo;
const summaryUpdateVideo = useRecoilValue(summaryUpdateVideoState);
const setSummaryVideoTime = useSetRecoilState(summaryVideoTimeState);
const isEditingView = useRecoilValue(summaryIsEditingViewState);
const [playSubHeadingId, setPlaySubHeadingId] = useRecoilState(
summaryPlaySubHeadingIdState,
);
const [toastList, setToastList] = useRecoilState(toastListState);

const subHeading = isEditingView
Expand All @@ -50,6 +59,75 @@ const SummaryDetailBox = ({ onRefresh }: Props) => {
}
};

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const handleMessage = (e: any) => {
if (e.origin === 'https://www.youtube.com') {
try {
const { info } = JSON.parse(e.data);

if (!info) return;

setPlaySubHeadingId((id) => {
const item = subHeading.find((s) => s.id === id);

// END or PAUSE
if ([0, 2].includes(info.playerState)) {
return -1;
}

if (item) {
if (
item.start_time > info.currentTime ||
info.currentTime > item.end_time
)
return -1;
}

return id;
});

setSummaryVideoTime(info.currentTime);
} catch (e) {
console.error(e);
}
}
};

useEffect(() => {
if (player.current) return;

player.current = new YT.Player('player', {
videoId: summaryVideo.youtube_id,
});

window.onmessage = handleMessage;

return () => {
setSummaryVideoTime(0);
setPlaySubHeadingId(-1);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [summaryVideo]);

useEffect(() => {
if (player.current) {
if (playSubHeadingId > -1) {
const item = subHeading.find((s) => s.id === playSubHeadingId);

if (item) {
player.current.seekTo(item.start_time, true);
player.current.playVideo();
}
} else if (playSubHeadingId === -2) {
// 영상 멈추기
player.current.pauseVideo();

setPlaySubHeadingId(-1);
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [playSubHeadingId]);

return (
<div
style={{
Expand All @@ -73,19 +151,7 @@ const SummaryDetailBox = ({ onRefresh }: Props) => {
))}
</div>

<iframe
src={`https://www.youtube.com/embed/QXDiRtANAzA?${
isEditingView && 'start=10&end=18&autoplay=1&disablekb=0'
}`}
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
allowFullScreen
style={{
margin: '20px 0',
width: '100%',
aspectRatio: '16 / 9',
borderRadius: 16,
}}
/>
<div id="player" />

<CategorySelectBox
disabled={isEditingView}
Expand All @@ -104,7 +170,15 @@ const SummaryDetailBox = ({ onRefresh }: Props) => {
}}
>
{subHeading.map((item, i) => (
<div key={item.id} className="subtitle">
<div
key={item.id}
className={`subtitle ${
playSubHeadingId > -1 &&
playSubHeadingId !== item.id &&
'disabled'
}`}
onClick={() => setPlaySubHeadingId(item.id)}
>
<span className="subtitle-index">{i + 1}</span>
<span className="subtitle-text">{item.name}</span>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
import { useMemo } from 'react';
import { useRecoilValue } from 'recoil';
import { useRecoilState, useRecoilValue } from 'recoil';

import PauseIcon from '@/assets/icons/pause.svg?react';
import PlayIcon from '@/assets/icons/play.svg?react';

import { IVideo } from '@/models/video';

import { summarySearchIsOpenState } from '@/stores/ui';
import { summaryTransformModalState } from '@/stores/modal';
import { summaryVideoState } from '@/stores/summary';
import {
summaryPlaySubHeadingIdState,
summaryVideoState,
} from '@/stores/summary';

import { escapeHTML } from '@/utils/string';
import { formatTime } from '@/utils/date';

type Props = {
keyword: string;
Expand All @@ -19,6 +24,9 @@ const ScriptViewer = ({ keyword }: Props) => {
const summaryVideo = useRecoilValue(summaryVideoState) as IVideo;
const searchIsOpen = useRecoilValue(summarySearchIsOpenState);
const transformModalIsOpen = useRecoilValue(summaryTransformModalState);
const [playSubHeadingId, setPlaySubHeadingId] = useRecoilState(
summaryPlaySubHeadingIdState,
);

const scriptList = useMemo(() => {
return summaryVideo.subHeading.map(({ name, content, ...others }) => {
Expand Down Expand Up @@ -48,20 +56,36 @@ const ScriptViewer = ({ keyword }: Props) => {
});
}, [summaryVideo, keyword, searchIsOpen, transformModalIsOpen]);

const handleClickPlayButton = (id: number) => {
if (playSubHeadingId === id) {
setPlaySubHeadingId(-2);
} else {
setPlaySubHeadingId(id);
}
};

return (
<div className="script-container">
{scriptList.map((script) => (
<div key={script.id}>
<div
style={{
display: 'flex',
alignItems: 'center',
alignItems: 'flex-start',
justifyContent: 'space-between',
gap: 4,
}}
>
<div style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
<span className="play-button">
<PlayIcon width={36} height={36} />
<div style={{ display: 'flex', alignItems: 'flex-start', gap: 4 }}>
<span
className="play-button"
onClick={() => handleClickPlayButton(script.id)}
>
{script.id === playSubHeadingId ? (
<PauseIcon width={36} height={36} />
) : (
<PlayIcon width={36} height={36} />
)}
</span>

<span
Expand All @@ -70,9 +94,17 @@ const ScriptViewer = ({ keyword }: Props) => {
/>
</div>

<span className="script-badge">
{script.start_time}-{script.end_time}
</span>
<div
style={{
display: 'flex',
alignItems: 'center',
minHeight: 36,
}}
>
<span className="script-badge">
{formatTime(script.start_time)}-{formatTime(script.end_time)}
</span>
</div>
</div>

<div
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useEffect, useState } from 'react';
import { useRecoilState, useRecoilValue } from 'recoil';
import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil';

import { getVideoAPI, updateVideoAPI } from '@/apis/videos';

Expand All @@ -9,6 +9,7 @@ import { IVideo } from '@/models/video';

import {
summaryIsEditingViewState,
summaryPlaySubHeadingIdState,
summaryUpdateVideoState,
summaryVideoState,
} from '@/stores/summary';
Expand All @@ -25,6 +26,7 @@ type Props = {

const ToolBox = ({ onRefresh, onChangeKeyword }: Props) => {
const summaryVideo = useRecoilValue(summaryVideoState) as IVideo;
const setPlaySubHeadingId = useSetRecoilState(summaryPlaySubHeadingIdState);
const [summaryUpdateVideo, setSummaryUpdateVideo] = useRecoilState(
summaryUpdateVideoState,
);
Expand All @@ -40,6 +42,7 @@ const ToolBox = ({ onRefresh, onChangeKeyword }: Props) => {
};

const handleClickModifyIcon = () => {
setPlaySubHeadingId(-1);
setIsEditingView(true);
setSummaryUpdateVideo({ ...summaryVideo });
};
Expand Down
Loading

0 comments on commit b50a2e0

Please sign in to comment.