From 6bc6f1542f258f17a35bd41955e6c6f18bc57c2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=80=E1=85=AF=E1=86=AB=E1=84=8B=E1=85=A7=E1=86=BC?= =?UTF-8?q?=E1=84=8B=E1=85=A5=E1=86=AB?= Date: Mon, 30 Nov 2020 01:26:31 +0900 Subject: [PATCH 01/34] =?UTF-8?q?[FE]:=20[CHORE]=20=ED=8C=8C=EB=B9=84?= =?UTF-8?q?=EC=BD=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/index.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/src/index.html b/client/src/index.html index b1bba6e..9acd27b 100644 --- a/client/src/index.html +++ b/client/src/index.html @@ -4,7 +4,8 @@ - react-app + + WAVE From ba58a5c79569b124fc023fd2fd4d05fd5cc33dfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=80=E1=85=AF=E1=86=AB=E1=84=8B=E1=85=A7=E1=86=BC?= =?UTF-8?q?=E1=84=8B=E1=85=A5=E1=86=AB?= Date: Mon, 30 Nov 2020 01:35:08 +0900 Subject: [PATCH 02/34] =?UTF-8?q?[FE]:=20[STYLE]=20#8=20=EB=93=9C=EB=A1=AD?= =?UTF-8?q?=EB=8B=A4=EC=9A=B4=20=EC=95=A0=EB=8B=88=EB=A9=94=EC=9D=B4?= =?UTF-8?q?=EC=85=98=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/atoms/FileInput/FileInput.tsx | 41 ++++++++++++++++--- 1 file changed, 35 insertions(+), 6 deletions(-) diff --git a/client/src/components/atoms/FileInput/FileInput.tsx b/client/src/components/atoms/FileInput/FileInput.tsx index 64cae41..ea07307 100644 --- a/client/src/components/atoms/FileInput/FileInput.tsx +++ b/client/src/components/atoms/FileInput/FileInput.tsx @@ -1,24 +1,52 @@ import React from 'react'; -import styled from 'styled-components'; +import styled, { keyframes } from 'styled-components'; import color from '@/theme/colors'; +const slide = keyframes` + from { + transform: translate(0, -50px) rotate(90deg); + opacity: 0; + } + to { + transform: translate(0, 0) rotate(0deg); + opacity: 1; + } +`; + const StyledDiv = styled.div` position: absolute; display: flex; + flex-direction: column; justify-content: center; border-radius: 5px; - padding: 5px 16px; top: 2rem; right: 0; - background-color: ${color.GRAY}; - box-shadow: 1px 1px 2px 1px ${color.WHITE}; + border: 1px solid ${color.BORDER}; + background-color: ${color.BLACK}; + box-shadow: 1px 1px 2px 1px ${color.BORDER}; + animation: ${slide} 0.4s -0.1s ease-out; + transform-origin: center center; + width: 5rem; `; -const StyledLabel = styled.label` +const FromLocal = styled.label` color: ${color.WHITE}; font-size: 12px; + text-align: center; cursor: pointer; + padding: 5px 16px; + transition: 0.7s; + border-radius: 5px 5px 0 0; + + &:hover { + background-color: ${color.GRAY}; + } +`; + +const FromServer = styled(FromLocal)` + border-top: 1px solid ${color.BORDER}; + border-radius: 0 0 5px 5px; `; const StyledInput = styled.input` @@ -33,13 +61,14 @@ const FileInput = React.forwardRef( ({ handleChange }, forwardedRef) => { return ( - 내 컴퓨터 + 로컬 + 서버 ); } From 52f585c6af3ecc4096f8ae9f5d7689ecd15111fa Mon Sep 17 00:00:00 2001 From: SSH1997 Date: Mon, 30 Nov 2020 18:00:37 +0900 Subject: [PATCH 03/34] =?UTF-8?q?[FE]=20:=20[FEAT]=20#20=20Tool=20componen?= =?UTF-8?q?t=20=EB=B2=84=ED=8A=BC=EA=B7=B8=EB=A3=B9=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 기능별 버튼 그룹 정리 - 차후에 logic은 파일로 빼서 관리해야할 필요가 있음 --- .../src/components/organisms/Tools/Tools.tsx | 187 +++++++++++++----- 1 file changed, 143 insertions(+), 44 deletions(-) diff --git a/client/src/components/organisms/Tools/Tools.tsx b/client/src/components/organisms/Tools/Tools.tsx index 7cf6b8e..2d7afad 100644 --- a/client/src/components/organisms/Tools/Tools.tsx +++ b/client/src/components/organisms/Tools/Tools.tsx @@ -6,8 +6,15 @@ import { BsFillSkipEndFill, BsFillPlayFill, BsFillPauseFill, + BsAspectRatio, + BsTerminal, + BsCheck, + BsX, } from 'react-icons/bs'; import { RiScissorsLine } from 'react-icons/ri'; +import { AiOutlineRotateLeft,AiOutlineFullscreenExit,AiOutlineFullscreen} from 'react-icons/ai' +import { MdRotateLeft, MdRotateRight } from 'react-icons/md'; +import { CgMergeHorizontal, CgMergeVertical} from 'react-icons/cg'; import webglController from '@/webgl/webglController'; import ButtonGroup from '@/components/molecules/ButtonGroup'; import UploadArea from '@/components/molecules/UploadArea'; @@ -34,6 +41,13 @@ interface button { children: React.ReactChild; } +interface ButtonData { + onClicks: (() => void)[]; + messages: string[]; + type: string; + childrens: any[] +} + const getVideoToolsData = ( backwardVideo: () => void, playPauseVideo: () => void, @@ -64,55 +78,60 @@ const getVideoToolsData = ( }, ]; -const getEditToolsData = ( - rotateLeft90Degree: () => void, - rotateRight90Degree: () => void, - reverseUpsideDown: () => void, - reverseSideToSide: () => void, - enlarge: () => void, - reduce: () => void, - crop: () => void +const getEditToolData = ( + rotateReverse: () => void, + ratio: () => void, + crop: () => void, ): button[] => [ { - onClick: rotateLeft90Degree, - message: "Left 90'", - type: 'transparent', - children: null, - }, - { - onClick: rotateRight90Degree, - message: "Right 90'", + onClick: rotateReverse, + message: '회전 / 반전', type: 'transparent', - children: null, + children: , }, { - onClick: reverseUpsideDown, - message: 'Up to Down', + onClick: ratio, + message: '비율', type: 'transparent', - children: null, + children: , }, - { - onClick: reverseSideToSide, - message: 'Side to Side', - type: 'transparent', - children: null, - }, - { onClick: enlarge, message: 'enlarge', type: 'transparent', children: null }, - { onClick: reduce, message: 'reduce', type: 'transparent', children: null }, { onClick: crop, - message: 'crop', + message: '자르기', type: 'transparent', children: , }, ]; -const EditTool = styled(ButtonGroup)``; -const VideoTool = styled(ButtonGroup)``; +const getSubEditToolsData = (buttonData: ButtonData): button[] => { + const buttons = []; + + if (!buttonData.onClicks) { + return []; + } + + for (let i = 0; i < buttonData.onClicks.length; i += 1) { + buttons.push({ + onClick: buttonData.onClicks[i], + message: buttonData.messages[i], + type: buttonData.type, + children: buttonData.childrens[i], + }) + } + + return buttons; +}; const Tools: React.FC = () => { const [play, setPlay] = useState(true); // Fix 스토어로 등록 const dispatch = useDispatch(); + const [toolType, setToolType] = useState(null); + const [buttonData, setButtonData] = useState({ + onClicks: null, + messages: null, + type: null, + childrens: null, + }); const { start, end } = useSelector(getStartEnd, shallowEqual); @@ -164,10 +183,93 @@ const Tools: React.FC = () => { const rotateRight90Degree = () => webglController.rotateRight90Degree(); const reverseUpsideDown = () => webglController.reverseUpsideDown(); const reverseSideToSide = () => webglController.reverseSideToSide(); - const enlarge = () => webglController.enlarge(); - const reduce = () => webglController.reduce(); + const rotateReverseMethods = [rotateLeft90Degree, rotateRight90Degree, reverseUpsideDown, reverseSideToSide]; + const rotateReverseMessages= ['왼쪽', '오른쪽', '상하 반전', '좌우 반전']; + const rorateReverseChildrens = [ + , + , + , + + ] + + const rotateReverse = () => { + if (toolType === 'videoEffect') { + setToolType(null); + setButtonData({ + onClicks: null, + messages: null, + type: null, + childrens: null, + }); + } else { + setToolType('videoEffect'); + setButtonData({ + onClicks: rotateReverseMethods, + messages: rotateReverseMessages, + type: 'transparent', + childrens : rorateReverseChildrens, + }) + } + } + + const ratioEnlarge = () => webglController.enlarge(); + const ratioReduce = () => webglController.reduce(); + const ratioMethods = [ratioEnlarge, ratioReduce]; + const ratioMessages= ['확대', '축소']; + const ratioChildrens = [ + , + , + ] + + const ratio = () => { + if (toolType === 'ratio') { + setToolType(null); + setButtonData({ + onClicks: null, + messages: null, + type: null, + childrens: null, + }); + } else { + setToolType('ratio'); + setButtonData({ + onClicks: ratioMethods, + messages: ratioMessages, + type: 'transparent', + childrens : ratioChildrens, + }) + } + } + + const cropInsert = () => webglController.enlarge(); + const cropConfirm = () => webglController.reduce(); + const cropCancle = () => webglController.reduce(); + const cropMethods = [cropInsert, cropConfirm, cropCancle]; + const cropMessages= ['직접입력', '확인', '취소']; + const cropChildrens = [ + , + , + , + ] + const crop = () => { - // crop + if (toolType === 'crop') { + setToolType(null); + setButtonData({ + onClicks: null, + messages: null, + type: null, + childrens: null, + }); + } else { + setToolType('crop'); + setButtonData({ + onClicks: cropMethods, + messages: cropMessages, + type: 'transparent', + childrens : cropChildrens, + }) + } }; return ( @@ -180,17 +282,14 @@ const Tools: React.FC = () => { play )} /> - + + + )}/> + ); From c8db18a657b021cc45db938046cb39c077951716 Mon Sep 17 00:00:00 2001 From: SSH1997 Date: Mon, 30 Nov 2020 18:01:29 +0900 Subject: [PATCH 04/34] =?UTF-8?q?[FE]=20:=20[STYLE]=20#20=20Tools=20button?= =?UTF-8?q?=20Group=20=EC=8A=A4=ED=83=80=EC=9D=BC=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 기존에 일렬로 보였던 배치에서 누르면 edit tool 위쪽에 보이게 배치를 바꿈 --- client/src/components/organisms/Tools/Tools.tsx | 17 ++++++++++++++++- .../organisms/VideoContainer/VideoContainer.tsx | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/client/src/components/organisms/Tools/Tools.tsx b/client/src/components/organisms/Tools/Tools.tsx index 2d7afad..ca82ff2 100644 --- a/client/src/components/organisms/Tools/Tools.tsx +++ b/client/src/components/organisms/Tools/Tools.tsx @@ -30,9 +30,24 @@ import { getStartEnd } from '@/store/selectors'; const StyledDiv = styled.div` display: flex; justify-content: space-between; - align-items: center; + align-items: flex-end; padding: 1rem; + height: 10rem; +`; + +const StyledEditToolDiv = styled.div` + display: flex; + flex-direction: column; + align-items: center; +`; + +const EditTool = styled(ButtonGroup)` + width: 20rem; +`; +const SubEditTool = styled(ButtonGroup)` + width: 25rem; `; +const VideoTool = styled(ButtonGroup)``; interface button { onClick: () => void; diff --git a/client/src/components/organisms/VideoContainer/VideoContainer.tsx b/client/src/components/organisms/VideoContainer/VideoContainer.tsx index d5415fe..cfd10a4 100644 --- a/client/src/components/organisms/VideoContainer/VideoContainer.tsx +++ b/client/src/components/organisms/VideoContainer/VideoContainer.tsx @@ -8,7 +8,7 @@ const StyledDiv = styled.div` `; const StyledCanvas = styled.canvas` - height: 35rem; + height: 30rem; `; const VideoContainer: React.FC = () => { From 64d9894e0b7065fb533dea770061843077a96d87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=80=E1=85=AF=E1=86=AB=E1=84=8B=E1=85=A7=E1=86=BC?= =?UTF-8?q?=E1=84=8B=E1=85=A5=E1=86=AB?= Date: Mon, 30 Nov 2020 19:55:34 +0900 Subject: [PATCH 05/34] =?UTF-8?q?[COMMON]:=20[CHORE]=20https=20=EB=B0=B0?= =?UTF-8?q?=ED=8F=AC=20=EB=B0=8F=20=ED=86=A0=ED=81=B0=20=EA=B0=92=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/index.html | 1 + 1 file changed, 1 insertion(+) diff --git a/client/src/index.html b/client/src/index.html index b1bba6e..ad06d01 100644 --- a/client/src/index.html +++ b/client/src/index.html @@ -4,6 +4,7 @@ + react-app From 9617a17de0e8a5d6795df4e117740ff7df322072 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=80=E1=85=AF=E1=86=AB=E1=84=8B=E1=85=A7=E1=86=BC?= =?UTF-8?q?=E1=84=8B=E1=85=A5=E1=86=AB?= Date: Mon, 30 Nov 2020 19:58:04 +0900 Subject: [PATCH 06/34] =?UTF-8?q?[FE]:=20[FIX]=20#10=20=EC=B7=A8=EC=86=8C?= =?UTF-8?q?=20=EB=B2=84=ED=8A=BC=EC=9D=84=20=EB=88=84=EB=A5=B4=EB=A9=B4=20?= =?UTF-8?q?canvas=EA=B0=80=20=EC=B4=88=EA=B8=B0=ED=99=94=EB=90=98=EC=A7=80?= =?UTF-8?q?=20=EC=95=8A=EB=8D=98=20=EB=AC=B8=EC=A0=9C=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - webglController에 claer메서드 추가 --- client/src/store/sagas.ts | 2 ++ client/src/webgl/webglController.ts | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/client/src/store/sagas.ts b/client/src/store/sagas.ts index 09d4577..264fea2 100644 --- a/client/src/store/sagas.ts +++ b/client/src/store/sagas.ts @@ -1,10 +1,12 @@ import { all, call, takeEvery } from 'redux-saga/effects'; import video from '@/video'; import watchSetVideo from '@/store/originalVideo/sagas'; +import webglController from '@/webgl/webglController'; import { RESET } from './actionTypes'; function* deleteSrc() { yield call(video.revoke); + yield call(webglController.clear); } function* watchReset() { diff --git a/client/src/webgl/webglController.ts b/client/src/webgl/webglController.ts index 78661eb..e26e84a 100644 --- a/client/src/webgl/webglController.ts +++ b/client/src/webgl/webglController.ts @@ -397,5 +397,9 @@ class WebglController { main = () => { this.glInit(); }; + + clear = () => { + this.gl.clear(this.gl.COLOR_BUFFER_BIT); + }; } export default new WebglController(); From be98d74b8287284e81a154c25b17ff2457a2cd40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=80=E1=85=AF=E1=86=AB=E1=84=8B=E1=85=A7=E1=86=BC?= =?UTF-8?q?=E1=84=8B=E1=85=A5=E1=86=AB?= Date: Mon, 30 Nov 2020 19:59:11 +0900 Subject: [PATCH 07/34] =?UTF-8?q?[FE]:=20[STYLE]=20#21=20=EC=B4=88?= =?UTF-8?q?=EA=B8=B0=20canvas=20=EB=B0=B0=EA=B2=BD=EC=9D=84=20=EA=B2=80?= =?UTF-8?q?=EC=9D=80=EC=83=89=EC=9C=BC=EB=A1=9C=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/organisms/VideoContainer/VideoContainer.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/client/src/components/organisms/VideoContainer/VideoContainer.tsx b/client/src/components/organisms/VideoContainer/VideoContainer.tsx index d5415fe..834799c 100644 --- a/client/src/components/organisms/VideoContainer/VideoContainer.tsx +++ b/client/src/components/organisms/VideoContainer/VideoContainer.tsx @@ -1,6 +1,8 @@ import React from 'react'; import styled from 'styled-components'; +import color from '@/theme/colors'; + const StyledDiv = styled.div` display: flex; justify-content: center; @@ -9,6 +11,7 @@ const StyledDiv = styled.div` const StyledCanvas = styled.canvas` height: 35rem; + background-color: ${color.BLACK}; `; const VideoContainer: React.FC = () => { From 8970ae11d7dc34a759366afffe2b677284f5d577 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=80=E1=85=AF=E1=86=AB=E1=84=8B=E1=85=A7=E1=86=BC?= =?UTF-8?q?=E1=84=8B=E1=85=A5=E1=86=AB?= Date: Mon, 30 Nov 2020 20:00:22 +0900 Subject: [PATCH 08/34] =?UTF-8?q?[FE]:=20[FIX]=20#24=20=EC=8D=B8=EB=84=A4?= =?UTF-8?q?=EC=9D=BC=20=EB=A1=9C=EB=94=A9=20=ED=9B=84=200=EC=B4=88?= =?UTF-8?q?=EB=A1=9C=20video=EB=A5=BC=20=EC=9D=B4=EB=8F=99=ED=95=98?= =?UTF-8?q?=EB=A9=B0=20=EC=83=9D=EA=B8=B0=EB=8A=94=20delay=20=ED=95=B4?= =?UTF-8?q?=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/video/video.tsx | 31 ++++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/client/src/video/video.tsx b/client/src/video/video.tsx index 201e2c7..20d0c27 100644 --- a/client/src/video/video.tsx +++ b/client/src/video/video.tsx @@ -1,12 +1,21 @@ class Video { private video: HTMLVideoElement; - private THUMNAIL_COUNT: number = 30; - private canvas: HTMLCanvasElement; + private THUMBNAIL_COUNT: number = 30; + private thumbnails: string[]; + private getAllowedFields: Set = new Set([ + 'paused', + 'duration', + 'videoWidth', + 'videoHeight', + 'src', + 'currentTime', + ]); + constructor() { this.canvas = document.createElement('canvas'); this.video = document.createElement('video'); @@ -18,6 +27,11 @@ class Video { return this.video.paused; }; + get = field => { + if (this.getAllowedFields.has(field)) return this.video[field]; + return undefined; + }; + getVideo = () => { return this.video; }; @@ -59,19 +73,18 @@ class Video { return new Promise((resolve, reject) => { try { (async () => { - const gap = (end - start) / (this.THUMNAIL_COUNT - 1); - let secs = start; + const gap = (end - start) / (this.THUMBNAIL_COUNT - 1); + let secs = end; - const images: string[] = []; + const images: string[] = new Array(this.THUMBNAIL_COUNT); - for (let count = 0; count < this.THUMNAIL_COUNT; count += 1) { + for (let count = this.THUMBNAIL_COUNT - 1; count >= 0; count -= 1) { this.setCurrentTime(secs); const image: string = await this.getImageAt(); - secs += gap; - images.push(image); + secs -= gap; + images[count] = image; } - this.setCurrentTime(0); this.thumbnails = images; resolve(images); })(); From 80f724cd4dc8bc92e9d3de6320ee72fe7a6e2069 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=80=E1=85=AF=E1=86=AB=E1=84=8B=E1=85=A7=E1=86=BC?= =?UTF-8?q?=E1=84=8B=E1=85=A5=E1=86=AB?= Date: Mon, 30 Nov 2020 21:35:02 +0900 Subject: [PATCH 09/34] =?UTF-8?q?[FE]:=20[CHORE]=20tsconfig=20lib=20versio?= =?UTF-8?q?n=20=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Array.flat() 을 쓰기 위한 여정.. --- tsconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsconfig.json b/tsconfig.json index 199fa1d..242701f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,7 +3,7 @@ "lib": [ "es5", "es6", - "es2015", + "es2019", "DOM" ], "jsx": "react", From 314cd79e07564126ad1e6f1f40cf75db8f980ad6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=80=E1=85=AF=E1=86=AB=E1=84=8B=E1=85=A7=E1=86=BC?= =?UTF-8?q?=E1=84=8B=E1=85=A5=E1=86=AB?= Date: Mon, 30 Nov 2020 21:36:56 +0900 Subject: [PATCH 10/34] =?UTF-8?q?[FE]:=20[REFACTOR]=20getter=20=ED=86=B5?= =?UTF-8?q?=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - video.get()에서 video element의 속성에 접근하도록 수정 --- client/src/components/atoms/Slider/Slider.tsx | 4 +-- .../molecules/CurrentTime/CurrentTime.tsx | 2 +- .../molecules/Thumbnail/Thumbnail.tsx | 2 +- .../src/components/organisms/Tools/Tools.tsx | 4 +-- client/src/video/video.tsx | 25 +++---------------- 5 files changed, 9 insertions(+), 28 deletions(-) diff --git a/client/src/components/atoms/Slider/Slider.tsx b/client/src/components/atoms/Slider/Slider.tsx index b2c490d..2d834ef 100644 --- a/client/src/components/atoms/Slider/Slider.tsx +++ b/client/src/components/atoms/Slider/Slider.tsx @@ -40,10 +40,10 @@ const Slider: React.FC = ({ thumbnailRef }) => { const time = useSelector(getCurrentTime); useEffect(() => { - const currentTime = video.getCurrentTime(); + const currentTime = video.get('currentTime'); const width = thumbnailRef.current.clientWidth; - const totalDuration = video.getDuration(); + const totalDuration = video.get('duration'); const movedLocation = totalDuration ? (currentTime / totalDuration) * width diff --git a/client/src/components/molecules/CurrentTime/CurrentTime.tsx b/client/src/components/molecules/CurrentTime/CurrentTime.tsx index 8acf37e..b434d40 100644 --- a/client/src/components/molecules/CurrentTime/CurrentTime.tsx +++ b/client/src/components/molecules/CurrentTime/CurrentTime.tsx @@ -17,7 +17,7 @@ const StyledDiv = styled.div` `; const CurrentTime: React.FC = () => { - const currentTime = () => Math.round(video.getCurrentTime()); + const currentTime = () => Math.round(video.get('currentTime')); const [time, setTime] = useState(currentTime()); const visible = useSelector(getVisible); diff --git a/client/src/components/molecules/Thumbnail/Thumbnail.tsx b/client/src/components/molecules/Thumbnail/Thumbnail.tsx index ca855bf..f5b7633 100644 --- a/client/src/components/molecules/Thumbnail/Thumbnail.tsx +++ b/client/src/components/molecules/Thumbnail/Thumbnail.tsx @@ -47,7 +47,7 @@ const Thumbnail: React.FC = () => { const distance = mouseLocation - offset; const width = thumbnailRef.current.clientWidth; - const duration = video.getDuration(); + const duration = video.get('duration'); const hoverTime = (distance / width) * duration; diff --git a/client/src/components/organisms/Tools/Tools.tsx b/client/src/components/organisms/Tools/Tools.tsx index 7cf6b8e..b579ce7 100644 --- a/client/src/components/organisms/Tools/Tools.tsx +++ b/client/src/components/organisms/Tools/Tools.tsx @@ -117,14 +117,14 @@ const Tools: React.FC = () => { const { start, end } = useSelector(getStartEnd, shallowEqual); const backwardVideo = () => { - const dstTime = Math.max(video.getCurrentTime() - 10, start); + const dstTime = Math.max(video.get('currentTime') - 10, start); video.setCurrentTime(dstTime); dispatch(moveTo(dstTime)); }; const forwardVideo = () => { - const dstTime = Math.min(video.getCurrentTime() + 10, end); + const dstTime = Math.min(video.get('currentTime') + 10, end); video.setCurrentTime(dstTime); dispatch(moveTo(dstTime)); diff --git a/client/src/video/video.tsx b/client/src/video/video.tsx index 20d0c27..43ae77e 100644 --- a/client/src/video/video.tsx +++ b/client/src/video/video.tsx @@ -36,26 +36,6 @@ class Video { return this.video; }; - getDuration = () => { - return this.video.duration; - }; - - getVideoWidth = () => { - return this.video.videoWidth; - }; - - getVideoHeight = () => { - return this.video.videoHeight; - }; - - getSrc = () => { - return this.video.src; - }; - - getCurrentTime = () => { - return this.video.currentTime; - }; - getThumbnails = () => { return [...this.thumbnails]; }; @@ -116,8 +96,9 @@ class Video { }; revoke = () => { - if (this.getSrc()) { - URL.revokeObjectURL(this.getSrc()); + const src = this.get('src'); + if (src) { + URL.revokeObjectURL(src); this.video.removeAttribute('src'); this.load(); } From 30549fb642cada2ec795b09603b8a5266862daa3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=80=E1=85=AF=E1=86=AB=E1=84=8B=E1=85=A7=E1=86=BC?= =?UTF-8?q?=E1=84=8B=E1=85=A5=E1=86=AB?= Date: Mon, 30 Nov 2020 21:37:46 +0900 Subject: [PATCH 11/34] =?UTF-8?q?[FE]:=20[STYLE]=20position=202=EC=B0=A8?= =?UTF-8?q?=EC=9B=90=20=EB=B0=B0=EC=97=B4=EB=A1=9C=20=EB=B3=80=EA=B2=BD=20?= =?UTF-8?q?=EB=B0=8F=20=ED=95=A8=EC=88=98=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/webgl/webglController.ts | 99 +++++++++-------------------- 1 file changed, 29 insertions(+), 70 deletions(-) diff --git a/client/src/webgl/webglController.ts b/client/src/webgl/webglController.ts index e26e84a..4f22be5 100644 --- a/client/src/webgl/webglController.ts +++ b/client/src/webgl/webglController.ts @@ -23,104 +23,63 @@ interface ProgramInfo { }; } -class WebglController { - copyVideo: Boolean; +const RATIO = 1.25; +const INVERSE = 1 / RATIO; - positions: Array; +class WebglController { + positions: number[][]; buffers: Buffers; gl: WebGLRenderingContext; + init = { + positions: [ + [-1.0, -1.0], + [1.0, -1.0], + [1.0, 1.0], + [-1.0, 1.0], + ], + }; + constructor() { - this.copyVideo = false; - this.positions = [-1.0, -1.0, 1.0, -1.0, 1.0, 1.0, -1.0, 1.0]; + this.positions = this.init.positions; } rotateLeft90Degree = () => { - this.positions.push(this.positions.shift()); + // 0123 => 1230 this.positions.push(this.positions.shift()); this.buffers = this.initBuffers(); }; rotateRight90Degree = () => { - this.positions.unshift(this.positions.pop()); + // 0123 => 3012 this.positions.unshift(this.positions.pop()); this.buffers = this.initBuffers(); }; reverseUpsideDown = () => { - const x1 = this.positions.shift(); - const y1 = this.positions.shift(); - - const x2 = this.positions.shift(); - const y2 = this.positions.shift(); - - const x3 = this.positions.shift(); - const y3 = this.positions.shift(); - this.positions.push(x3); - this.positions.push(y3); - - this.positions.push(x2); - this.positions.push(y2); - - this.positions.push(x1); - this.positions.push(y1); - + // 0123 => 3210 + this.positions.reverse(); this.buffers = this.initBuffers(); }; reverseSideToSide = () => { - const x1 = this.positions.shift(); - const y1 = this.positions.shift(); - - const x2 = this.positions.shift(); - const y2 = this.positions.shift(); - - const x3 = this.positions.shift(); - const y3 = this.positions.shift(); - - this.positions.push(x3); - this.positions.push(y3); - - this.positions.unshift(y1); - this.positions.unshift(x1); - - this.positions.unshift(y2); - this.positions.unshift(x2); - + // 0123 => 1032 + this.positions = [ + ...this.positions.slice(0, 2).reverse(), + ...this.positions.slice(-2).reverse(), + ]; this.buffers = this.initBuffers(); }; enlarge = () => { - const temp = []; - - this.positions.forEach(element => { - if (element < 0) { - temp.push(element - 1); - } else { - temp.push(element + 1); - } - }); - - this.positions = temp; - + this.positions = this.positions.map(pair => pair.map(val => val * RATIO)); this.buffers = this.initBuffers(); }; reduce = () => { - const temp = []; - - this.positions.forEach(element => { - if (element < 0) { - temp.push(element + 1); - } else { - temp.push(element - 1); - } - }); - - this.positions = temp; - + this.positions = this.positions.map(pair => pair.map(val => val * INVERSE)); this.buffers = this.initBuffers(); }; @@ -139,7 +98,7 @@ class WebglController { this.gl.bindBuffer(this.gl.ARRAY_BUFFER, positionBuffer); this.gl.bufferData( this.gl.ARRAY_BUFFER, - new Float32Array(this.positions), + new Float32Array(this.positions.flat()), this.gl.STATIC_DRAW ); @@ -353,8 +312,8 @@ class WebglController { glInit = () => { this.gl = this.initCanvas( - video.getVideoWidth().toString(), - video.getVideoHeight().toString() + video.get('videoWidth').toString(), + video.get('videoHeight').toString() ); this.buffers = this.initBuffers(); const shaderProgram = this.initShaderProgram(); @@ -384,7 +343,7 @@ class WebglController { const texture = this.initTexture(); const render = () => { - if (!video.getSrc()) return; + if (!video.get('src')) return; this.updateTexture(texture); this.drawScene(programInfo, texture); From 9f6ddbdf5a48acbf5308ae3835fb786e980accfd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=80=E1=85=AF=E1=86=AB=E1=84=8B=E1=85=A7=E1=86=BC?= =?UTF-8?q?=E1=84=8B=E1=85=A5=E1=86=AB?= Date: Mon, 30 Nov 2020 22:23:45 +0900 Subject: [PATCH 12/34] =?UTF-8?q?[FE]:=20[FEAT]=20webglController=20reset?= =?UTF-8?q?=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 영상이 있는 상태에서 새로운 파일을 불러오는 경우 편집 중인 상태로 썸네일을 찍는 모습이 보이는 문제가 있다. --- .../components/molecules/UploadArea/UploadArea.tsx | 9 ++++----- client/src/store/originalVideo/sagas.ts | 14 ++++++++++---- client/src/store/sagas.ts | 9 +-------- client/src/webgl/webglController.ts | 6 ++++-- 4 files changed, 19 insertions(+), 19 deletions(-) diff --git a/client/src/components/molecules/UploadArea/UploadArea.tsx b/client/src/components/molecules/UploadArea/UploadArea.tsx index ba46c91..1409a9a 100644 --- a/client/src/components/molecules/UploadArea/UploadArea.tsx +++ b/client/src/components/molecules/UploadArea/UploadArea.tsx @@ -8,6 +8,8 @@ import { setVideo } from '@/store/originalVideo/actions'; import { getName } from '@/store/selectors'; import { reset } from '@/store/actionTypes'; +import webglController from '@/webgl/webglController'; + const StyledDiv = styled.div` display: flex; align-items: center; @@ -29,11 +31,8 @@ const UploadArea: React.FC = () => { const handleChange = () => { const localFile: File = ref.current?.files[0]; - - if (localFile) { - const objectURL = URL.createObjectURL(localFile); - dispatch(setVideo(localFile, objectURL)); - } else dispatch(reset()); + const objectURL = URL.createObjectURL(localFile); + dispatch(setVideo(localFile, objectURL)); setVisible(false); }; diff --git a/client/src/store/originalVideo/sagas.ts b/client/src/store/originalVideo/sagas.ts index d277b48..ee70e4d 100644 --- a/client/src/store/originalVideo/sagas.ts +++ b/client/src/store/originalVideo/sagas.ts @@ -1,13 +1,19 @@ -import { put, call, takeLatest } from 'redux-saga/effects'; +import { put, call, takeLatest, select } from 'redux-saga/effects'; import video from '@/video/video'; -import WebglController from '@/webgl/webglController'; +import webglController from '@/webgl/webglController'; import { loadMetadata } from './actions'; import { setThumbnails } from '../currentVideo/actions'; import { SET_VIDEO, error } from '../actionTypes'; +import { getDuration } from '../selectors'; const TIMEOUT = 5_000; +export function* deleteSrc() { + yield call(webglController.reset); + yield call(video.revoke); +} + function waitMetadataLoading(objectURL) { return new Promise((resolve, reject) => { const timer = setTimeout(reject, TIMEOUT, 'loading metadata timeout'); @@ -33,7 +39,7 @@ function* load(action) { const thumbnails: string[] = yield call(video.makeThumbnails, 0, duration); - yield call(WebglController.main); + yield call(webglController.main); yield put(setThumbnails(thumbnails)); } catch (err) { console.log(err); @@ -41,6 +47,6 @@ function* load(action) { } } -export default function* watchSetVideo() { +export function* watchSetVideo() { yield takeLatest(SET_VIDEO, load); } diff --git a/client/src/store/sagas.ts b/client/src/store/sagas.ts index 264fea2..f58be4a 100644 --- a/client/src/store/sagas.ts +++ b/client/src/store/sagas.ts @@ -1,14 +1,7 @@ import { all, call, takeEvery } from 'redux-saga/effects'; -import video from '@/video'; -import watchSetVideo from '@/store/originalVideo/sagas'; -import webglController from '@/webgl/webglController'; +import { watchSetVideo, deleteSrc } from '@/store/originalVideo/sagas'; import { RESET } from './actionTypes'; -function* deleteSrc() { - yield call(video.revoke); - yield call(webglController.clear); -} - function* watchReset() { yield takeEvery(RESET, deleteSrc); } diff --git a/client/src/webgl/webglController.ts b/client/src/webgl/webglController.ts index 4f22be5..00ccd41 100644 --- a/client/src/webgl/webglController.ts +++ b/client/src/webgl/webglController.ts @@ -43,7 +43,7 @@ class WebglController { }; constructor() { - this.positions = this.init.positions; + this.positions = this.init.positions.map(pair => [...pair]); } rotateLeft90Degree = () => { @@ -357,8 +357,10 @@ class WebglController { this.glInit(); }; - clear = () => { + reset = () => { this.gl.clear(this.gl.COLOR_BUFFER_BIT); + this.positions = this.init.positions.map(pair => [...pair]); + this.initBuffers(); }; } export default new WebglController(); From 1d2079bb4705323146815892a987da34e7e125c4 Mon Sep 17 00:00:00 2001 From: SSH1997 Date: Tue, 1 Dec 2020 00:32:04 +0900 Subject: [PATCH 13/34] =?UTF-8?q?[FE]=20:=20[REFACTOR]=20#20=20Button=20Da?= =?UTF-8?q?ta=20=EC=84=A0=ED=83=9D=20logic=20=EB=A6=AC=ED=8C=A9=ED=86=A0?= =?UTF-8?q?=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/organisms/Tools/Tools.tsx | 189 +++++++----------- .../components/organisms/Tools/reducer.tsx | 82 ++++++++ 2 files changed, 151 insertions(+), 120 deletions(-) create mode 100644 client/src/components/organisms/Tools/reducer.tsx diff --git a/client/src/components/organisms/Tools/Tools.tsx b/client/src/components/organisms/Tools/Tools.tsx index ca82ff2..ab6515d 100644 --- a/client/src/components/organisms/Tools/Tools.tsx +++ b/client/src/components/organisms/Tools/Tools.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useState, useReducer } from 'react'; import styled from 'styled-components'; import { shallowEqual, useDispatch, useSelector } from 'react-redux'; import { @@ -7,14 +7,10 @@ import { BsFillPlayFill, BsFillPauseFill, BsAspectRatio, - BsTerminal, - BsCheck, - BsX, } from 'react-icons/bs'; import { RiScissorsLine } from 'react-icons/ri'; -import { AiOutlineRotateLeft,AiOutlineFullscreenExit,AiOutlineFullscreen} from 'react-icons/ai' -import { MdRotateLeft, MdRotateRight } from 'react-icons/md'; -import { CgMergeHorizontal, CgMergeVertical} from 'react-icons/cg'; +import { MdScreenRotation } from 'react-icons/md'; + import webglController from '@/webgl/webglController'; import ButtonGroup from '@/components/molecules/ButtonGroup'; import UploadArea from '@/components/molecules/UploadArea'; @@ -26,6 +22,9 @@ import { moveTo, } from '@/store/currentVideo/actions'; import { getStartEnd } from '@/store/selectors'; +import { cropStart, cropCancel, cropConfirm } from '@/store/actionTypes'; + +import reducer, { initialData, ButtonData, ButtonDataAction } from './reducer'; const StyledDiv = styled.div` display: flex; @@ -56,13 +55,6 @@ interface button { children: React.ReactChild; } -interface ButtonData { - onClicks: (() => void)[]; - messages: string[]; - type: string; - childrens: any[] -} - const getVideoToolsData = ( backwardVideo: () => void, playPauseVideo: () => void, @@ -96,13 +88,13 @@ const getVideoToolsData = ( const getEditToolData = ( rotateReverse: () => void, ratio: () => void, - crop: () => void, + crop: () => void ): button[] => [ { onClick: rotateReverse, message: '회전 / 반전', type: 'transparent', - children: , + children: , }, { onClick: ratio, @@ -118,35 +110,19 @@ const getEditToolData = ( }, ]; -const getSubEditToolsData = (buttonData: ButtonData): button[] => { - const buttons = []; - - if (!buttonData.onClicks) { - return []; - } - - for (let i = 0; i < buttonData.onClicks.length; i += 1) { - buttons.push({ - onClick: buttonData.onClicks[i], - message: buttonData.messages[i], - type: buttonData.type, - children: buttonData.childrens[i], - }) - } - - return buttons; -}; +const getSubEditToolsData = (buttonData: ButtonData): button[] => + [...Array(buttonData.onClicks?.length)].map((_, idx) => ({ + onClick: buttonData.onClicks[idx], + message: buttonData.messages[idx], + type: buttonData.type, + children: buttonData.childrens[idx], + })); const Tools: React.FC = () => { const [play, setPlay] = useState(true); // Fix 스토어로 등록 const dispatch = useDispatch(); const [toolType, setToolType] = useState(null); - const [buttonData, setButtonData] = useState({ - onClicks: null, - messages: null, - type: null, - childrens: null, - }); + const [buttonData, dispatchButtonData] = useReducer(reducer, initialData); const { start, end } = useSelector(getStartEnd, shallowEqual); @@ -194,96 +170,67 @@ const Tools: React.FC = () => { } }; - const rotateLeft90Degree = () => webglController.rotateLeft90Degree(); - const rotateRight90Degree = () => webglController.rotateRight90Degree(); - const reverseUpsideDown = () => webglController.reverseUpsideDown(); - const reverseSideToSide = () => webglController.reverseSideToSide(); - const rotateReverseMethods = [rotateLeft90Degree, rotateRight90Degree, reverseUpsideDown, reverseSideToSide]; - const rotateReverseMessages= ['왼쪽', '오른쪽', '상하 반전', '좌우 반전']; - const rorateReverseChildrens = [ - , - , - , - - ] - - const rotateReverse = () => { + const handleRotateLeft90Degree = () => webglController.rotateLeft90Degree(); + const handleRotateRight90Degree = () => webglController.rotateRight90Degree(); + const handleReverseUpsideDown = () => webglController.reverseUpsideDown(); + const handleReverseSideToSide = () => webglController.reverseSideToSide(); + const rotateReverseMethods = [ + handleRotateLeft90Degree, + handleRotateRight90Degree, + handleReverseUpsideDown, + handleReverseSideToSide, + ]; + + const handleRotateReverse = () => { if (toolType === 'videoEffect') { setToolType(null); - setButtonData({ - onClicks: null, - messages: null, - type: null, - childrens: null, - }); + dispatchButtonData({ type: null }); } else { setToolType('videoEffect'); - setButtonData({ - onClicks: rotateReverseMethods, - messages: rotateReverseMessages, - type: 'transparent', - childrens : rorateReverseChildrens, - }) + dispatchButtonData({ + type: 'videoEffect', + payload: rotateReverseMethods, + }); } - } - - const ratioEnlarge = () => webglController.enlarge(); - const ratioReduce = () => webglController.reduce(); - const ratioMethods = [ratioEnlarge, ratioReduce]; - const ratioMessages= ['확대', '축소']; - const ratioChildrens = [ - , - , - ] + }; + + const handleRatioEnlarge = () => webglController.enlarge(); + const handleRatioReduce = () => webglController.reduce(); + const ratioMethods = [handleRatioEnlarge, handleRatioReduce]; - const ratio = () => { + const handleRatio = () => { if (toolType === 'ratio') { setToolType(null); - setButtonData({ - onClicks: null, - messages: null, - type: null, - childrens: null, - }); + dispatchButtonData({ type: null }); } else { setToolType('ratio'); - setButtonData({ - onClicks: ratioMethods, - messages: ratioMessages, - type: 'transparent', - childrens : ratioChildrens, - }) + dispatchButtonData({ type: 'ratio', payload: ratioMethods }); } - } - - const cropInsert = () => webglController.enlarge(); - const cropConfirm = () => webglController.reduce(); - const cropCancle = () => webglController.reduce(); - const cropMethods = [cropInsert, cropConfirm, cropCancle]; - const cropMessages= ['직접입력', '확인', '취소']; - const cropChildrens = [ - , - , - , - ] + }; - const crop = () => { + const handleCropInsert = () => webglController.enlarge(); + const handleCropConfirm = () => { + dispatch(cropConfirm()); + setToolType(null); + dispatchButtonData({ type: null }); + dispatch(cropCancel()); + }; + const handleCropCancel = () => { + setToolType(null); + dispatchButtonData({ type: null }); + dispatch(cropCancel()); + }; + const cropMethods = [handleCropInsert, handleCropConfirm, handleCropCancel]; + + const handleCrop = () => { if (toolType === 'crop') { + dispatch(cropCancel()); setToolType(null); - setButtonData({ - onClicks: null, - messages: null, - type: null, - childrens: null, - }); + dispatchButtonData({ type: null }); } else { + dispatch(cropStart()); setToolType('crop'); - setButtonData({ - onClicks: cropMethods, - messages: cropMessages, - type: 'transparent', - childrens : cropChildrens, - }) + dispatchButtonData({ type: 'crop', payload: cropMethods }); } }; @@ -298,12 +245,14 @@ const Tools: React.FC = () => { )} /> - - + + diff --git a/client/src/components/organisms/Tools/reducer.tsx b/client/src/components/organisms/Tools/reducer.tsx new file mode 100644 index 0000000..54d5e1d --- /dev/null +++ b/client/src/components/organisms/Tools/reducer.tsx @@ -0,0 +1,82 @@ +import React from 'react'; +import { BsTerminal, BsCheck, BsX } from 'react-icons/bs'; +import { + MdRotateLeft, + MdRotateRight, + MdZoomIn, + MdZoomOut, +} from 'react-icons/md'; +import { CgMergeHorizontal, CgMergeVertical } from 'react-icons/cg'; + +import size from '@/theme/sizes'; + +export interface ButtonData { + onClicks: (() => void)[]; + messages: string[]; + type: 'default' | 'transparent'; + childrens: React.ReactChild[]; +} + +export interface ButtonDataAction { + type: 'crop' | 'videoEffect' | 'ratio' | null; + payload?: (() => void)[]; +} + +// crop +const cropMessages = ['직접입력', '확인', '취소']; +const cropChildrens = [ + , + , + , +]; + +// videoEffect +const rotateReverseMessages = ['왼쪽', '오른쪽', '상하 반전', '좌우 반전']; +const rotateReverseChildrens = [ + , + , + , + , +]; + +// ratio +const ratioMessages = ['확대', '축소']; +const ratioChildrens = [ + , + , +]; + +export const initialData: ButtonData = { + onClicks: [], + messages: [], + type: 'transparent', + childrens: [], +}; + +export default (state: ButtonData, action: ButtonDataAction): ButtonData => { + switch (action.type) { + case 'crop': + return { + onClicks: action.payload, + messages: cropMessages, + type: 'transparent', + childrens: cropChildrens, + }; + case 'videoEffect': + return { + onClicks: action.payload, + messages: rotateReverseMessages, + type: 'transparent', + childrens: rotateReverseChildrens, + }; + case 'ratio': + return { + onClicks: action.payload, + messages: ratioMessages, + type: 'transparent', + childrens: ratioChildrens, + }; + default: + return initialData; + } +}; From 03e384fe0744d2208f8c7a479025666afe7ebbd9 Mon Sep 17 00:00:00 2001 From: SSH1997 Date: Tue, 1 Dec 2020 00:34:12 +0900 Subject: [PATCH 14/34] =?UTF-8?q?[FE]=20:=20[FEAT]=20#48=20Crop=20store=20?= =?UTF-8?q?=EB=93=B1=EB=A1=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/store/actionTypes.ts | 12 ++++++++ client/src/store/crop/actions.ts | 44 ++++++++++++++++++++++++++++++ client/src/store/crop/reducer.ts | 47 ++++++++++++++++++++++++++++++++ client/src/store/reducer.ts | 3 ++ client/src/store/selectors.ts | 9 ++++++ 5 files changed, 115 insertions(+) create mode 100644 client/src/store/crop/actions.ts create mode 100644 client/src/store/crop/reducer.ts diff --git a/client/src/store/actionTypes.ts b/client/src/store/actionTypes.ts index e4a1eb2..f7f6bd0 100644 --- a/client/src/store/actionTypes.ts +++ b/client/src/store/actionTypes.ts @@ -16,6 +16,18 @@ export const MOVE_TO = 'current/MOVE_TO'; export const SET_THUMBNAILS = 'current/SET_THUMBNAILS'; export const CROP = 'current/CROP'; +// crop +export const CROP_START = 'crop/CROP_START'; +export const cropStart = () => ({ type: CROP_START }); + +export const CROP_CANCEL = 'crop/CROP_CANCEL'; +export const cropCancel = () => ({ type: CROP_CANCEL }); + +export const CROP_CONFIRM = 'crop/CROP_CONFIRM'; +export const cropConfirm = () => ({ type: CROP_CONFIRM }); + +export const CROP_END = 'crop/CROP_END'; +export const cropEnd = () => ({ type: CROP_END }); // history // global diff --git a/client/src/store/crop/actions.ts b/client/src/store/crop/actions.ts new file mode 100644 index 0000000..17c2123 --- /dev/null +++ b/client/src/store/crop/actions.ts @@ -0,0 +1,44 @@ +import { + CROP_START, + CROP_CANCEL, + CROP_CONFIRM, + CROP_END, +} from '../actionTypes'; + +export const cropStart = () => ({ + type: typeof CROP_START, +}); + +export const cropCancel = () => ({ + type: typeof CROP_CANCEL, +}); + +export const cropConfirm = () => ({ + type: typeof CROP_CONFIRM, +}); + +export const cropEnd = () => ({ + type: typeof CROP_END, +}); + +export type CropStartAction = { + type: typeof CROP_START; +}; + +export type CropCancelAction = { + type: typeof CROP_CANCEL; +}; + +export type CropConfirmAction = { + type: typeof CROP_CONFIRM; +}; + +export type CropEnd = { + type: typeof CROP_END; +}; + +export type CropAction = + | CropStartAction + | CropCancelAction + | CropConfirmAction + | CropEnd; diff --git a/client/src/store/crop/reducer.ts b/client/src/store/crop/reducer.ts new file mode 100644 index 0000000..3121fc1 --- /dev/null +++ b/client/src/store/crop/reducer.ts @@ -0,0 +1,47 @@ +import { + CROP_START, + CROP_CANCEL, + CROP_CONFIRM, + CROP_END, +} from '../actionTypes'; +import { CropAction } from './actions'; + +export interface CropState { + isCrop: boolean; + isCropConfirm: boolean; +} + +const initialState: CropState = { + isCrop: false, + isCropConfirm: false, +}; + +export default ( + state: CropState = initialState, + action: CropAction +): CropState => { + switch (action.type) { + case CROP_START: + return { + ...state, + isCrop: true, + }; + case CROP_CANCEL: + return { + ...state, + isCrop: false, + }; + case CROP_CONFIRM: + return { + ...state, + isCropConfirm: true, + }; + case CROP_END: // -> CROP + return { + ...state, + isCropConfirm: false, + }; + default: + return state; + } +}; diff --git a/client/src/store/reducer.ts b/client/src/store/reducer.ts index 5e786ba..93013ec 100644 --- a/client/src/store/reducer.ts +++ b/client/src/store/reducer.ts @@ -1,15 +1,18 @@ import { combineReducers } from 'redux'; import originalVideo, { OriginalVideoState } from './originalVideo/reducer'; import currentVideo, { CurrentVideoState } from './currentVideo/reducer'; +import crop, { CropState } from './crop/reducer'; export interface RootState { originalVideo: OriginalVideoState; currentVideo: CurrentVideoState; + crop: CropState; } const reducers = { originalVideo, currentVideo, + crop, }; export default combineReducers(reducers); diff --git a/client/src/store/selectors.ts b/client/src/store/selectors.ts index 336be27..190ca8f 100644 --- a/client/src/store/selectors.ts +++ b/client/src/store/selectors.ts @@ -25,3 +25,12 @@ export const getStartEnd = (state: RootState) => { }; export const getThumbnails = (state: RootState) => state.currentVideo.thumbnails; + +// crop +export const getIsCrop = (state: RootState) => state.crop.isCrop; + +export const getIsCropConfirm = (state: RootState) => state.crop.isCropConfirm; + +// export getThumbnailsEffect = (state:RootState) => { +// return {state.currentVideo.thumbnails,state.crop.isCrop,state.crop.isCropConfirm} +// } From 09434149ad1167c01a4e174a52185a99a9fa421a Mon Sep 17 00:00:00 2001 From: SSH1997 Date: Tue, 1 Dec 2020 00:39:22 +0900 Subject: [PATCH 15/34] =?UTF-8?q?[FE]=20:=20[FEAT]=20#19=20Thumbnail?= =?UTF-8?q?=EC=9D=98=20crop=20logic=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../molecules/Thumbnail/Thumbnail.tsx | 32 ++++++++++++++++--- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/client/src/components/molecules/Thumbnail/Thumbnail.tsx b/client/src/components/molecules/Thumbnail/Thumbnail.tsx index ca855bf..67a23db 100644 --- a/client/src/components/molecules/Thumbnail/Thumbnail.tsx +++ b/client/src/components/molecules/Thumbnail/Thumbnail.tsx @@ -1,12 +1,18 @@ -import React, { useState, useRef } from 'react'; +import React, { useState, useRef, useEffect } from 'react'; import styled from 'styled-components'; -import { useDispatch, useSelector } from 'react-redux'; +import { shallowEqual, useDispatch, useSelector } from 'react-redux'; -import { moveTo } from '@/store/currentVideo/actions'; +import { moveTo, crop } from '@/store/currentVideo/actions'; +import { cropEnd } from '@/store/actionTypes'; import Slider from '@/components/atoms/Slider'; import HoverSlider from '@/components/atoms/HoverSlider'; import video from '@/video'; -import { getThumbnails } from '@/store/selectors'; +import { + getThumbnails, + getIsCrop, + getIsCropConfirm, + getStartEnd, +} from '@/store/selectors'; import CropLayer from '@/components/molecules/CropLayer'; const StyledDiv = styled.div` @@ -24,11 +30,27 @@ const StyledImg = styled.img` const Thumbnail: React.FC = () => { const thumbnails = useSelector(getThumbnails); + const isCrop = useSelector(getIsCrop); + const isCropConfirm = useSelector(getIsCropConfirm); + // const { thumbnails,isCrop, isCropConfirm } = useSelector(getThumbnailsEffect, shallowEqual); + const [time, setTime] = useState(0); const [position, setPosition] = useState([0, 0]); + const { start, end } = useSelector(getStartEnd); const dispatch = useDispatch(); + useEffect(() => { + if (isCrop) { + setPosition([start, end]); + } + }, [isCrop]); + + useEffect(() => { + dispatch(crop(position[0], position[1])); + dispatch(cropEnd()); + }, [isCropConfirm]); + const thumbnailRef = useRef(null); const hoverSliderRef = useRef(null); @@ -71,7 +93,7 @@ const Thumbnail: React.FC = () => { onMouseLeave={handleMouseLeave} onMouseEnter={handleMouseEnter} > - + {isCrop && } {thumbnails.map(image => { From 7ab72888828b951b1f60499af3f210e418518c69 Mon Sep 17 00:00:00 2001 From: SSH1997 Date: Tue, 1 Dec 2020 00:39:59 +0900 Subject: [PATCH 16/34] =?UTF-8?q?[FE]=20:=20[FEAT]=20#39=20Crop=20Layer=20?= =?UTF-8?q?MIN=20MAX=20=EC=84=A4=EC=A0=95=20Logic=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/molecules/CropLayer/CropLayer.tsx | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/client/src/components/molecules/CropLayer/CropLayer.tsx b/client/src/components/molecules/CropLayer/CropLayer.tsx index b1930b1..df1022c 100644 --- a/client/src/components/molecules/CropLayer/CropLayer.tsx +++ b/client/src/components/molecules/CropLayer/CropLayer.tsx @@ -1,13 +1,12 @@ -import React, { useState } from 'react'; -import { useSelector } from 'react-redux'; +import React from 'react'; import { Range } from 'react-range'; import styled from 'styled-components'; + import color from '@/theme/colors'; import convertReactStyleToCSS from '@/utils/convert'; +import video from '@/video'; -const STEP = 0.1; const MIN = 0; -const MAX = 100; interface OverlayProps { width: number; @@ -59,7 +58,9 @@ const Thumb = styled.div` } `; -const CropLayer = ({ positions, setPositions, duration }) => { +const CropLayer = ({ positions, setPositions }) => { + const MAX = video.getDuration(); + const STEP = (MAX - MIN) / 1024; return ( { onMouseDown={props.onMouseDown} onTouchStart={props.onTouchStart} > - - + +
Date: Tue, 1 Dec 2020 09:15:21 +0900 Subject: [PATCH 17/34] =?UTF-8?q?[FE]=20:=20[FIX]=20video=20get=20method?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - video.getDuration -> video.get('duration') --- client/src/components/molecules/CropLayer/CropLayer.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/components/molecules/CropLayer/CropLayer.tsx b/client/src/components/molecules/CropLayer/CropLayer.tsx index df1022c..bf6c427 100644 --- a/client/src/components/molecules/CropLayer/CropLayer.tsx +++ b/client/src/components/molecules/CropLayer/CropLayer.tsx @@ -59,7 +59,7 @@ const Thumb = styled.div` `; const CropLayer = ({ positions, setPositions }) => { - const MAX = video.getDuration(); + const MAX = video.get('duration'); const STEP = (MAX - MIN) / 1024; return ( From 5ed1bde98be36298f9c745cfa6d6ca9a9fe95775 Mon Sep 17 00:00:00 2001 From: SSH1997 Date: Tue, 1 Dec 2020 09:16:53 +0900 Subject: [PATCH 18/34] =?UTF-8?q?[FE]=20:=20[REFACTOR]=20useSelector=20?= =?UTF-8?q?=ED=86=B5=ED=95=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - thumbnails / isCrop / isCropConfirm useSelector 통합 --- .../molecules/Thumbnail/Thumbnail.tsx | 17 ++++++----------- client/src/store/selectors.ts | 13 ++++++++++--- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/client/src/components/molecules/Thumbnail/Thumbnail.tsx b/client/src/components/molecules/Thumbnail/Thumbnail.tsx index 5da584e..1a20c3c 100644 --- a/client/src/components/molecules/Thumbnail/Thumbnail.tsx +++ b/client/src/components/molecules/Thumbnail/Thumbnail.tsx @@ -7,12 +7,7 @@ import { cropEnd } from '@/store/actionTypes'; import Slider from '@/components/atoms/Slider'; import HoverSlider from '@/components/atoms/HoverSlider'; import video from '@/video'; -import { - getThumbnails, - getIsCrop, - getIsCropConfirm, - getStartEnd, -} from '@/store/selectors'; +import { getThumbnailsEffect, getStartEnd } from '@/store/selectors'; import CropLayer from '@/components/molecules/CropLayer'; const StyledDiv = styled.div` @@ -29,14 +24,14 @@ const StyledImg = styled.img` `; const Thumbnail: React.FC = () => { - const thumbnails = useSelector(getThumbnails); - const isCrop = useSelector(getIsCrop); - const isCropConfirm = useSelector(getIsCropConfirm); - // const { thumbnails,isCrop, isCropConfirm } = useSelector(getThumbnailsEffect, shallowEqual); + const { thumbnails, isCrop, isCropConfirm } = useSelector( + getThumbnailsEffect, + shallowEqual + ); + const { start, end } = useSelector(getStartEnd); const [time, setTime] = useState(0); const [position, setPosition] = useState([0, 0]); - const { start, end } = useSelector(getStartEnd); const dispatch = useDispatch(); diff --git a/client/src/store/selectors.ts b/client/src/store/selectors.ts index 190ca8f..e072779 100644 --- a/client/src/store/selectors.ts +++ b/client/src/store/selectors.ts @@ -31,6 +31,13 @@ export const getIsCrop = (state: RootState) => state.crop.isCrop; export const getIsCropConfirm = (state: RootState) => state.crop.isCropConfirm; -// export getThumbnailsEffect = (state:RootState) => { -// return {state.currentVideo.thumbnails,state.crop.isCrop,state.crop.isCropConfirm} -// } +export const getThumbnailsEffect = (state: RootState) => { + const { thumbnails } = state.currentVideo; + const { isCrop, isCropConfirm } = state.crop; + + return { + thumbnails, + isCrop, + isCropConfirm, + }; +}; From 823c61761a1df753b07130e91964aa579f254d78 Mon Sep 17 00:00:00 2001 From: SSH1997 Date: Tue, 1 Dec 2020 09:17:42 +0900 Subject: [PATCH 19/34] =?UTF-8?q?[FE]=20:=20[FIX]=20cropEnd=20action?= =?UTF-8?q?=EC=9D=84=20crop=20action=EC=9C=BC=EB=A1=9C=20=EB=8C=80?= =?UTF-8?q?=EC=B2=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../molecules/Thumbnail/Thumbnail.tsx | 2 -- client/src/store/actionTypes.ts | 2 -- client/src/store/crop/actions.ts | 17 ++++------------- client/src/store/crop/reducer.ts | 9 ++------- 4 files changed, 6 insertions(+), 24 deletions(-) diff --git a/client/src/components/molecules/Thumbnail/Thumbnail.tsx b/client/src/components/molecules/Thumbnail/Thumbnail.tsx index 1a20c3c..093eebd 100644 --- a/client/src/components/molecules/Thumbnail/Thumbnail.tsx +++ b/client/src/components/molecules/Thumbnail/Thumbnail.tsx @@ -3,7 +3,6 @@ import styled from 'styled-components'; import { shallowEqual, useDispatch, useSelector } from 'react-redux'; import { moveTo, crop } from '@/store/currentVideo/actions'; -import { cropEnd } from '@/store/actionTypes'; import Slider from '@/components/atoms/Slider'; import HoverSlider from '@/components/atoms/HoverSlider'; import video from '@/video'; @@ -43,7 +42,6 @@ const Thumbnail: React.FC = () => { useEffect(() => { dispatch(crop(position[0], position[1])); - dispatch(cropEnd()); }, [isCropConfirm]); const thumbnailRef = useRef(null); diff --git a/client/src/store/actionTypes.ts b/client/src/store/actionTypes.ts index f7f6bd0..93c9903 100644 --- a/client/src/store/actionTypes.ts +++ b/client/src/store/actionTypes.ts @@ -26,8 +26,6 @@ export const cropCancel = () => ({ type: CROP_CANCEL }); export const CROP_CONFIRM = 'crop/CROP_CONFIRM'; export const cropConfirm = () => ({ type: CROP_CONFIRM }); -export const CROP_END = 'crop/CROP_END'; -export const cropEnd = () => ({ type: CROP_END }); // history // global diff --git a/client/src/store/crop/actions.ts b/client/src/store/crop/actions.ts index 17c2123..1c8add8 100644 --- a/client/src/store/crop/actions.ts +++ b/client/src/store/crop/actions.ts @@ -1,9 +1,4 @@ -import { - CROP_START, - CROP_CANCEL, - CROP_CONFIRM, - CROP_END, -} from '../actionTypes'; +import { CROP_START, CROP_CANCEL, CROP_CONFIRM, CROP } from '../actionTypes'; export const cropStart = () => ({ type: typeof CROP_START, @@ -17,10 +12,6 @@ export const cropConfirm = () => ({ type: typeof CROP_CONFIRM, }); -export const cropEnd = () => ({ - type: typeof CROP_END, -}); - export type CropStartAction = { type: typeof CROP_START; }; @@ -33,12 +24,12 @@ export type CropConfirmAction = { type: typeof CROP_CONFIRM; }; -export type CropEnd = { - type: typeof CROP_END; +export type Crop = { + type: typeof CROP; }; export type CropAction = | CropStartAction | CropCancelAction | CropConfirmAction - | CropEnd; + | Crop; diff --git a/client/src/store/crop/reducer.ts b/client/src/store/crop/reducer.ts index 3121fc1..5f2625e 100644 --- a/client/src/store/crop/reducer.ts +++ b/client/src/store/crop/reducer.ts @@ -1,9 +1,4 @@ -import { - CROP_START, - CROP_CANCEL, - CROP_CONFIRM, - CROP_END, -} from '../actionTypes'; +import { CROP_START, CROP_CANCEL, CROP_CONFIRM, CROP } from '../actionTypes'; import { CropAction } from './actions'; export interface CropState { @@ -36,7 +31,7 @@ export default ( ...state, isCropConfirm: true, }; - case CROP_END: // -> CROP + case CROP: return { ...state, isCropConfirm: false, From 9dbf2c15c8bc8d93daa48aabf72a7a4d6da6f4fa Mon Sep 17 00:00:00 2001 From: SSH1997 Date: Tue, 1 Dec 2020 09:40:43 +0900 Subject: [PATCH 20/34] =?UTF-8?q?[FE]=20:=20[FIX]=20#48=20cropConfrim=20ac?= =?UTF-8?q?tion=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/components/organisms/Tools/Tools.tsx | 1 - client/src/store/crop/reducer.ts | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/components/organisms/Tools/Tools.tsx b/client/src/components/organisms/Tools/Tools.tsx index 31f8a22..27e67da 100644 --- a/client/src/components/organisms/Tools/Tools.tsx +++ b/client/src/components/organisms/Tools/Tools.tsx @@ -213,7 +213,6 @@ const Tools: React.FC = () => { dispatch(cropConfirm()); setToolType(null); dispatchButtonData({ type: null }); - dispatch(cropCancel()); }; const handleCropCancel = () => { setToolType(null); diff --git a/client/src/store/crop/reducer.ts b/client/src/store/crop/reducer.ts index 5f2625e..e896537 100644 --- a/client/src/store/crop/reducer.ts +++ b/client/src/store/crop/reducer.ts @@ -29,6 +29,7 @@ export default ( case CROP_CONFIRM: return { ...state, + isCrop: false, isCropConfirm: true, }; case CROP: From dafa042c2f858e3ed87010f13efb08353b4793b5 Mon Sep 17 00:00:00 2001 From: SSH1997 Date: Tue, 1 Dec 2020 09:42:41 +0900 Subject: [PATCH 21/34] =?UTF-8?q?[FE]=20:=20[FIX]=20#19=20isCropConfirm=20?= =?UTF-8?q?selector=20=EB=A1=9C=EC=A7=81=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - false -> true 일 때만 useEffect에서 currentVideoStore의 crop action을 dispatch하도록 변경 --- client/src/components/molecules/Thumbnail/Thumbnail.tsx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/client/src/components/molecules/Thumbnail/Thumbnail.tsx b/client/src/components/molecules/Thumbnail/Thumbnail.tsx index 093eebd..fc87998 100644 --- a/client/src/components/molecules/Thumbnail/Thumbnail.tsx +++ b/client/src/components/molecules/Thumbnail/Thumbnail.tsx @@ -35,13 +35,11 @@ const Thumbnail: React.FC = () => { const dispatch = useDispatch(); useEffect(() => { - if (isCrop) { - setPosition([start, end]); - } + if (isCrop) setPosition([start, end]); }, [isCrop]); useEffect(() => { - dispatch(crop(position[0], position[1])); + if (isCropConfirm) dispatch(crop(position[0], position[1])); }, [isCropConfirm]); const thumbnailRef = useRef(null); From 391a14c97b832bb85e52d79a1ded00df2bc205cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=80=E1=85=AF=E1=86=AB=E1=84=8B=E1=85=A7=E1=86=BC?= =?UTF-8?q?=E1=84=8B=E1=85=A5=E1=86=AB?= Date: Tue, 1 Dec 2020 10:06:51 +0900 Subject: [PATCH 22/34] =?UTF-8?q?[FE]:=20[REFACTOR]=20App=20->=20EditPage?= =?UTF-8?q?=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/App.tsx | 16 ++-------------- client/src/pages/edit.tsx | 39 ++++++++++++++++++++++++++++++++++++++ client/src/pages/index.tsx | 0 3 files changed, 41 insertions(+), 14 deletions(-) create mode 100644 client/src/pages/edit.tsx delete mode 100644 client/src/pages/index.tsx diff --git a/client/src/App.tsx b/client/src/App.tsx index 2f488aa..2554d10 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -1,25 +1,13 @@ import React from 'react'; -import { useSelector } from 'react-redux'; -import TimeLine from '@/components/organisms/TimeLine'; +import EditPage from '@/pages/Edit'; import GlobalStyle from '@/theme/globalStyle'; -import Header from '@/components/organisms/Header'; -import Tools from '@/components/organisms/Tools'; -import VideoContainer from '@/components/organisms/VideoContainer'; -import Loading from '@/components/atoms/Loading'; -import { getMessage } from '@/store/selectors'; const App: React.FC = () => { - const message = useSelector(getMessage); - return ( <> - {message && } -
- - - + ); }; diff --git a/client/src/pages/edit.tsx b/client/src/pages/edit.tsx new file mode 100644 index 0000000..064edde --- /dev/null +++ b/client/src/pages/edit.tsx @@ -0,0 +1,39 @@ +import React, { useState } from 'react'; +import { useSelector } from 'react-redux'; +import styled from 'styled-components'; + +import TimeLine from '@/components/organisms/TimeLine'; +import Header from '@/components/organisms/Header'; +import Tools from '@/components/organisms/Tools'; +import VideoContainer from '@/components/organisms/VideoContainer'; +import Loading from '@/components/atoms/Loading'; +import { getMessage } from '@/store/selectors'; + +const StyledDiv = styled.div` + display: flex; + flex-direction: column; + justify-content: space-between; + height: 100vh; + width: 100vw; +`; + +const BottomDiv = styled.div``; + +const EditPage: React.FC = () => { + const message = useSelector(getMessage); + const [isEdit, setEdit] = useState(''); + + return ( + + {message && } +
+ + + + + + + ); +}; + +export default React.memo(EditPage); diff --git a/client/src/pages/index.tsx b/client/src/pages/index.tsx deleted file mode 100644 index e69de29..0000000 From db3ed13928df522ac3ce004b5fcb0d469c9cb83f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=80=E1=85=AF=E1=86=AB=E1=84=8B=E1=85=A7=E1=86=BC?= =?UTF-8?q?=E1=84=8B=E1=85=A5=E1=86=AB?= Date: Tue, 1 Dec 2020 10:07:32 +0900 Subject: [PATCH 23/34] =?UTF-8?q?[FE]:=20[REFACTOR]=20#20=20Tools=20?= =?UTF-8?q?=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - buttonData 분리 --- .../src/components/organisms/Tools/Tools.tsx | 185 +++++++----------- .../components/organisms/Tools/buttonData.tsx | 85 ++++++++ 2 files changed, 153 insertions(+), 117 deletions(-) create mode 100644 client/src/components/organisms/Tools/buttonData.tsx diff --git a/client/src/components/organisms/Tools/Tools.tsx b/client/src/components/organisms/Tools/Tools.tsx index 31f8a22..cfbffa9 100644 --- a/client/src/components/organisms/Tools/Tools.tsx +++ b/client/src/components/organisms/Tools/Tools.tsx @@ -1,20 +1,10 @@ import React, { useState, useReducer } from 'react'; -import styled from 'styled-components'; +import styled, { keyframes } from 'styled-components'; import { shallowEqual, useDispatch, useSelector } from 'react-redux'; -import { - BsFillSkipStartFill, - BsFillSkipEndFill, - BsFillPlayFill, - BsFillPauseFill, - BsAspectRatio, -} from 'react-icons/bs'; -import { RiScissorsLine } from 'react-icons/ri'; -import { MdScreenRotation } from 'react-icons/md'; import webglController from '@/webgl/webglController'; import ButtonGroup from '@/components/molecules/ButtonGroup'; import UploadArea from '@/components/molecules/UploadArea'; -import size from '@/theme/sizes'; import video from '@/video'; import { play as playAction, @@ -24,14 +14,33 @@ import { import { getStartEnd } from '@/store/selectors'; import { cropStart, cropCancel, cropConfirm } from '@/store/actionTypes'; -import reducer, { initialData, ButtonData, ButtonDataAction } from './reducer'; +import reducer, { initialData, ButtonDataAction } from './reducer'; +import { + getEditToolData, + getSubEditToolsData, + getVideoToolsData, +} from './buttonData'; + +const UP = 'up'; +const DOWN = 'down'; const StyledDiv = styled.div` + position: relative; display: flex; justify-content: space-between; align-items: flex-end; padding: 1rem; - height: 10rem; +`; + +const slide = keyframes` + from { + transform: translate(0, 50px); + opacity: 0; + } + to { + transform: translate(0, 0); + opacity: 1; + } `; const StyledEditToolDiv = styled.div` @@ -43,82 +52,25 @@ const StyledEditToolDiv = styled.div` const EditTool = styled(ButtonGroup)` width: 20rem; `; -const SubEditTool = styled(ButtonGroup)` + +const WrapperDiv = styled.div` width: 25rem; + display: flex; + justify-content: center; + animation: ${slide} 0.5s -0.1s ease-out; `; -const VideoTool = styled(ButtonGroup)``; -interface button { - onClick: () => void; - message: string; - type: 'default' | 'transparent'; - children: React.ReactChild; -} - -const getVideoToolsData = ( - backwardVideo: () => void, - playPauseVideo: () => void, - forwardVideo: () => void, - play: boolean -): button[] => [ - { - onClick: backwardVideo, - message: '', - type: 'transparent', - children: , - }, - { - onClick: playPauseVideo, - message: '', - type: 'transparent', - children: play ? ( - - ) : ( - - ), - }, - { - onClick: forwardVideo, - message: '', - type: 'transparent', - children: , - }, -]; +const SubEditTool = styled(ButtonGroup)` + width: 100%; +`; -const getEditToolData = ( - rotateReverse: () => void, - ratio: () => void, - crop: () => void -): button[] => [ - { - onClick: rotateReverse, - message: '회전 / 반전', - type: 'transparent', - children: , - }, - { - onClick: ratio, - message: '비율', - type: 'transparent', - children: , - }, - { - onClick: crop, - message: '자르기', - type: 'transparent', - children: , - }, -]; +const VideoTool = styled(ButtonGroup)``; -const getSubEditToolsData = (buttonData: ButtonData): button[] => - [...Array(buttonData.onClicks?.length)].map((_, idx) => ({ - onClick: buttonData.onClicks[idx], - message: buttonData.messages[idx], - type: buttonData.type, - children: buttonData.childrens[idx], - })); +interface props { + setEdit: Function; +} -const Tools: React.FC = () => { +const Tools: React.FC = ({ setEdit }) => { const [play, setPlay] = useState(true); // Fix 스토어로 등록 const dispatch = useDispatch(); const [toolType, setToolType] = useState(null); @@ -148,7 +100,6 @@ const Tools: React.FC = () => { video.pause(); dispatch(pause()); } - setPlay(!play); }; @@ -170,6 +121,21 @@ const Tools: React.FC = () => { } }; + const closeSubtool = () => { + setEdit(DOWN); + setToolType(null); + dispatchButtonData({ type: null }); + }; + + const openSubtool = ( + type: 'videoEffect' | 'ratio' | 'crop', + payload: (() => void)[] + ) => { + setEdit(UP); + setToolType(type); + dispatchButtonData({ type, payload }); + }; + const handleRotateLeft90Degree = () => webglController.rotateLeft90Degree(); const handleRotateRight90Degree = () => webglController.rotateRight90Degree(); const handleReverseUpsideDown = () => webglController.reverseUpsideDown(); @@ -181,56 +147,37 @@ const Tools: React.FC = () => { handleReverseSideToSide, ]; - const handleRotateReverse = () => { - if (toolType === 'videoEffect') { - setToolType(null); - dispatchButtonData({ type: null }); - } else { - setToolType('videoEffect'); - dispatchButtonData({ - type: 'videoEffect', - payload: rotateReverseMethods, - }); - } - }; - const handleRatioEnlarge = () => webglController.enlarge(); const handleRatioReduce = () => webglController.reduce(); const ratioMethods = [handleRatioEnlarge, handleRatioReduce]; - const handleRatio = () => { - if (toolType === 'ratio') { - setToolType(null); - dispatchButtonData({ type: null }); - } else { - setToolType('ratio'); - dispatchButtonData({ type: 'ratio', payload: ratioMethods }); - } - }; - - const handleCropInsert = () => webglController.enlarge(); + const handleCropInsert = () => null; const handleCropConfirm = () => { dispatch(cropConfirm()); - setToolType(null); - dispatchButtonData({ type: null }); + closeSubtool(); dispatch(cropCancel()); }; const handleCropCancel = () => { - setToolType(null); - dispatchButtonData({ type: null }); + closeSubtool(); dispatch(cropCancel()); }; const cropMethods = [handleCropInsert, handleCropConfirm, handleCropCancel]; + const handleRotateReverse = () => + toolType === 'videoEffect' + ? closeSubtool() + : openSubtool('videoEffect', rotateReverseMethods); + + const handleRatio = () => + toolType === 'ratio' ? closeSubtool() : openSubtool('ratio', ratioMethods); + const handleCrop = () => { if (toolType === 'crop') { dispatch(cropCancel()); - setToolType(null); - dispatchButtonData({ type: null }); + closeSubtool(); } else { + openSubtool('crop', cropMethods); dispatch(cropStart()); - setToolType('crop'); - dispatchButtonData({ type: 'crop', payload: cropMethods }); } }; @@ -245,7 +192,11 @@ const Tools: React.FC = () => { )} /> - + {toolType && ( + + + + )} void; + message: string; + type: 'default' | 'transparent'; + children: React.ReactChild; +} + +export const getVideoToolsData = ( + backwardVideo: () => void, + playPauseVideo: () => void, + forwardVideo: () => void, + play: boolean +): button[] => [ + { + onClick: backwardVideo, + message: '', + type: 'transparent', + children: , + }, + { + onClick: playPauseVideo, + message: '', + type: 'transparent', + children: play ? ( + + ) : ( + + ), + }, + { + onClick: forwardVideo, + message: '', + type: 'transparent', + children: , + }, +]; + +export const getEditToolData = ( + rotateReverse: () => void, + ratio: () => void, + crop: () => void +): button[] => [ + { + onClick: rotateReverse, + message: '회전 / 반전', + type: 'transparent', + children: , + }, + { + onClick: ratio, + message: '비율', + type: 'transparent', + children: , + }, + { + onClick: crop, + message: '자르기', + type: 'transparent', + children: , + }, +]; + +export const getSubEditToolsData = (buttonData: ButtonData): button[] => + [...Array(buttonData.onClicks?.length)].map((_, idx) => ({ + onClick: buttonData.onClicks[idx], + message: buttonData.messages[idx], + type: buttonData.type, + children: buttonData.childrens[idx], + })); From 3b52408db5102d8237f1e0a516f1278319b6ac6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=80=E1=85=AF=E1=86=AB=E1=84=8B=E1=85=A7=E1=86=BC?= =?UTF-8?q?=E1=84=8B=E1=85=A5=E1=86=AB?= Date: Tue, 1 Dec 2020 10:07:59 +0900 Subject: [PATCH 24/34] =?UTF-8?q?[FE]:=20[FEAT]=20subTool=20=EC=95=A0?= =?UTF-8?q?=EB=8B=88=EB=A9=94=EC=9D=B4=EC=85=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../VideoContainer/VideoContainer.tsx | 43 +++++++++++++++++-- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/client/src/components/organisms/VideoContainer/VideoContainer.tsx b/client/src/components/organisms/VideoContainer/VideoContainer.tsx index 8e77158..42628fb 100644 --- a/client/src/components/organisms/VideoContainer/VideoContainer.tsx +++ b/client/src/components/organisms/VideoContainer/VideoContainer.tsx @@ -1,8 +1,37 @@ import React from 'react'; -import styled from 'styled-components'; +import styled, { css, keyframes } from 'styled-components'; import color from '@/theme/colors'; +const UP = 'up'; +const DOWN = 'down'; + +const slideUp = keyframes` + from { + transform: translate(0, 2rem); + } + to { + transform: translate(0, 0); + } +`; + +const slideDown = keyframes` + from { + transform: translate(0, -2rem); + } + to { + transform: translate(0, 0); + } +`; + +const videoUp = css` + animation: ${slideUp} 0.3s ease-out; +`; + +const videoDown = css` + animation: ${slideDown} 0.5s ease-out; +`; + const StyledDiv = styled.div` display: flex; justify-content: center; @@ -10,14 +39,20 @@ const StyledDiv = styled.div` `; const StyledCanvas = styled.canvas` - height: 30rem; + height: 32rem; background-color: ${color.BLACK}; + ${({ isEdit }) => (isEdit === UP ? videoUp : '')}; + ${({ isEdit }) => (isEdit === DOWN ? videoDown : '')}; `; -const VideoContainer: React.FC = () => { +interface props { + isEdit: string; +} + +const VideoContainer: React.FC = ({ isEdit }) => { return ( - + ); }; From 63a6425277d3b6893836eed08ca9bc3a97a9d9b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=80=E1=85=AF=E1=86=AB=E1=84=8B=E1=85=A7=E1=86=BC?= =?UTF-8?q?=E1=84=8B=E1=85=A5=E1=86=AB?= Date: Tue, 1 Dec 2020 10:08:35 +0900 Subject: [PATCH 25/34] =?UTF-8?q?[FE]:=20[FIX]=20=EB=A6=AC=ED=8C=A9?= =?UTF-8?q?=ED=86=A0=EB=A7=81=20=ED=9B=84=20=EC=88=98=EC=A0=95=20=EC=95=88?= =?UTF-8?q?=ED=95=9C=20=EB=B6=80=EB=B6=84=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/components/molecules/CropLayer/CropLayer.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/components/molecules/CropLayer/CropLayer.tsx b/client/src/components/molecules/CropLayer/CropLayer.tsx index df1022c..bf6c427 100644 --- a/client/src/components/molecules/CropLayer/CropLayer.tsx +++ b/client/src/components/molecules/CropLayer/CropLayer.tsx @@ -59,7 +59,7 @@ const Thumb = styled.div` `; const CropLayer = ({ positions, setPositions }) => { - const MAX = video.getDuration(); + const MAX = video.get('duration'); const STEP = (MAX - MIN) / 1024; return ( From d575402c8d33c6231fba6eed807cf9c18f4eb094 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=80=E1=85=AF=E1=86=AB=E1=84=8B=E1=85=A7=E1=86=BC?= =?UTF-8?q?=E1=84=8B=E1=85=A5=E1=86=AB?= Date: Tue, 1 Dec 2020 10:20:31 +0900 Subject: [PATCH 26/34] =?UTF-8?q?[FE]:=20[FIX]=20#20=20crop=20=EC=83=81?= =?UTF-8?q?=ED=83=9C=EC=97=90=EC=84=9C=20=EB=8B=A4=EB=A5=B8=20=EB=B2=84?= =?UTF-8?q?=ED=8A=BC=EC=9D=84=20=EB=88=8C=EB=A0=80=EC=9D=84=EB=95=8C=20cro?= =?UTF-8?q?pLayer=EA=B0=80=20=EC=82=AC=EB=9D=BC=EC=A7=80=EC=A7=80=EC=95=8A?= =?UTF-8?q?=EB=8D=98=20=EB=AC=B8=EC=A0=9C=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/components/organisms/Tools/Tools.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/client/src/components/organisms/Tools/Tools.tsx b/client/src/components/organisms/Tools/Tools.tsx index d0f1dc8..4d4657f 100644 --- a/client/src/components/organisms/Tools/Tools.tsx +++ b/client/src/components/organisms/Tools/Tools.tsx @@ -131,6 +131,7 @@ const Tools: React.FC = ({ setEdit }) => { type: 'videoEffect' | 'ratio' | 'crop', payload: (() => void)[] ) => { + if (type !== 'crop') dispatch(cropCancel()); setEdit(UP); setToolType(type); dispatchButtonData({ type, payload }); From c1694bc46bb2b4f6b0b95160bbc8897bc2b8883b Mon Sep 17 00:00:00 2001 From: SSH1997 Date: Tue, 1 Dec 2020 14:44:06 +0900 Subject: [PATCH 27/34] =?UTF-8?q?[FE]=20:=20[FIX]=20Tools=20play=20state?= =?UTF-8?q?=20->=20useSelector=EB=A1=9C=20=EB=8C=80=EC=B2=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/components/organisms/Tools/Tools.tsx | 5 ++--- client/src/components/organisms/Tools/buttonData.tsx | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/client/src/components/organisms/Tools/Tools.tsx b/client/src/components/organisms/Tools/Tools.tsx index 4d4657f..a7d226d 100644 --- a/client/src/components/organisms/Tools/Tools.tsx +++ b/client/src/components/organisms/Tools/Tools.tsx @@ -71,7 +71,7 @@ interface props { } const Tools: React.FC = ({ setEdit }) => { - const [play, setPlay] = useState(true); // Fix 스토어로 등록 + const play = useSelector(getPlaying); const dispatch = useDispatch(); const [toolType, setToolType] = useState(null); const [buttonData, dispatchButtonData] = useReducer(reducer, initialData); @@ -93,14 +93,13 @@ const Tools: React.FC = ({ setEdit }) => { }; const playPauseVideo = () => { - if (play) { + if (!play) { video.play(); dispatch(playAction()); } else { video.pause(); dispatch(pause()); } - setPlay(!play); }; document.onkeydown = (event: KeyboardEvent) => { diff --git a/client/src/components/organisms/Tools/buttonData.tsx b/client/src/components/organisms/Tools/buttonData.tsx index 12ec479..9b7241d 100644 --- a/client/src/components/organisms/Tools/buttonData.tsx +++ b/client/src/components/organisms/Tools/buttonData.tsx @@ -38,9 +38,9 @@ export const getVideoToolsData = ( message: '', type: 'transparent', children: play ? ( - - ) : ( + ) : ( + ), }, { From fd60408073814ca3f1bc9d69986edb525551ac66 Mon Sep 17 00:00:00 2001 From: SSH1997 Date: Tue, 1 Dec 2020 14:46:49 +0900 Subject: [PATCH 28/34] =?UTF-8?q?[FE]=20:=20[FIX]=20Crop=20Layer=20?= =?UTF-8?q?=EC=83=81=ED=83=9C=EA=B4=80=EB=A6=AC=20=EB=B0=A9=EC=8B=9D=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 기존에 Thumbnail에서 관리하던 position / isCrop / isCropConfirm 등을 cropLayer에서 useSelector로 관리 --- .../molecules/CropLayer/CropLayer.tsx | 23 ++++++++++++++-- .../molecules/Thumbnail/Thumbnail.tsx | 26 +++++-------------- client/src/store/crop/reducer.ts | 3 +-- client/src/store/selectors.ts | 7 +++-- 4 files changed, 32 insertions(+), 27 deletions(-) diff --git a/client/src/components/molecules/CropLayer/CropLayer.tsx b/client/src/components/molecules/CropLayer/CropLayer.tsx index bf6c427..26d3ebe 100644 --- a/client/src/components/molecules/CropLayer/CropLayer.tsx +++ b/client/src/components/molecules/CropLayer/CropLayer.tsx @@ -1,10 +1,13 @@ -import React from 'react'; +import React, { useEffect, useState } from 'react'; +import { useDispatch, useSelector, shallowEqual } from 'react-redux'; import { Range } from 'react-range'; import styled from 'styled-components'; import color from '@/theme/colors'; import convertReactStyleToCSS from '@/utils/convert'; import video from '@/video'; +import { crop } from '@/store/currentVideo/actions'; +import { getStartEnd, getCropState } from '@/store/selectors'; const MIN = 0; @@ -58,9 +61,25 @@ const Thumb = styled.div` } `; -const CropLayer = ({ positions, setPositions }) => { +const CropLayer = () => { const MAX = video.get('duration'); const STEP = (MAX - MIN) / 1024; + + const { start, end } = useSelector(getStartEnd, shallowEqual); + const { isCrop, isCropConfirm } = useSelector(getCropState, shallowEqual); + + const [positions, setPositions] = useState([0, 0]); + + const dispatch = useDispatch(); + + useEffect(() => { + if (isCrop) setPositions([start, end]); + }, [isCrop]); + + useEffect(() => { + if (isCropConfirm) dispatch(crop(positions[0], positions[1])); + }, [isCropConfirm]); + return ( { - const { thumbnails, isCrop, isCropConfirm } = useSelector( - getThumbnailsEffect, - shallowEqual - ); - const { start, end } = useSelector(getStartEnd); + const thumbnails = useSelector(getThumbnails); + const isCrop = useSelector(getIsCrop); const [time, setTime] = useState(0); - const [position, setPosition] = useState([0, 0]); const dispatch = useDispatch(); - useEffect(() => { - if (isCrop) setPosition([start, end]); - }, [isCrop]); - - useEffect(() => { - if (isCropConfirm) dispatch(crop(position[0], position[1])); - }, [isCropConfirm]); - const thumbnailRef = useRef(null); const hoverSliderRef = useRef(null); @@ -84,7 +72,7 @@ const Thumbnail: React.FC = () => { onMouseLeave={handleMouseLeave} onMouseEnter={handleMouseEnter} > - {isCrop && } + {isCrop && } {thumbnails.map(image => { diff --git a/client/src/store/crop/reducer.ts b/client/src/store/crop/reducer.ts index e896537..d5d27f6 100644 --- a/client/src/store/crop/reducer.ts +++ b/client/src/store/crop/reducer.ts @@ -29,12 +29,11 @@ export default ( case CROP_CONFIRM: return { ...state, - isCrop: false, isCropConfirm: true, }; case CROP: return { - ...state, + isCrop: false, isCropConfirm: false, }; default: diff --git a/client/src/store/selectors.ts b/client/src/store/selectors.ts index e072779..fd48a05 100644 --- a/client/src/store/selectors.ts +++ b/client/src/store/selectors.ts @@ -31,12 +31,11 @@ export const getIsCrop = (state: RootState) => state.crop.isCrop; export const getIsCropConfirm = (state: RootState) => state.crop.isCropConfirm; -export const getThumbnailsEffect = (state: RootState) => { - const { thumbnails } = state.currentVideo; - const { isCrop, isCropConfirm } = state.crop; +export const getCropState = (state: RootState) => { + const { isCrop } = state.crop; + const { isCropConfirm } = state.crop; return { - thumbnails, isCrop, isCropConfirm, }; From 9c8c98027fa073f0f7d56f0a4dc2c28a8ad6d545 Mon Sep 17 00:00:00 2001 From: SSH1997 Date: Tue, 1 Dec 2020 14:47:26 +0900 Subject: [PATCH 29/34] =?UTF-8?q?[FE]=20:=20[FIX]=20=EC=8D=B8=EB=84=A4?= =?UTF-8?q?=EC=9D=BC=20=ED=91=9C=EC=8B=9C=20Logic=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/components/molecules/Thumbnail/Thumbnail.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/components/molecules/Thumbnail/Thumbnail.tsx b/client/src/components/molecules/Thumbnail/Thumbnail.tsx index dc1760c..902e571 100644 --- a/client/src/components/molecules/Thumbnail/Thumbnail.tsx +++ b/client/src/components/molecules/Thumbnail/Thumbnail.tsx @@ -75,7 +75,7 @@ const Thumbnail: React.FC = () => { {isCrop && } - {thumbnails.map(image => { + {(isCrop ? video.getThumbnails() : thumbnails).map(image => { return ; })} From e6aebde1c99ce8071388ef4ee6cbbaf8a37b79f4 Mon Sep 17 00:00:00 2001 From: SSH1997 Date: Tue, 1 Dec 2020 14:48:13 +0900 Subject: [PATCH 30/34] =?UTF-8?q?[FE]=20:=20[REFACTOR]=20ButtonTypes=20enu?= =?UTF-8?q?m=20=EC=84=A0=EC=96=B8=20=EB=B0=8F=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/organisms/Tools/reducer.tsx | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/client/src/components/organisms/Tools/reducer.tsx b/client/src/components/organisms/Tools/reducer.tsx index 54d5e1d..29062a4 100644 --- a/client/src/components/organisms/Tools/reducer.tsx +++ b/client/src/components/organisms/Tools/reducer.tsx @@ -10,6 +10,12 @@ import { CgMergeHorizontal, CgMergeVertical } from 'react-icons/cg'; import size from '@/theme/sizes'; +export enum ButtonTypes { + crop = 'crop', + videoEffect = 'videoEffect', + ratio = 'ratio', +} + export interface ButtonData { onClicks: (() => void)[]; messages: string[]; @@ -17,8 +23,8 @@ export interface ButtonData { childrens: React.ReactChild[]; } -export interface ButtonDataAction { - type: 'crop' | 'videoEffect' | 'ratio' | null; +interface ButtonDataAction { + type: ButtonTypes | null; payload?: (() => void)[]; } @@ -55,21 +61,21 @@ export const initialData: ButtonData = { export default (state: ButtonData, action: ButtonDataAction): ButtonData => { switch (action.type) { - case 'crop': + case ButtonTypes.crop: return { onClicks: action.payload, messages: cropMessages, type: 'transparent', childrens: cropChildrens, }; - case 'videoEffect': + case ButtonTypes.videoEffect: return { onClicks: action.payload, messages: rotateReverseMessages, type: 'transparent', childrens: rotateReverseChildrens, }; - case 'ratio': + case ButtonTypes.ratio: return { onClicks: action.payload, messages: ratioMessages, From b08db006922a0f20206a0ebe0af2766f85b8d321 Mon Sep 17 00:00:00 2001 From: SSH1997 Date: Tue, 1 Dec 2020 14:49:04 +0900 Subject: [PATCH 31/34] =?UTF-8?q?[FE]=20:=20[REFACTOR]=20method=20?= =?UTF-8?q?=EB=A9=94=EB=AA=A8=EC=9D=B4=EC=A0=9C=EC=9D=B4=EC=85=98=20?= =?UTF-8?q?=EB=B0=8F=20=EB=B6=88=ED=95=84=EC=9A=94=ED=95=9C=20=ED=95=A8?= =?UTF-8?q?=EC=88=98=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/organisms/Tools/Tools.tsx | 68 +++++++++---------- 1 file changed, 32 insertions(+), 36 deletions(-) diff --git a/client/src/components/organisms/Tools/Tools.tsx b/client/src/components/organisms/Tools/Tools.tsx index a7d226d..8304bdc 100644 --- a/client/src/components/organisms/Tools/Tools.tsx +++ b/client/src/components/organisms/Tools/Tools.tsx @@ -1,4 +1,4 @@ -import React, { useState, useReducer } from 'react'; +import React, { useState, useReducer, useCallback, useMemo } from 'react'; import styled, { keyframes } from 'styled-components'; import { shallowEqual, useDispatch, useSelector } from 'react-redux'; @@ -11,10 +11,9 @@ import { pause, moveTo, } from '@/store/currentVideo/actions'; -import { getStartEnd } from '@/store/selectors'; +import { getStartEnd, getPlaying } from '@/store/selectors'; import { cropStart, cropCancel, cropConfirm } from '@/store/actionTypes'; - -import reducer, { initialData, ButtonDataAction } from './reducer'; +import reducer, { initialData, ButtonTypes } from './reducer'; import { getEditToolData, getSubEditToolsData, @@ -126,56 +125,53 @@ const Tools: React.FC = ({ setEdit }) => { dispatchButtonData({ type: null }); }; - const openSubtool = ( - type: 'videoEffect' | 'ratio' | 'crop', - payload: (() => void)[] - ) => { - if (type !== 'crop') dispatch(cropCancel()); + const openSubtool = (type: ButtonTypes, payload: (() => void)[]) => { + if (type !== ButtonTypes.crop) dispatch(cropCancel()); setEdit(UP); setToolType(type); dispatchButtonData({ type, payload }); }; - const handleRotateLeft90Degree = () => webglController.rotateLeft90Degree(); - const handleRotateRight90Degree = () => webglController.rotateRight90Degree(); - const handleReverseUpsideDown = () => webglController.reverseUpsideDown(); - const handleReverseSideToSide = () => webglController.reverseSideToSide(); - const rotateReverseMethods = [ - handleRotateLeft90Degree, - handleRotateRight90Degree, - handleReverseUpsideDown, - handleReverseSideToSide, - ]; - - const handleRatioEnlarge = () => webglController.enlarge(); - const handleRatioReduce = () => webglController.reduce(); - const ratioMethods = [handleRatioEnlarge, handleRatioReduce]; - - const handleCropInsert = () => {}; // TODO: - const handleCropConfirm = () => { + const handleCropManually = useCallback(() => {}, []); // TODO: + const handleCropConfirm = useCallback(() => { dispatch(cropConfirm()); closeSubtool(); - }; - const handleCropCancel = () => { - closeSubtool(); + }, [dispatch]); + const handleCropCancel = useCallback(() => { dispatch(cropCancel()); - }; - const cropMethods = [handleCropInsert, handleCropConfirm, handleCropCancel]; + closeSubtool(); + }, [dispatch]); + + const methods = useMemo( + () => ({ + rotateReverse: [ + webglController.rotateLeft90Degree, + webglController.rotateRight90Degree, + webglController.reverseUpsideDown, + webglController.reverseSideToSide, + ], + ratio: [webglController.enlarge, webglController.reduce], + crop: [handleCropManually, handleCropConfirm, handleCropCancel], + }), + [] + ); const handleRotateReverse = () => - toolType === 'videoEffect' + toolType === ButtonTypes.videoEffect ? closeSubtool() - : openSubtool('videoEffect', rotateReverseMethods); + : openSubtool(ButtonTypes.videoEffect, methods.rotateReverse); const handleRatio = () => - toolType === 'ratio' ? closeSubtool() : openSubtool('ratio', ratioMethods); + toolType === ButtonTypes.ratio + ? closeSubtool() + : openSubtool(ButtonTypes.ratio, methods.ratio); const handleCrop = () => { - if (toolType === 'crop') { + if (toolType === ButtonTypes.crop) { dispatch(cropCancel()); closeSubtool(); } else { - openSubtool('crop', cropMethods); + openSubtool(ButtonTypes.crop, methods.crop); dispatch(cropStart()); } }; From 47b7117ddeefc0249f478efac3dede3268c33860 Mon Sep 17 00:00:00 2001 From: SSH1997 Date: Tue, 1 Dec 2020 15:45:09 +0900 Subject: [PATCH 32/34] =?UTF-8?q?[FE]=20:=20[FEAT]=20#19=20video=20crop=20?= =?UTF-8?q?=EC=8B=9C=20=EC=8D=B8=EB=84=A4=EC=9D=BC=20=EC=9E=AC=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - saga 에서 video.makeThumbnails 호출 -> 최초 호출이 아닐 경우 재 설정된 썸네일 반환 --- client/src/store/crop/actions.ts | 9 +++------ client/src/store/crop/reducer.ts | 4 ++-- client/src/store/crop/sagas.ts | 25 +++++++++++++++++++++++++ client/src/store/sagas.ts | 3 ++- client/src/video/video.tsx | 3 ++- 5 files changed, 34 insertions(+), 10 deletions(-) create mode 100644 client/src/store/crop/sagas.ts diff --git a/client/src/store/crop/actions.ts b/client/src/store/crop/actions.ts index 1c8add8..f2f9dbc 100644 --- a/client/src/store/crop/actions.ts +++ b/client/src/store/crop/actions.ts @@ -1,4 +1,5 @@ import { CROP_START, CROP_CANCEL, CROP_CONFIRM, CROP } from '../actionTypes'; +import { CropAction } from '../currentVideo/actions'; export const cropStart = () => ({ type: typeof CROP_START, @@ -24,12 +25,8 @@ export type CropConfirmAction = { type: typeof CROP_CONFIRM; }; -export type Crop = { - type: typeof CROP; -}; - -export type CropAction = +export type CropStoreAction = | CropStartAction | CropCancelAction | CropConfirmAction - | Crop; + | CropAction; diff --git a/client/src/store/crop/reducer.ts b/client/src/store/crop/reducer.ts index d5d27f6..70270d3 100644 --- a/client/src/store/crop/reducer.ts +++ b/client/src/store/crop/reducer.ts @@ -1,5 +1,5 @@ import { CROP_START, CROP_CANCEL, CROP_CONFIRM, CROP } from '../actionTypes'; -import { CropAction } from './actions'; +import { CropStoreAction } from './actions'; export interface CropState { isCrop: boolean; @@ -13,7 +13,7 @@ const initialState: CropState = { export default ( state: CropState = initialState, - action: CropAction + action: CropStoreAction ): CropState => { switch (action.type) { case CROP_START: diff --git a/client/src/store/crop/sagas.ts b/client/src/store/crop/sagas.ts new file mode 100644 index 0000000..e12cbb3 --- /dev/null +++ b/client/src/store/crop/sagas.ts @@ -0,0 +1,25 @@ +import { put, call, takeLatest } from 'redux-saga/effects'; + +import video from '@/video/video'; +import webglController from '@/webgl/webglController'; +import { setThumbnails } from '../currentVideo/actions'; +import { CROP, error } from '../actionTypes'; + +function* updateThumbnails(action) { + try { + const thumbnails: string[] = yield call( + video.makeThumbnails, + action.payload.start, + action.payload.end + ); + yield call(webglController.main); + yield put(setThumbnails(thumbnails)); + } catch (err) { + console.log(err); + yield put(error()); + } +} + +export default function* CropThumbnail() { + yield takeLatest(CROP, updateThumbnails); +} diff --git a/client/src/store/sagas.ts b/client/src/store/sagas.ts index f58be4a..7fb727a 100644 --- a/client/src/store/sagas.ts +++ b/client/src/store/sagas.ts @@ -1,5 +1,6 @@ import { all, call, takeEvery } from 'redux-saga/effects'; import { watchSetVideo, deleteSrc } from '@/store/originalVideo/sagas'; +import CropThumbnail from '@/store/crop/sagas'; import { RESET } from './actionTypes'; function* watchReset() { @@ -7,5 +8,5 @@ function* watchReset() { } export default function* rootSaga() { - yield all([watchSetVideo(), watchReset()]); + yield all([watchSetVideo(), watchReset(), CropThumbnail()]); } diff --git a/client/src/video/video.tsx b/client/src/video/video.tsx index 43ae77e..5f3bbb0 100644 --- a/client/src/video/video.tsx +++ b/client/src/video/video.tsx @@ -65,7 +65,8 @@ class Video { secs -= gap; images[count] = image; } - this.thumbnails = images; + if (start === 0 && end === this.video.duration) + this.thumbnails = images; resolve(images); })(); } catch (err) { From a8ce1c15e1481eb80682584d2d53ecc7e395770f Mon Sep 17 00:00:00 2001 From: SSH1997 Date: Tue, 1 Dec 2020 15:46:17 +0900 Subject: [PATCH 33/34] [FE] : [FIX] currentVideo Store CropAction export --- client/src/store/currentVideo/actions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/store/currentVideo/actions.ts b/client/src/store/currentVideo/actions.ts index 390a2c9..738ac53 100644 --- a/client/src/store/currentVideo/actions.ts +++ b/client/src/store/currentVideo/actions.ts @@ -51,7 +51,7 @@ export type SetThumbnailsAction = { }; }; -type CropAction = { +export type CropAction = { type: typeof CROP; payload: { start: number; From 3e409357251294b43632e48ec9d3a51565bb667e Mon Sep 17 00:00:00 2001 From: SSH1997 Date: Tue, 1 Dec 2020 15:46:54 +0900 Subject: [PATCH 34/34] =?UTF-8?q?[FE]=20:=20[FEAT]=20TimeZone=20=ED=91=9C?= =?UTF-8?q?=EC=8B=9C=20logic=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 현재 설정되어있는 start / end 에 맞추어 시간 표시 --- .../molecules/TimeZone/TimeZone.tsx | 19 ++++++++++++++----- client/src/store/selectors.ts | 9 +++++++++ 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/client/src/components/molecules/TimeZone/TimeZone.tsx b/client/src/components/molecules/TimeZone/TimeZone.tsx index 83e7be1..ea1feed 100644 --- a/client/src/components/molecules/TimeZone/TimeZone.tsx +++ b/client/src/components/molecules/TimeZone/TimeZone.tsx @@ -1,8 +1,13 @@ import React from 'react'; -import { useSelector } from 'react-redux'; +import { shallowEqual, useSelector } from 'react-redux'; import styled from 'styled-components'; -import { getDuration } from '@/store/selectors'; +import { + getDuration, + getStartEnd, + getIsCrop, + getIsCropAndDuration, +} from '@/store/selectors'; import TimeText from '@/components/atoms/TimeText'; import color from '@/theme/colors'; @@ -23,7 +28,8 @@ const InnerDiv = styled.div` const PART_COUNT = 6; -const getTimes = (duration: number): number[] => { +const getTimes = ({ start, end }): number[] => { + const duration: number = end - start; if (!duration) return []; const times: number[] = []; @@ -40,8 +46,11 @@ const getTimes = (duration: number): number[] => { }; const TimeZone: React.FC = () => { - const duration: number = Math.round(useSelector(getDuration)); - const times: number[] = getTimes(duration); + const { isCrop, duration } = useSelector(getIsCropAndDuration, shallowEqual); + const { start, end } = useSelector(getStartEnd); + + const parameter = isCrop ? { start: 0, end: duration } : { start, end }; + const times: number[] = getTimes(parameter); return ( diff --git a/client/src/store/selectors.ts b/client/src/store/selectors.ts index fd48a05..934baf4 100644 --- a/client/src/store/selectors.ts +++ b/client/src/store/selectors.ts @@ -40,3 +40,12 @@ export const getCropState = (state: RootState) => { isCropConfirm, }; }; +export const getIsCropAndDuration = (state: RootState) => { + const { isCrop } = state.crop; + const { length } = state.originalVideo; + + return { + isCrop, + duration: length, + }; +};