Skip to content

Commit

Permalink
Add production-progress page while waiting for audiobook production
Browse files Browse the repository at this point in the history
  • Loading branch information
sprel committed Nov 12, 2024
1 parent 0d2105e commit 23124df
Show file tree
Hide file tree
Showing 11 changed files with 2,770 additions and 2,449 deletions.
39 changes: 39 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@
"react-dev-utils": "^12.0.1",
"react-docgen": "^7.1.0",
"react-dom": "^18.3.1",
"react-icon": "^1.0.0",
"react-icons": "^5.3.0",
"react-inspector": "^6.0.2",
"react-refresh": "^0.11.0",
"react-router-dom": "^6.27.0",
Expand Down
2 changes: 1 addition & 1 deletion src/database/bookinfo.json
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@
"author": "Author1",
"press": "Press1",
"runningTime": "10",
"intro": "국가는 국민 모두의 생산 및 생활의 기반이 되는 국토의 효율적이고 균형있는 이용·개발과 보전을 위하여 법률이 정하는 바에 의하여 그에 관한 필요한 제한과 의무를 과할 수 있다. 선거운동은 각급 선거관리위원회의 관리하에 법률이 정하는 범위안에서 하되, 균등한 기회가 보장되어야 한다. 대통령은 조약을 체결·비준하고, 외교사절을 신임·접수 또는 파견하며, 선전포고와 강화를 한다.",
"intro": "국가는 국민 모두의 생산 및 생활의 기반이 되는 국토의 효율적이고 균형있는 이용·개발과 보전을 위하여 법률이 정하는 바에 의하여 그에 관한 필요한 제한과 의무를 과할 수 있다. 선거운동은 각급 선거관리위원회의 관리하에 법률이 정하는 범위안에서 하되, 균등한 기회가 보장되어야 한다. 대통령은 조약을 체결·비준하고, 외교사절을 신임·접수 또는 파견하며, 선전포고와 강화를 한다.\n국가는 국민 모두의 생산 및 생활의 기반이 되는 국토의 효율적이고 균형있는 이용·개발과 보전을 위하여 법률이 정하는 바에 의하여 그에 관한 필요한 제한과 의무를 과할 수 있다. 선거운동은 각급 선거관리위원회의 관리하에 법률이 정하는 범위안에서 하되, 균등한 기회가 보장되어야 한다. 대통령은 조약을 체결·비준하고, 외교사절을 신임·접수 또는 파견하며, 선전포고와 강화를 한다.\n국가는 국민 모두의 생산 및 생활의 기반이 되는 국토의 효율적이고 균형있는 이용·개발과 보전을 위하여 법률이 정하는 바에 의하여 그에 관한 필요한 제한과 의무를 과할 수 있다. 선거운동은 각급 선거관리위원회의 관리하에 법률이 정하는 범위안에서 하되, 균등한 기회가 보장되어야 한다. 대통령은 조약을 체결·비준하고, 외교사절을 신임·접수 또는 파견하며, 선전포고와 강화를 한다.",
"contents": ["1. 국회", "2. 선거"]
},
{
Expand Down
File renamed without changes
13 changes: 6 additions & 7 deletions src/pages/Audiobook_Player.css
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
.textbook-frame {
width: 30rem;
height: 40rem;
max-width: 48%;
display: flex;
justify-content: center;
align-items: center;
Expand All @@ -15,7 +16,9 @@
}
.textbook-content {
width: 26rem;
max-width: 90%;
height: 35rem;
margin-bottom: 1.5rem;
font-size: 1.5rem;
overflow: hidden;
text-align: justify;
Expand All @@ -26,11 +29,11 @@
}
.pagenum-left {
left: 2rem;
bottom: 1.5rem;
bottom: 1rem;
}
.pagenum-right {
right: 2rem;
bottom: 1.5rem;
bottom: 1rem;
}

.playbar-container {
Expand All @@ -46,11 +49,7 @@ button.icon-button {
background: none;
margin-right: 24px;
}
.play-icon {
width: 38px;
height: 45px;
}
.pause-icon {
.play-icon, .pause-icon, .stop-icon {
width: 38px;
height: 45px;
}
Expand Down
150 changes: 103 additions & 47 deletions src/pages/Audiobook_Player.js
Original file line number Diff line number Diff line change
@@ -1,68 +1,105 @@
import "./Audiobook_Player.css";
import play from "../icons/play.svg";
import pause from "../icons/pause.svg";
import stop from "../icons/stop.svg";
import playIcon from "../icons/play.svg";
import pauseIcon from "../icons/pause.svg";
import stopIcon from "../icons/stop.svg";
import { useEffect, useState, useRef } from "react";
import { useLocation } from "react-router-dom";
const SERVER_URL = process.env.SERVER_URL;

export default function P_Audiobook_Player() {
const [bookInfo, setBookInfo] = useState();
const [textLeft, setTextLeft] = useState("");
const [textRight, setTextRight] = useState("");
const [audioSrc, setAudioSrc] = useState(null);
const [isPlaying, setIsPlaying] = useState(false);
const audioRef = useRef(null);
const location = useLocation();

const queryParams = new URLSearchParams(location.search);
const bookId = queryParams.get("bookId");
const userId = queryParams.get("userId");

// 프론트엔드 단독 환경(로컬)에서 테스트하는 코드
// 책 데이터 불러오기
useEffect(() => {
fetch(`${SERVER_URL}/api/book/detail?bookId=${bookId}`)
.then(async (res) => {
const json = await res.json();
return json;
})
.then((data) => {
const bookData = {
id: data.id,
title: data.title,
image: `data:image/jpeg;base64,${data.image}`,
author: data.author,
press: data.press,
runningTime: data.runningTime,
intro: data.intro,
};
setBookInfo(bookData);
})
.catch((error) => console.error("Error: ", error));
}, [bookId]);
const fetchBookDetails = async () => {
try {
const response = await fetch("http://localhost:3001/detailInfos");
const data = await response.json();
const book = data.find((item) => item.id === 1);
setBookInfo(book); // bookInfo state에 데이터 저장
console.log(data, book);
} catch (error) {
console.error("Error fetching book details:", error);
}
};
fetchBookDetails();
}, []); // 컴포넌트가 마운트될 때 한 번만 실행

useEffect(() => {
fetch(`${SERVER_URL}/api/audio?userId=${userId}&bookId=${bookId}`)
.then((res) => res.blob())
.then((blob) => {
const url = URL.createObjectURL(blob);
setAudioSrc(url);
})
.catch((error) => console.error("Error: ", error));
}, [userId, bookId]);

const handlePlay = () => {
if (audioRef.current) {
audioRef.current.play();
if (isPlaying) {
audioRef.current.play().catch((error) => console.error("Play error: ", error));
} else {
audioRef.current.pause();
}
}
};
}, [isPlaying]);

