Skip to content

Commit

Permalink
Merge pull request #56 from PortalCube/dev
Browse files Browse the repository at this point in the history
[Feat/FE] 동영상 업로드 페이지의 API를 새로운 버전으로 연결한다.
  • Loading branch information
PortalCube authored Oct 5, 2023
2 parents e71cc66 + 4e8b289 commit ed79d29
Show file tree
Hide file tree
Showing 13 changed files with 358 additions and 76 deletions.
29 changes: 15 additions & 14 deletions src/components/SkeletonVideo.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const Loading = styled.div`
flex-direction: column;
align-items: center;
justify-content: center;
color: white;
background-color: rgba(0, 0, 0, 0.5);
&.disable {
Expand All @@ -39,13 +40,13 @@ const Loading = styled.div`

const Text = styled.p`
margin-top: 12px;
font-size: 12px;
font-size: 16px;
text-align: center;
`;

const Icon = styled(ImSpinner2)`
width: 48px;
height: 48px;
width: 72px;
height: 72px;
animation: spin 5s infinite linear;
@keyframes spin {
Expand Down Expand Up @@ -124,6 +125,17 @@ function drawPoints(context, data, ratio) {
});
}

function getContainedSize(video) {
const ratio = video.videoWidth / video.videoHeight;
let width = video.clientHeight * ratio;
let height = video.clientHeight;
if (width > video.clientWidth) {
width = video.clientWidth;
height = video.clientWidth / ratio;
}
return [width, height];
}

const SkeletonVideo = ({ src, skeleton, onLoad, ...props }) => {
const containerRef = useRef(null);
const canvasRef = useRef(null);
Expand Down Expand Up @@ -197,17 +209,6 @@ const SkeletonVideo = ({ src, skeleton, onLoad, ...props }) => {
drawPoints(context, data[currentFrame], ratio);
}

function getContainedSize(video) {
const ratio = video.videoWidth / video.videoHeight;
let width = video.clientHeight * ratio;
let height = video.clientHeight;
if (width > video.clientWidth) {
width = video.clientWidth;
height = video.clientWidth / ratio;
}
return [width, height];
}

function getWidthAndHeight(event) {
onLoad(event);
setAspectRatio(event.target.videoWidth / event.target.videoHeight);
Expand Down
100 changes: 100 additions & 0 deletions src/components/TagSelect.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { useContext, useState } from "react";
import styled from "styled-components";
import PropTypes from "prop-types";
import { CATEGORY, POSITION } from "../librarys/type";
import { ReducerContext } from "../librarys/context.js";

const Container = styled.div`
display: flex;
flex-direction: column;
justify-content: center;
gap: 16px;
`;

const FilterSection = styled.div`
height: 36px;
display: flex;
align-items: center;
gap: 12px;
`;

const Header = styled.div`
width: 120px;
display: flex;
align-items: center;
justify-content: space-between;
font-size: 20px;
font-weight: 700;
color: #5f5f5f;
&::after {
content: "";
width: 2px;
height: 20px;
background-color: #5f5f5f;
}
`;

const Button = styled.button`
width: 100px;
height: 100%;
background-color: ${(props) => (props.selected ? "#6968CC" : "#1E1E1E")};
color: #f2f2f2;
border-radius: 10px;
font-size: 16px;
border: none;
cursor: pointer;
&:focus {
outline: none;
}
`;

const TagSelect = () => {
const [state, dispatch] = useContext(ReducerContext);
const { category, pose } = state;

function onClick(type, payload) {
dispatch({
type,
payload,
});
}

return (
<Container>
<FilterSection>
<Header>카테고리</Header>
{CATEGORY.map(({ key, value }) => (
<Button
key={key}
selected={category === key}
onClick={() => onClick("category", key)}
>
{value}
</Button>
))}
</FilterSection>

<FilterSection>
<Header>자세</Header>
{POSITION.map(({ key, value }) => (
<Button
key={key}
selected={pose === key}
onClick={() => onClick("pose", key)}
>
{value} 자세
</Button>
))}
</FilterSection>
</Container>
);
};

TagSelect.propTypes = {
onChange: PropTypes.func,
};

export default TagSelect;
100 changes: 68 additions & 32 deletions src/components/VideoUploader.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import styled from 'styled-components';
import PropTypes from 'prop-types';
import { useState } from 'react';
import styled from "styled-components";
import PropTypes from "prop-types";
import { useContext, useMemo, useRef, useState } from "react";
import SkeletonVideo from "./SkeletonVideo";
import { getSkeletons } from "../librarys/skeleton-api";
import classNames from "classnames";
import { ReducerContext } from "../librarys/context.js";

const VideoUploadContainer = styled.div`
width: 540px;
Expand All @@ -11,12 +15,12 @@ const VideoUploadContainer = styled.div`
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
cursor: pointer;
position: relative;
overflow: hidden;
overflow: hidden;
&:hover {
opacity: 0.8;
&.disable {
cursor: default;
}
`;

Expand All @@ -28,39 +32,75 @@ const UploadText = styled.span`
`;

const HiddenInput = styled.input`
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
opacity: 0;
cursor: pointer;
display: none;
`;