const handlePause = () => {
if (audioRef.current) {
audioRef.current.pause();
}
// 서버 연동 시 실행 코드
// useEffect(() => {
// fetch(`${SERVER_URL}/api/book/detail?bookId=${bookId}`)
// .then(async (res) => {
// const json = await res.json();
// return json;
// })
// .then((data) => {
// const bookData = {
// id: data.id,
// title: data.title,
// image: `data:image/jpeg;base64,${data.image}`,
// author: data.author,
// press: data.press,
// runningTime: data.runningTime,
// intro: data.intro,
// };
// setBookInfo(bookData);
// })
// .catch((error) => console.error("Error: ", error));
// }, [bookId]);

// useEffect(() => {
// fetch(`${SERVER_URL}/api/audio?userId=${userId}&bookId=${bookId}`)
// .then((res) => res.blob())
// .then((blob) => {
// const url = URL.createObjectURL(blob);
// setAudioSrc(url);
// })
// .catch((error) => console.error("Error: ", error));
// }, [userId, bookId]);

const togglePlayPause = () => {
// useEffect 없을 때. 실제 구동 환경.
// if (audioRef.current) {
// if (isPlaying) {
// audioRef.current.pause();
// } else {
// audioRef.current.play();
// }
// setIsPlaying(!isPlaying);
// } else {
// setIsPlaying(!isPlaying); // 오디오 파일 없을 때 테스트.
// }

// useEffect 있을 때. 프론트엔드 단독 테스트 환경.
setIsPlaying((prev) => !prev);
};

const handleStop = () => {
if (audioRef.current) {
audioRef.current.pause();
audioRef.current.currentTime = 0;
setIsPlaying(false);
}
};

Expand All @@ -72,29 +109,45 @@ export default function P_Audiobook_Player() {
}
};

// const handlePlay = () => {
// if (audioRef.current) {
// audioRef.current.play();
// }
// };

// const handlePause = () => {
// if (audioRef.current) {
// audioRef.current.pause();
// }
// };

// const handleStop = () => {
// if (audioRef.current) {
// audioRef.current.pause();
// audioRef.current.currentTime = 0;
// }
// };

return (
<div className="audiobook-player-container">
<div className="textbook-container">
<div className="textbook-frame">
<div className="textbook-content loading-text">
<div className="textbook-content text-content-left loading-text">
{bookInfo ? bookInfo.intro : "Loading..."}
</div>
<span className="textbook-pagenum pagenum-left">1</span>
</div>
<div className="textbook-frame">
<div className="textbook-content"></div>
<div className="textbook-content text-content-right"></div>
<span className="textbook-pagenum pagenum-right">2</span>
</div>
</div>
<div className="playbar-container">
<button className="icon-button" onClick={handlePlay}>
<img src={play} alt="play" className="play-icon" />
</button>
<button className="icon-button" onClick={handlePause}>
<img src={pause} alt="pause" className="pause-icon" />
<button className="icon-button" onClick={togglePlayPause}>
<img src={isPlaying ? pauseIcon : playIcon} alt="play/pause" className="play-icon" />
</button>
<button className="icon-button" onClick={handleStop}>
<img src={stop} alt="pause" className="pause-icon" />
<img src={stopIcon} alt="stop" className="stop-icon" />
</button>
<input
type="range"
Expand All @@ -105,7 +158,10 @@ export default function P_Audiobook_Player() {
className="volume-bar"
/>
{/* <div className="progressbar-background"></div> */}
{audioSrc && <audio ref={audioRef} src={audioSrc} controls />}
{/* 서버 연동 시 코드 */}
{/* {audioSrc && <audio ref={audioRef} src={audioSrc} controls />} */}
{/* 서버 연동 X 테스트 코드 */}
{<audio ref={audioRef} src={audioSrc} controls style={{ display: "none" }} />}
</div>
</div>
);
Expand Down
1 change: 1 addition & 0 deletions src/pages/Book_Detail.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export default function P_Book_Detail() {
};

const onClickProduction = async () => {
navigate(`/production-progress`);
try {
const response = await fetch(AUDIOBOOK_PRODUCTION_SERVER_URL, {
method: "POST",
Expand Down
32 changes: 32 additions & 0 deletions src/pages/Production_Progress.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
.production-progress-container {
width: 100%;
height: 100%;
}
.status-icon-container {
height: 30rem;
}
.status-icon {
width: 10rem;
height: 10rem;
animation: rotation 1.5s linear infinite;
}
@keyframes rotation {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}

.loading-icon {
width: 10rem;
fill: #756AB6;
}
status-icon-container > .icon-size {
width: 10rem !important;

}
.message-container {
height: calc(100% - 30rem);
}
Loading

0 comments on commit 23124df

Please sign in to comment.