const VideoPreview = styled.video`
const VideoPreview = styled(SkeletonVideo)`
width: 100%;
height: 100%;
border-radius: 10px;
`;

const VideoUploader = ({ onUpload }) => {
const [videoPreview, setVideoPreview] = useState(null);
const VideoUploader = () => {
const input = useRef(null);
const [state, dispatch] = useContext(ReducerContext);
const { skeleton } = state;
const url = useMemo(
() => (state.video ? URL.createObjectURL(state.video) : null),
[state.video],
);

const handleFileChange = (event) => {
const handleVideoChange = async (event) => {
const file = event.target.files[0];
if (file) {
// 파일 객체에서 URL을 생성하여 미리보기에 사용
const videoURL = URL.createObjectURL(file);
setVideoPreview(videoURL);
onUpload && onUpload(file);

if (!file) {
return;
}

// 클라이언트 단에서 영상 띄우기
dispatch({
type: "video",
payload: file,
});

// AI에게 영상 binary를 전송
const formData = new FormData();
formData.append("video_file", file);
const skeleton = await getSkeletons(formData);

// 스켈레톤 받아오면 등록
dispatch({
type: "skeleton",
payload: skeleton,
});
};

function handleMetadata(event) {
dispatch({
type: "duration",
payload: Number(event.target.duration),
});
}

function onClick() {
if (input && url === null) {
input.current.click();
}
}

return (
<VideoUploadContainer>
<HiddenInput type="file" accept="video/*" onChange={handleFileChange} />
{videoPreview ? (
<VideoPreview controls src={videoPreview} />
<VideoUploadContainer
onClick={onClick}
className={classNames({ disable: url !== null })}
>
<HiddenInput
ref={input}
type="file"
accept="video/*"
onChange={handleVideoChange}
/>
{url ? (
<VideoPreview src={url} skeleton={skeleton} onLoad={handleMetadata} />
) : (
<UploadText>
여기를 클릭해서 <br /> 동영상 업로드
Expand All @@ -70,8 +110,4 @@ const VideoUploader = ({ onUpload }) => {
);
};

VideoUploader.propTypes = {
onUpload: PropTypes.func,
};

export default VideoUploader;
export default VideoUploader;
2 changes: 1 addition & 1 deletion src/components/player/BorderBox.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useState, useEffect, useRef, useContext } from "react";
import styled from "styled-components";
import { StateContext } from "../../librarys/context";
import { StateContext } from "../../librarys/context.jsx";

const Container = styled.div`
width: 100%;
Expand Down
2 changes: 1 addition & 1 deletion src/components/player/ControllerSection.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { MdClose, MdPlayArrow, MdRefresh } from "react-icons/md";
import classNames from "classnames";

import styled from "styled-components";
import { DispatchContext, StateContext } from "../../librarys/context";
import { DispatchContext, StateContext } from "../../librarys/context.jsx";

import Player from "../../librarys/player.js";
import { useNavigate } from "react-router-dom";
Expand Down
2 changes: 1 addition & 1 deletion src/components/player/Countdown.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useState, useEffect, useRef, useContext } from "react";
import styled from "styled-components";
import PropTypes from "prop-types";
import { StateContext } from "../../librarys/context";
import { StateContext } from "../../librarys/context.jsx";

const Container = styled.div`
max-width: 700px;
Expand Down
2 changes: 1 addition & 1 deletion src/components/player/GuideSection.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useState, useEffect, useRef, useContext } from "react";
import styled from "styled-components";

import Player from "../../librarys/player";
import { DispatchContext, StateContext } from "../../librarys/context";
import { DispatchContext, StateContext } from "../../librarys/context.jsx";

const Container = styled.div`
max-width: 20%;
Expand Down
40 changes: 39 additions & 1 deletion src/librarys/axios.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export default axios.create({
timeout: 10000,
});

export function getSpringAxios(token) {
export function getSpringAxios(token = null) {
const options = {
baseURL: "http://motus.website/",
timeout: 10000,
Expand All @@ -26,3 +26,41 @@ export function getAIAxios() {
timeout: 1000 * 60 * 60 * 24,
});
}

export async function createVideo(options) {
const axios = getSpringAxios();

const data = new FormData();
data.append("title", options.title);
data.append("description", options.description);
data.append("category", options.category);
data.append("position", options.pose);
data.append("frame", options.totalFrame);
data.append("playTime", options.duration);
data.append("files[0]", options.video);
data.append("files[1]", options.skeleton);

const response = await axios.post("/video/create", data);
return response.data;
}

export async function removeVideo(id) {
const axios = getSpringAxios();

const response = await axios.delete("/video/delete/" + id);
return response.data;
}

export async function listVideo() {
const axios = getSpringAxios();

const response = await axios.get("/video/list");
return response.data;
}

export async function getVideo(id) {
const axios = getSpringAxios();

const response = await axios.get("/video/" + id);
return response.data;
}
3 changes: 3 additions & 0 deletions src/librarys/context.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { createContext } from "react";

export const ReducerContext = createContext();
Loading

0 comments on commit ed79d29

Please sign in to comment.