From 10bd732c0b0f91ae669c5e4d135b62acfee5185f Mon Sep 17 00:00:00 2001 From: ukkodeveloper Date: Tue, 10 Oct 2023 11:23:57 +0900 Subject: [PATCH 01/95] =?UTF-8?q?feat:=20waveScrubber=20ui=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../songs/components/VoteInterface.tsx | 2 + .../youtube/components/ScrubberProgress.tsx | 42 +++++ .../youtube/components/WaveScrubber.tsx | 150 ++++++++++++++++++ 3 files changed, 194 insertions(+) create mode 100644 frontend/src/features/youtube/components/ScrubberProgress.tsx create mode 100644 frontend/src/features/youtube/components/WaveScrubber.tsx diff --git a/frontend/src/features/songs/components/VoteInterface.tsx b/frontend/src/features/songs/components/VoteInterface.tsx index 8550ba630..1484f14eb 100644 --- a/frontend/src/features/songs/components/VoteInterface.tsx +++ b/frontend/src/features/songs/components/VoteInterface.tsx @@ -2,6 +2,7 @@ import { styled } from 'styled-components'; import { useAuthContext } from '@/features/auth/components/AuthProvider'; import useVoteInterfaceContext from '@/features/songs/hooks/useVoteInterfaceContext'; import VideoSlider from '@/features/youtube/components/VideoSlider'; +import WaveScrubber from '@/features/youtube/components/WaveScrubber'; import useVideoPlayerContext from '@/features/youtube/hooks/useVideoPlayerContext'; import useModal from '@/shared/components/Modal/hooks/useModal'; import Modal from '@/shared/components/Modal/Modal'; @@ -45,6 +46,7 @@ const VoteInterface = () => { + 등록 diff --git a/frontend/src/features/youtube/components/ScrubberProgress.tsx b/frontend/src/features/youtube/components/ScrubberProgress.tsx new file mode 100644 index 000000000..60fa4dfaf --- /dev/null +++ b/frontend/src/features/youtube/components/ScrubberProgress.tsx @@ -0,0 +1,42 @@ +import styled, { keyframes } from 'styled-components'; + +interface ScrubberProgressProps { + prevTime: number; + interval: number; + isPaused: boolean; +} + +const ScrubberProgress = ({ prevTime, interval, isPaused }: ScrubberProgressProps) => { + return ; +}; + +export default ScrubberProgress; + +const fillAnimation = keyframes` + 0% { + background-position: right; + } + 100% { + background-position: left; + } +`; + +const PlayingBoxBackground = styled.div<{ prevTime: number; interval: number; isPaused: boolean }>` + z-index: 0; + width: 150px; + height: 50px; + border-radius: 5px; + + position: absolute; // 화면에 고정된 위치 + left: 50%; // 뷰포트의 중앙에 위치 + transform: translateX(-50%); // 뷰포트 중앙 + + background: linear-gradient(to left, transparent 50%, pink 50%) right; + background-size: ${({ interval }) => 200 + (30 - interval)}%; + transition: 10s linear; + animation: ${fillAnimation} ${({ interval }) => interval}s linear infinite; + animation-play-state: ${({ isPaused }) => (isPaused ? 'paused' : 'running')}; + animation-delay: ${({ prevTime }) => -prevTime}s; + + pointer-events: none; +`; diff --git a/frontend/src/features/youtube/components/WaveScrubber.tsx b/frontend/src/features/youtube/components/WaveScrubber.tsx new file mode 100644 index 000000000..2639d6512 --- /dev/null +++ b/frontend/src/features/youtube/components/WaveScrubber.tsx @@ -0,0 +1,150 @@ +import { useEffect, useRef, useState } from 'react'; +import styled from 'styled-components'; +import useVoteInterfaceContext from '@/features/songs/hooks/useVoteInterfaceContext'; +import ScrubberProgress from '@/features/youtube/components/ScrubberProgress'; +import useVideoPlayerContext from '@/features/youtube/hooks/useVideoPlayerContext'; +import { secondsToMinSec } from '@/shared/utils/convertTime'; + +export function useDebounce(value: T, delay?: number): T { + const [debouncedValue, setDebouncedValue] = useState(value); + + useEffect(() => { + const timer = setTimeout(() => setDebouncedValue(value), delay || 500); + + return () => { + clearTimeout(timer); + }; + }, [value, delay]); + + return debouncedValue; +} + +const WaveScrubber = () => { + const { interval, partStartTime, videoLength, updatePartStartTime } = useVoteInterfaceContext(); + const { videoPlayer, playerState } = useVideoPlayerContext(); + + const temptKey = useRef(0); + const debouncedValue = useDebounce(partStartTime, 300); + + const soundLengthCanControl = videoLength - interval; + + useEffect(() => { + videoPlayer.current?.seekTo(partStartTime, true); + }, [debouncedValue]); + + const pairBars = Array.from({ length: soundLengthCanControl }, () => ( + <> + + + + )); + + return ( + + { + if (playerState !== 1) { + videoPlayer?.current?.playVideo(); + } + const { scrollWidth, scrollLeft } = e.currentTarget; + const unit = (scrollWidth - 350) / soundLengthCanControl; //350: 전체 width + const timeScrolled = Math.floor(scrollLeft / unit); + + if (timeScrolled >= 0 && timeScrolled <= soundLengthCanControl) { + const { minute, second } = secondsToMinSec(timeScrolled); + updatePartStartTime('minute', minute); + updatePartStartTime('second', second); + temptKey.current += temptKey.current + 1; + } + }} + onClick={() => { + videoPlayer?.current?.playVideo(); + }} + > + {pairBars} + + + + {playerState === 1 && ( + + )} + + ); +}; + +export default WaveScrubber; + +const Flex = styled.div` + z-index: 3; + display: flex; + column-gap: 8px; + background-color: transparent; + align-items: center; + overflow-x: scroll; + width: 100%; + height: 100px; + + border: 1px solid darkorange; + + padding: 0 calc((100% - 150px) / 2); +`; + +const LongBar = styled.div` + z-index: 2; + height: 20px; + width: 4px; + border-radius: 5px; + background-color: grey; + left: calc(50%); +`; + +const ShortBar = styled.div` + z-index: 2; + height: 15px; + width: 4px; + border-radius: 5px; + background-color: grey; +`; + +const PlayingBox = styled.div` + z-index: 1; + width: 150px; + height: 50px; + + border: none; + border-radius: 4px; + + box-shadow: 0 0 0 3px inset grey; + position: absolute; // 화면에 고정된 위치 + left: 50%; // 뷰포트의 중앙에 위치 + transform: translateX(-50%); // 뷰포트 중앙을 기준으로 수평 정렬 + + pointer-events: none; +`; + +const Container = styled.div` + width: 100%; + background-color: black; + border: 2px solid greenyellow; + margin: auto; + + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +`; From 7d5539262bcbf3cf1e50c08557939c57fe63659a Mon Sep 17 00:00:00 2001 From: ukkodeveloper Date: Tue, 10 Oct 2023 11:34:55 +0900 Subject: [PATCH 02/95] =?UTF-8?q?refactor:=20wave=20ui=20=EB=B3=84?= =?UTF-8?q?=EB=8F=84=EC=9D=98=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8?= =?UTF-8?q?=EB=A1=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../features/youtube/components/SoundWave.tsx | 33 +++++++++++ .../youtube/components/WaveScrubber.tsx | 59 ++++--------------- frontend/src/router.tsx | 6 +- 3 files changed, 44 insertions(+), 54 deletions(-) create mode 100644 frontend/src/features/youtube/components/SoundWave.tsx diff --git a/frontend/src/features/youtube/components/SoundWave.tsx b/frontend/src/features/youtube/components/SoundWave.tsx new file mode 100644 index 000000000..4f3496501 --- /dev/null +++ b/frontend/src/features/youtube/components/SoundWave.tsx @@ -0,0 +1,33 @@ +import styled from 'styled-components'; + +interface SoundWaveProps { + waveLength: number; +} + +const SoundWave = ({ waveLength }: SoundWaveProps) => { + return Array.from({ length: waveLength }, () => ( + <> + + + + )); +}; + +export default SoundWave; + +const LongBar = styled.div` + z-index: 2; + height: 20px; + width: 4px; + border-radius: 5px; + background-color: grey; + left: calc(50%); +`; + +const ShortBar = styled.div` + z-index: 2; + height: 15px; + width: 4px; + border-radius: 5px; + background-color: grey; +`; diff --git a/frontend/src/features/youtube/components/WaveScrubber.tsx b/frontend/src/features/youtube/components/WaveScrubber.tsx index 2639d6512..82a377f3f 100644 --- a/frontend/src/features/youtube/components/WaveScrubber.tsx +++ b/frontend/src/features/youtube/components/WaveScrubber.tsx @@ -2,6 +2,7 @@ import { useEffect, useRef, useState } from 'react'; import styled from 'styled-components'; import useVoteInterfaceContext from '@/features/songs/hooks/useVoteInterfaceContext'; import ScrubberProgress from '@/features/youtube/components/ScrubberProgress'; +import SoundWave from '@/features/youtube/components/SoundWave'; import useVideoPlayerContext from '@/features/youtube/hooks/useVideoPlayerContext'; import { secondsToMinSec } from '@/shared/utils/convertTime'; @@ -24,56 +25,33 @@ const WaveScrubber = () => { const { videoPlayer, playerState } = useVideoPlayerContext(); const temptKey = useRef(0); - const debouncedValue = useDebounce(partStartTime, 300); + const debouncedValue = useDebounce(partStartTime, 1000); - const soundLengthCanControl = videoLength - interval; + const maxPartStartTime = videoLength - interval; useEffect(() => { videoPlayer.current?.seekTo(partStartTime, true); + videoPlayer.current?.playVideo(); }, [debouncedValue]); - const pairBars = Array.from({ length: soundLengthCanControl }, () => ( - <> - - - - )); - return ( { - if (playerState !== 1) { - videoPlayer?.current?.playVideo(); - } const { scrollWidth, scrollLeft } = e.currentTarget; - const unit = (scrollWidth - 350) / soundLengthCanControl; //350: 전체 width + const unit = (scrollWidth - 350) / maxPartStartTime; //350: 전체 width const timeScrolled = Math.floor(scrollLeft / unit); - if (timeScrolled >= 0 && timeScrolled <= soundLengthCanControl) { + if (timeScrolled >= 0 && timeScrolled <= maxPartStartTime) { const { minute, second } = secondsToMinSec(timeScrolled); updatePartStartTime('minute', minute); updatePartStartTime('second', second); temptKey.current += temptKey.current + 1; } }} - onClick={() => { - videoPlayer?.current?.playVideo(); - }} > - {pairBars} + - {playerState === 1 && ( - - - ), + element: , }, { path: `${ROUTE_PATH.SONG_DETAILS}/:id/:genre`, From c973da29525e2990b381bd24d714aad05af7afaa Mon Sep 17 00:00:00 2001 From: ukkodeveloper Date: Tue, 10 Oct 2023 13:14:25 +0900 Subject: [PATCH 03/95] =?UTF-8?q?refactor:=20useDebounceEffect=20deps=20?= =?UTF-8?q?=EC=9D=B8=EC=9E=90=20=ED=83=80=EC=9E=85=20=EB=B0=B0=EC=97=B4?= =?UTF-8?q?=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/shared/hooks/useDebounceEffect.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/frontend/src/shared/hooks/useDebounceEffect.ts b/frontend/src/shared/hooks/useDebounceEffect.ts index 1fcb6b4ab..9136580c8 100644 --- a/frontend/src/shared/hooks/useDebounceEffect.ts +++ b/frontend/src/shared/hooks/useDebounceEffect.ts @@ -1,7 +1,11 @@ import { useCallback, useEffect } from 'react'; -const useDebounceEffect = (fn: () => void, deps: D, delay: number = 500) => { - const callback = useCallback(fn, [deps]); +const useDebounceEffect = >( + fn: () => void, + deps: D, + delay: number = 500 +) => { + const callback = useCallback(fn, [...deps]); useEffect(() => { const timer = window.setTimeout(() => { From 6be7ada5c8b9663d506cd043163fa94051d7a6cc Mon Sep 17 00:00:00 2001 From: ukkodeveloper Date: Tue, 10 Oct 2023 13:15:09 +0900 Subject: [PATCH 04/95] =?UTF-8?q?refactor:=20WaveScrubber=20=EC=9D=B4?= =?UTF-8?q?=EB=B2=A4=ED=8A=B8=20=ED=95=B8=EB=93=A4=EB=9F=AC=20=EB=93=B1=20?= =?UTF-8?q?=EB=A6=AC=ED=8C=A9=ED=84=B0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../youtube/components/WaveScrubber.tsx | 62 +++++++------------ 1 file changed, 24 insertions(+), 38 deletions(-) diff --git a/frontend/src/features/youtube/components/WaveScrubber.tsx b/frontend/src/features/youtube/components/WaveScrubber.tsx index 82a377f3f..1e12b3121 100644 --- a/frontend/src/features/youtube/components/WaveScrubber.tsx +++ b/frontend/src/features/youtube/components/WaveScrubber.tsx @@ -1,59 +1,45 @@ -import { useEffect, useRef, useState } from 'react'; +import { useRef } from 'react'; import styled from 'styled-components'; import useVoteInterfaceContext from '@/features/songs/hooks/useVoteInterfaceContext'; import ScrubberProgress from '@/features/youtube/components/ScrubberProgress'; import SoundWave from '@/features/youtube/components/SoundWave'; import useVideoPlayerContext from '@/features/youtube/hooks/useVideoPlayerContext'; +import useDebounceEffect from '@/shared/hooks/useDebounceEffect'; import { secondsToMinSec } from '@/shared/utils/convertTime'; +import PlayerState = YT.PlayerState; -export function useDebounce(value: T, delay?: number): T { - const [debouncedValue, setDebouncedValue] = useState(value); - - useEffect(() => { - const timer = setTimeout(() => setDebouncedValue(value), delay || 500); - - return () => { - clearTimeout(timer); - }; - }, [value, delay]); - - return debouncedValue; -} - +const PROGRESS_WIDTH = 350; const WaveScrubber = () => { const { interval, partStartTime, videoLength, updatePartStartTime } = useVoteInterfaceContext(); - const { videoPlayer, playerState } = useVideoPlayerContext(); - + const { playerState, seekTo } = useVideoPlayerContext(); const temptKey = useRef(0); - const debouncedValue = useDebounce(partStartTime, 1000); - const maxPartStartTime = videoLength - interval; + const seekAndPlay = () => { + seekTo(partStartTime); + }; + + const changePartStartTime: React.UIEventHandler = (e) => { + const { scrollWidth, scrollLeft } = e.currentTarget; + const unit = (scrollWidth - PROGRESS_WIDTH) / maxPartStartTime; + const partStartTimeToChange = Math.floor(scrollLeft / unit); - useEffect(() => { - videoPlayer.current?.seekTo(partStartTime, true); - videoPlayer.current?.playVideo(); - }, [debouncedValue]); + if (partStartTimeToChange >= 0 && partStartTimeToChange <= maxPartStartTime) { + const { minute, second } = secondsToMinSec(partStartTimeToChange); + updatePartStartTime('minute', minute); + updatePartStartTime('second', second); + temptKey.current += temptKey.current + 1; + } + }; + + useDebounceEffect<[number, number]>(seekAndPlay, [partStartTime, interval], 300); return ( - { - const { scrollWidth, scrollLeft } = e.currentTarget; - const unit = (scrollWidth - 350) / maxPartStartTime; //350: 전체 width - const timeScrolled = Math.floor(scrollLeft / unit); - - if (timeScrolled >= 0 && timeScrolled <= maxPartStartTime) { - const { minute, second } = secondsToMinSec(timeScrolled); - updatePartStartTime('minute', minute); - updatePartStartTime('second', second); - temptKey.current += temptKey.current + 1; - } - }} - > + - {playerState === 1 && ( + {playerState === PlayerState.PLAYING && ( Date: Tue, 10 Oct 2023 14:35:41 +0900 Subject: [PATCH 05/95] =?UTF-8?q?fix:=20PlayerState=20enum=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/features/youtube/components/WaveScrubber.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/frontend/src/features/youtube/components/WaveScrubber.tsx b/frontend/src/features/youtube/components/WaveScrubber.tsx index 1e12b3121..af2aee556 100644 --- a/frontend/src/features/youtube/components/WaveScrubber.tsx +++ b/frontend/src/features/youtube/components/WaveScrubber.tsx @@ -6,7 +6,6 @@ import SoundWave from '@/features/youtube/components/SoundWave'; import useVideoPlayerContext from '@/features/youtube/hooks/useVideoPlayerContext'; import useDebounceEffect from '@/shared/hooks/useDebounceEffect'; import { secondsToMinSec } from '@/shared/utils/convertTime'; -import PlayerState = YT.PlayerState; const PROGRESS_WIDTH = 350; const WaveScrubber = () => { @@ -39,7 +38,7 @@ const WaveScrubber = () => { - {playerState === PlayerState.PLAYING && ( + {playerState === 1 && ( Date: Tue, 10 Oct 2023 14:37:48 +0900 Subject: [PATCH 06/95] =?UTF-8?q?feat:=20=ED=82=AC=EB=A7=81=ED=8C=8C?= =?UTF-8?q?=ED=8A=B8=20interval=205~15=EC=B4=88=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/PartIntervalController.tsx | 61 +++++++++++++++++++ .../songs/components/VoteInterface.tsx | 5 +- .../components/VoteInterfaceProvider.tsx | 32 ++++++++-- .../features/songs/constants/partInterval.ts | 2 + frontend/src/shared/types/killingPart.ts | 4 +- 5 files changed, 95 insertions(+), 9 deletions(-) create mode 100644 frontend/src/features/songs/components/PartIntervalController.tsx create mode 100644 frontend/src/features/songs/constants/partInterval.ts diff --git a/frontend/src/features/songs/components/PartIntervalController.tsx b/frontend/src/features/songs/components/PartIntervalController.tsx new file mode 100644 index 000000000..c13a47f73 --- /dev/null +++ b/frontend/src/features/songs/components/PartIntervalController.tsx @@ -0,0 +1,61 @@ +import { styled } from 'styled-components'; +import useVoteInterfaceContext from '@/features/songs/hooks/useVoteInterfaceContext'; + +const PartIntervalController = () => { + const { interval, plusPartInterval, minusPartInterval } = useVoteInterfaceContext(); + + return ( + + - + {`${interval}초`} + + + + ); +}; + +export default PartIntervalController; + +const Container = styled.div` + display: flex; + flex-direction: row; + gap: 20px; + justify-content: center; + + width: 100%; +`; + +const ControlButton = styled.button` + flex: 1; + + min-width: 50px; + + margin: 0; + padding: 0; + + font-weight: '500'; + color: ${({ theme: { color } }) => color.white}; + text-align: center; + line-height: 1.8; + + background-color: ${({ theme: { color } }) => color.secondary}; + border: none; + border-radius: 10px; +`; + +const IntervalItem = styled.p` + flex: 1; + + min-width: 50px; + + margin: 0; + + text-align: center; + line-height: 1.8; + + font-weight: '700'; + color: ${({ theme: { color } }) => color.black}; + + background-color: ${({ theme: { color } }) => color.white}; + border: none; + border-radius: 10px; +`; diff --git a/frontend/src/features/songs/components/VoteInterface.tsx b/frontend/src/features/songs/components/VoteInterface.tsx index 1484f14eb..3f793e9c7 100644 --- a/frontend/src/features/songs/components/VoteInterface.tsx +++ b/frontend/src/features/songs/components/VoteInterface.tsx @@ -1,5 +1,6 @@ import { styled } from 'styled-components'; import { useAuthContext } from '@/features/auth/components/AuthProvider'; +import PartIntervalController from '@/features/songs/components/PartIntervalController'; import useVoteInterfaceContext from '@/features/songs/hooks/useVoteInterfaceContext'; import VideoSlider from '@/features/youtube/components/VideoSlider'; import WaveScrubber from '@/features/youtube/components/WaveScrubber'; @@ -11,7 +12,6 @@ import useToastContext from '@/shared/components/Toast/hooks/useToastContext'; import { toPlayingTimeText } from '@/shared/utils/convertTime'; import copyClipboard from '@/shared/utils/copyClipBoard'; import { usePostKillingPart } from '../remotes/usePostKillingPart'; -import KillingPartToggleGroup from './KillingPartToggleGroup'; const VoteInterface = () => { const { showToast } = useToastContext(); @@ -43,7 +43,8 @@ const VoteInterface = () => { 같은 파트에 대한 여러 번의 등록은 한 번의 등록으로 처리됩니다. - + + diff --git a/frontend/src/features/songs/components/VoteInterfaceProvider.tsx b/frontend/src/features/songs/components/VoteInterfaceProvider.tsx index 7f1972355..bace7e7fc 100644 --- a/frontend/src/features/songs/components/VoteInterfaceProvider.tsx +++ b/frontend/src/features/songs/components/VoteInterfaceProvider.tsx @@ -1,14 +1,16 @@ import { createContext, useEffect, useState } from 'react'; +import { MAX_PART_INTERVAL, MIN_PART_INTERVAL } from '@/features/songs/constants/partInterval'; import useVideoPlayerContext from '@/features/youtube/hooks/useVideoPlayerContext'; -import type { KillingPartInterval } from '../types/KillingPartToggleGroup.type'; import type { PropsWithChildren } from 'react'; interface VoteInterfaceContextProps extends VoteInterfaceProviderProps { partStartTime: number; - interval: KillingPartInterval; + interval: number; // NOTE: Why both setState and eventHandler have same naming convention? updatePartStartTime: (timeUnit: string, value: number) => void; updateKillingPartInterval: React.MouseEventHandler; + plusPartInterval: () => void; + minusPartInterval: () => void; } export const VoteInterfaceContext = createContext(null); @@ -25,12 +27,12 @@ export const VoteInterfaceProvider = ({ songId, songVideoId, }: PropsWithChildren) => { - const [interval, setInterval] = useState(10); + const [interval, setInterval] = useState(10); const [partStartTime, setPartStartTime] = useState(0); const { videoPlayer } = useVideoPlayerContext(); const updateKillingPartInterval: React.MouseEventHandler = (e) => { - const newInterval = Number(e.currentTarget.dataset['interval']) as KillingPartInterval; + const newInterval = Number(e.currentTarget.dataset['interval']) as number; const partEndTime = partStartTime + newInterval; if (partEndTime > videoLength) { @@ -41,6 +43,26 @@ export const VoteInterfaceProvider = ({ setInterval(newInterval); }; + const plusPartInterval = () => { + setInterval((prevInterval) => { + const currentInterval = prevInterval + 1; + if (currentInterval >= MIN_PART_INTERVAL && currentInterval <= MAX_PART_INTERVAL) { + return currentInterval; + } + return prevInterval; + }); + }; + + const minusPartInterval = () => { + setInterval((prevInterval) => { + const currentInterval = prevInterval - 1; + if (currentInterval >= MIN_PART_INTERVAL && currentInterval <= MAX_PART_INTERVAL) { + return currentInterval; + } + return prevInterval; + }); + }; + const updatePartStartTime = (timeUnit: string, value: number) => { if (timeUnit === 'minute') { setPartStartTime((prev) => { @@ -79,6 +101,8 @@ export const VoteInterfaceProvider = ({ songVideoId, updatePartStartTime, updateKillingPartInterval, + plusPartInterval, + minusPartInterval, }} > {children} diff --git a/frontend/src/features/songs/constants/partInterval.ts b/frontend/src/features/songs/constants/partInterval.ts new file mode 100644 index 000000000..b57126bc4 --- /dev/null +++ b/frontend/src/features/songs/constants/partInterval.ts @@ -0,0 +1,2 @@ +export const MAX_PART_INTERVAL = 15; +export const MIN_PART_INTERVAL = 5; diff --git a/frontend/src/shared/types/killingPart.ts b/frontend/src/shared/types/killingPart.ts index 14aced5a6..828c58c88 100644 --- a/frontend/src/shared/types/killingPart.ts +++ b/frontend/src/shared/types/killingPart.ts @@ -1,8 +1,6 @@ -import type { KillingPartInterval } from '@/features/songs/types/KillingPartToggleGroup.type'; - export type PartVideoUrl = `https://youtu.be/${string}?start=${number}&end=${number}`; export interface KillingPartPostRequest { startSecond: number; - length: KillingPartInterval; + length: number; } From 913e23bd7671d4615f279ce8d045c440c74350d1 Mon Sep 17 00:00:00 2001 From: ukkodeveloper Date: Tue, 10 Oct 2023 15:07:24 +0900 Subject: [PATCH 07/95] =?UTF-8?q?design:=20=EC=8A=AC=EB=9D=BC=EC=9D=B4?= =?UTF-8?q?=EB=8D=94=20=EB=94=94=EC=9E=90=EC=9D=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../features/youtube/components/ScrubberProgress.tsx | 2 +- .../src/features/youtube/components/SoundWave.tsx | 6 +++--- .../src/features/youtube/components/WaveScrubber.tsx | 11 +++++------ 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/frontend/src/features/youtube/components/ScrubberProgress.tsx b/frontend/src/features/youtube/components/ScrubberProgress.tsx index 60fa4dfaf..02d81d1bc 100644 --- a/frontend/src/features/youtube/components/ScrubberProgress.tsx +++ b/frontend/src/features/youtube/components/ScrubberProgress.tsx @@ -31,7 +31,7 @@ const PlayingBoxBackground = styled.div<{ prevTime: number; interval: number; is left: 50%; // 뷰포트의 중앙에 위치 transform: translateX(-50%); // 뷰포트 중앙 - background: linear-gradient(to left, transparent 50%, pink 50%) right; + background: linear-gradient(to left, transparent 50%, pink 50%); background-size: ${({ interval }) => 200 + (30 - interval)}%; transition: 10s linear; animation: ${fillAnimation} ${({ interval }) => interval}s linear infinite; diff --git a/frontend/src/features/youtube/components/SoundWave.tsx b/frontend/src/features/youtube/components/SoundWave.tsx index 4f3496501..e0c692153 100644 --- a/frontend/src/features/youtube/components/SoundWave.tsx +++ b/frontend/src/features/youtube/components/SoundWave.tsx @@ -17,10 +17,10 @@ export default SoundWave; const LongBar = styled.div` z-index: 2; - height: 20px; + height: 24px; width: 4px; border-radius: 5px; - background-color: grey; + background-color: ${({ theme: { color } }) => color.white}; left: calc(50%); `; @@ -29,5 +29,5 @@ const ShortBar = styled.div` height: 15px; width: 4px; border-radius: 5px; - background-color: grey; + background-color: ${({ theme: { color } }) => color.white}; `; diff --git a/frontend/src/features/youtube/components/WaveScrubber.tsx b/frontend/src/features/youtube/components/WaveScrubber.tsx index af2aee556..9f4b0926f 100644 --- a/frontend/src/features/youtube/components/WaveScrubber.tsx +++ b/frontend/src/features/youtube/components/WaveScrubber.tsx @@ -62,8 +62,6 @@ const Flex = styled.div` width: 100%; height: 100px; - border: 1px solid darkorange; - padding: 0 calc((100% - 150px) / 2); `; @@ -72,10 +70,10 @@ const PlayingBox = styled.div` width: 150px; height: 50px; - border: none; + border: transparent; border-radius: 4px; - box-shadow: 0 0 0 3px inset grey; + box-shadow: 0 0 0 2px inset ${({ theme: { color } }) => color.white}; position: absolute; left: 50%; transform: translateX(-50%); @@ -85,9 +83,10 @@ const PlayingBox = styled.div` const Container = styled.div` width: 100%; - background-color: black; - border: 2px solid greenyellow; + background-color: ${({ theme: { color } }) => color.secondary}; + margin: auto; + border-radius: 8px; display: flex; flex-direction: column; From f08767c00498fd9c79feb9f904149197042c7685 Mon Sep 17 00:00:00 2001 From: ukkodeveloper Date: Tue, 10 Oct 2023 16:10:19 +0900 Subject: [PATCH 08/95] =?UTF-8?q?design:=20ios=20=ED=99=98=EA=B2=BD?= =?UTF-8?q?=EC=9D=84=20=EA=B3=A0=EB=A0=A4=ED=95=98=EC=97=AC=20100vh=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9=EC=9D=84=20=EC=9C=84=ED=95=B4=20layout=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/shared/components/Layout/Layout.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/frontend/src/shared/components/Layout/Layout.tsx b/frontend/src/shared/components/Layout/Layout.tsx index a0d6aaf7a..08011be38 100644 --- a/frontend/src/shared/components/Layout/Layout.tsx +++ b/frontend/src/shared/components/Layout/Layout.tsx @@ -39,4 +39,8 @@ const LayoutContainer = styled.main` @media (max-width: ${({ theme }) => theme.breakPoints.xxs}) { padding: 0 16px; } + + @supports (-webkit-appearance: none) and (stroke-color: transparent) { + min-height: -webkit-fill-available; + } `; From b2af6e2f6ea8d3327bffb032f9fa5fc23d87cf9c Mon Sep 17 00:00:00 2001 From: ukkodeveloper Date: Tue, 10 Oct 2023 16:10:51 +0900 Subject: [PATCH 09/95] =?UTF-8?q?design:=20Thumbnail=20size=20sm=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/features/songs/components/Thumbnail.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/frontend/src/features/songs/components/Thumbnail.tsx b/frontend/src/features/songs/components/Thumbnail.tsx index 9a90e106a..fd52768c9 100644 --- a/frontend/src/features/songs/components/Thumbnail.tsx +++ b/frontend/src/features/songs/components/Thumbnail.tsx @@ -28,6 +28,10 @@ const Wrapper = styled.div<{ $size: Size; $borderRadius: number }>` `; const SIZE_VARIANTS = { + sm: css` + width: 50px; + height: 50px; + `, md: css` width: 60px; height: 60px; From 3e58646f28b5e0df5dbfe8d4564c86a70ed5cc1c Mon Sep 17 00:00:00 2001 From: ukkodeveloper Date: Tue, 10 Oct 2023 16:11:38 +0900 Subject: [PATCH 10/95] =?UTF-8?q?design:=20Collecting=20Page=20=EC=A0=84?= =?UTF-8?q?=EB=B0=98=EC=A0=81=EC=9D=B8=20=EB=94=94=EC=9E=90=EC=9D=B8=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/PartIntervalController.tsx | 9 ++- .../songs/components/VoteInterface.tsx | 18 +++--- .../youtube/components/WaveScrubber.tsx | 2 +- frontend/src/pages/PartCollectingPage.tsx | 55 ++++++++++++------- 4 files changed, 48 insertions(+), 36 deletions(-) diff --git a/frontend/src/features/songs/components/PartIntervalController.tsx b/frontend/src/features/songs/components/PartIntervalController.tsx index c13a47f73..4be383fc7 100644 --- a/frontend/src/features/songs/components/PartIntervalController.tsx +++ b/frontend/src/features/songs/components/PartIntervalController.tsx @@ -7,7 +7,7 @@ const PartIntervalController = () => { return ( - - {`${interval}초`} + {`${interval} 초`} + ); @@ -30,9 +30,8 @@ const ControlButton = styled.button` min-width: 50px; margin: 0; - padding: 0; - - font-weight: '500'; + padding: 4px 11px; + font-weight: '700'; color: ${({ theme: { color } }) => color.white}; text-align: center; line-height: 1.8; @@ -46,7 +45,7 @@ const IntervalItem = styled.p` flex: 1; min-width: 50px; - + padding: 4px 11px; margin: 0; text-align: center; diff --git a/frontend/src/features/songs/components/VoteInterface.tsx b/frontend/src/features/songs/components/VoteInterface.tsx index 3f793e9c7..06404ffb5 100644 --- a/frontend/src/features/songs/components/VoteInterface.tsx +++ b/frontend/src/features/songs/components/VoteInterface.tsx @@ -2,7 +2,6 @@ import { styled } from 'styled-components'; import { useAuthContext } from '@/features/auth/components/AuthProvider'; import PartIntervalController from '@/features/songs/components/PartIntervalController'; import useVoteInterfaceContext from '@/features/songs/hooks/useVoteInterfaceContext'; -import VideoSlider from '@/features/youtube/components/VideoSlider'; import WaveScrubber from '@/features/youtube/components/WaveScrubber'; import useVideoPlayerContext from '@/features/youtube/hooks/useVideoPlayerContext'; import useModal from '@/shared/components/Modal/hooks/useModal'; @@ -39,14 +38,13 @@ const VoteInterface = () => { return ( - 당신의 킬링파트를 등록하세요 + 나만의 파트 저장하기 같은 파트에 대한 여러 번의 등록은 한 번의 등록으로 처리됩니다. - - + @@ -83,7 +81,7 @@ const Container = styled.div` `; const RegisterTitle = styled.p` - font-size: 22px; + font-size: 20px; font-weight: 700; color: ${({ theme: { color } }) => color.white}; @@ -93,16 +91,17 @@ const RegisterTitle = styled.p` `; const Register = styled.button` - cursor: pointer; - width: 100%; - height: 36px; font-weight: 700; color: ${({ theme: { color } }) => color.white}; background-color: ${({ theme: { color } }) => color.primary}; - border: none; + border: 2px solid ${({ theme: { color } }) => color.primary}; + padding: 4px 11px; + + letter-spacing: 6px; + border-radius: 10px; `; @@ -151,5 +150,6 @@ const ButtonContainer = styled.div` `; const Warning = styled.div` + font-size: 14px; color: ${({ theme: { color } }) => color.subText}; `; diff --git a/frontend/src/features/youtube/components/WaveScrubber.tsx b/frontend/src/features/youtube/components/WaveScrubber.tsx index 9f4b0926f..b0e10eb35 100644 --- a/frontend/src/features/youtube/components/WaveScrubber.tsx +++ b/frontend/src/features/youtube/components/WaveScrubber.tsx @@ -60,7 +60,7 @@ const Flex = styled.div` align-items: center; overflow-x: scroll; width: 100%; - height: 100px; + height: 80px; padding: 0 calc((100% - 150px) / 2); `; diff --git a/frontend/src/pages/PartCollectingPage.tsx b/frontend/src/pages/PartCollectingPage.tsx index 6644e247e..b7f1d7f73 100644 --- a/frontend/src/pages/PartCollectingPage.tsx +++ b/frontend/src/pages/PartCollectingPage.tsx @@ -5,6 +5,7 @@ import VoteInterface from '@/features/songs/components/VoteInterface'; import { VoteInterfaceProvider } from '@/features/songs/components/VoteInterfaceProvider'; import { VideoPlayerProvider } from '@/features/youtube/components/VideoPlayerProvider'; import Youtube from '@/features/youtube/components/Youtube'; +import Spacing from '@/shared/components/Spacing'; import useFetch from '@/shared/hooks/useFetch'; import fetcher from '@/shared/remotes'; import type { VotingSongList } from '@/shared/types/song'; @@ -20,19 +21,24 @@ const PartCollectingPage = () => { return ( - - - - {title} - {singer} - - - - - - - - + + + + + + {title} + {singer} + + + + + + + + + + + ); }; @@ -42,17 +48,20 @@ export default PartCollectingPage; const Container = styled.section` display: flex; flex-direction: column; - gap: 20px; + background-color: ${({ theme: { color } }) => color.black300}; + border-radius: 8px; width: 100%; - padding-top: ${({ theme: { headerHeight } }) => headerHeight.desktop}; + + padding: 10px; + margin-top: ${({ theme: { headerHeight } }) => headerHeight.desktop}; @media (max-width: ${({ theme }) => theme.breakPoints.xs}) { - padding-top: ${({ theme: { headerHeight } }) => headerHeight.mobile}; + margin-top: ${({ theme: { headerHeight } }) => headerHeight.mobile}; } @media (max-width: ${({ theme }) => theme.breakPoints.xxs}) { - padding-top: ${({ theme: { headerHeight } }) => headerHeight.xxs}; + margin-top: ${({ theme: { headerHeight } }) => headerHeight.xxs}; } `; @@ -70,27 +79,31 @@ const Info = styled.div` const SongTitle = styled.p` overflow: hidden; - font-size: 24px; + font-size: 22px; font-weight: 700; color: ${({ theme: { color } }) => color.white}; text-overflow: ellipsis; white-space: nowrap; @media (max-width: ${({ theme }) => theme.breakPoints.md}) { - font-size: 20px; + font-size: 18px; } `; const Singer = styled.p` overflow: hidden; - font-size: 18px; + font-size: 16px; font-weight: 700; color: ${({ theme: { color } }) => color.subText}; text-overflow: ellipsis; white-space: nowrap; @media (max-width: ${({ theme }) => theme.breakPoints.md}) { - font-size: 16px; + font-size: 14px; } `; + +const Wrapper = styled.div` + position: relative; +`; From aa0b9a7ec9d1a6342f2720ba98e103347e4613d0 Mon Sep 17 00:00:00 2001 From: ukkodeveloper Date: Wed, 11 Oct 2023 15:28:14 +0900 Subject: [PATCH 11/95] =?UTF-8?q?feat:=20badge=20component=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../youtube/components/PlayerBadge.tsx | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 frontend/src/features/youtube/components/PlayerBadge.tsx diff --git a/frontend/src/features/youtube/components/PlayerBadge.tsx b/frontend/src/features/youtube/components/PlayerBadge.tsx new file mode 100644 index 000000000..436676606 --- /dev/null +++ b/frontend/src/features/youtube/components/PlayerBadge.tsx @@ -0,0 +1,30 @@ +import styled from 'styled-components'; +import type { PropsWithChildren } from 'react'; + +interface PlayerBadge extends PropsWithChildren { + isActive?: boolean; +} + +const PlayerBadge = ({ isActive = false, children }: PlayerBadge) => { + return {children}; +}; + +export default PlayerBadge; + +const Badge = styled.span<{ $isActive: boolean }>` + text-align: center; + height: 30px; + background-color: ${({ theme: { color }, $isActive }) => ($isActive ? 'pink' : color.disabled)}; + color: ${({ theme: { color }, $isActive }) => ($isActive ? color.black : color.white)}; + font-size: 13px; + padding: 0 8px; + line-height: 2.3; + border-radius: 40px; + max-width: 50px; + + display: flex; + justify-content: center; + align-items: center; + + transition: background-color 0.2s ease-in; +`; From 905d31eca906a38a8cd07eea96bbb21c2ff9434c Mon Sep 17 00:00:00 2001 From: ukkodeveloper Date: Wed, 11 Oct 2023 15:28:40 +0900 Subject: [PATCH 12/95] =?UTF-8?q?feat:=20progress=20bar=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EB=B0=8F=20=EC=A0=84=EC=B2=B4=20=EC=9E=AC=EC=83=9D?= =?UTF-8?q?=20progress=20bar=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../youtube/components/ScrubberProgress.tsx | 68 ++++++++++++++++--- 1 file changed, 58 insertions(+), 10 deletions(-) diff --git a/frontend/src/features/youtube/components/ScrubberProgress.tsx b/frontend/src/features/youtube/components/ScrubberProgress.tsx index 02d81d1bc..1fe587f85 100644 --- a/frontend/src/features/youtube/components/ScrubberProgress.tsx +++ b/frontend/src/features/youtube/components/ScrubberProgress.tsx @@ -1,13 +1,11 @@ import styled, { keyframes } from 'styled-components'; interface ScrubberProgressProps { - prevTime: number; interval: number; - isPaused: boolean; } -const ScrubberProgress = ({ prevTime, interval, isPaused }: ScrubberProgressProps) => { - return ; +const ScrubberProgress = ({ interval }: ScrubberProgressProps) => { + return ; }; export default ScrubberProgress; @@ -21,22 +19,72 @@ const fillAnimation = keyframes` } `; -const PlayingBoxBackground = styled.div<{ prevTime: number; interval: number; isPaused: boolean }>` +const PlayingBoxBackground = styled.div<{ interval: number }>` z-index: 0; width: 150px; height: 50px; border-radius: 5px; - position: absolute; // 화면에 고정된 위치 - left: 50%; // 뷰포트의 중앙에 위치 - transform: translateX(-50%); // 뷰포트 중앙 + position: absolute; + left: 50%; + transform: translateX(-50%); background: linear-gradient(to left, transparent 50%, pink 50%); background-size: ${({ interval }) => 200 + (30 - interval)}%; transition: 10s linear; animation: ${fillAnimation} ${({ interval }) => interval}s linear infinite; - animation-play-state: ${({ isPaused }) => (isPaused ? 'paused' : 'running')}; - animation-delay: ${({ prevTime }) => -prevTime}s; + + pointer-events: none; +`; + +export const ScrubberProgressAllPlaying = () => { + return ; +}; + +const animate = keyframes` + 0%, 100% { + clip-path: polygon( + 0% 45%, + 16% 44%, + 33% 50%, + 54% 60%, + 70% 61%, + 84% 59%, + 100% 52%, + 100% 100%, + 0% 100% + ); + } + + 50% { + clip-path: polygon( + 0% 60%, + 15% 65%, + 34% 66%, + 51% 62%, + 67% 50%, + 84% 45%, + 100% 46%, + 100% 100%, + 0% 100% + ); + } +`; + +const AllPlayingBackground = styled.div` + z-index: 0; + width: 150px; + height: 50px; + border-radius: 5px; + + position: absolute; + left: 50%; + transform: translateX(-50%); + + background: linear-gradient(to left, deeppink, pink); + transition: 10s linear; + + animation: ${animate} 4s ease-in-out infinite; pointer-events: none; `; From be1a18ac15b6ec0e5dc8876e9507cebd83dd1b8b Mon Sep 17 00:00:00 2001 From: ukkodeveloper Date: Wed, 11 Oct 2023 15:29:11 +0900 Subject: [PATCH 13/95] =?UTF-8?q?design:=20VoteInterface=20=EC=83=81?= =?UTF-8?q?=ED=95=98=20=EA=B0=84=EA=B2=A9=20=EC=A1=B0=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/features/songs/components/VoteInterface.tsx | 8 +++----- frontend/src/pages/PartCollectingPage.tsx | 5 ++--- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/frontend/src/features/songs/components/VoteInterface.tsx b/frontend/src/features/songs/components/VoteInterface.tsx index 06404ffb5..ca7129835 100644 --- a/frontend/src/features/songs/components/VoteInterface.tsx +++ b/frontend/src/features/songs/components/VoteInterface.tsx @@ -39,18 +39,16 @@ const VoteInterface = () => { return ( 나만의 파트 저장하기 - + 같은 파트에 대한 여러 번의 등록은 한 번의 등록으로 처리됩니다. - + - - + 등록 - {user?.nickname}님의 diff --git a/frontend/src/pages/PartCollectingPage.tsx b/frontend/src/pages/PartCollectingPage.tsx index b7f1d7f73..1d60eb32c 100644 --- a/frontend/src/pages/PartCollectingPage.tsx +++ b/frontend/src/pages/PartCollectingPage.tsx @@ -24,16 +24,15 @@ const PartCollectingPage = () => { - {title} {singer} - + - + From 228ceacdddf2cc6e09633ecc3a060e9cfbac1dfd Mon Sep 17 00:00:00 2001 From: ukkodeveloper Date: Wed, 11 Oct 2023 15:29:54 +0900 Subject: [PATCH 14/95] =?UTF-8?q?design:=20=EB=B2=84=ED=8A=BC=20=EB=B0=8F?= =?UTF-8?q?=20scrubber=20=EC=82=AC=EC=9A=A9=20=EC=8B=9C=20=EB=94=94?= =?UTF-8?q?=EC=9E=90=EC=9D=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../features/songs/components/PartIntervalController.tsx | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/frontend/src/features/songs/components/PartIntervalController.tsx b/frontend/src/features/songs/components/PartIntervalController.tsx index 4be383fc7..90241477e 100644 --- a/frontend/src/features/songs/components/PartIntervalController.tsx +++ b/frontend/src/features/songs/components/PartIntervalController.tsx @@ -39,6 +39,11 @@ const ControlButton = styled.button` background-color: ${({ theme: { color } }) => color.secondary}; border: none; border-radius: 10px; + + &:active { + transition: box-shadow 0.2s ease; + background-color: ${({ theme: { color } }) => color.disabled}; + } `; const IntervalItem = styled.p` @@ -57,4 +62,8 @@ const IntervalItem = styled.p` background-color: ${({ theme: { color } }) => color.white}; border: none; border-radius: 10px; + &:active { + transition: box-shadow 0.1s ease; + box-shadow: 0 0 0 1px inset pink; + } `; From 6b939c5e5907bedfd2b820daccebe94eef06bf5f Mon Sep 17 00:00:00 2001 From: ukkodeveloper Date: Wed, 11 Oct 2023 15:31:56 +0900 Subject: [PATCH 15/95] =?UTF-8?q?feat:=20=EA=B0=81=EC=A2=85=20=EB=93=B1?= =?UTF-8?q?=EB=A1=9D=20=EA=B4=80=EB=A0=A8=20=EC=A0=9C=EC=96=B4=20=ED=83=9C?= =?UTF-8?q?=EA=B7=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/VoteInterfaceProvider.tsx | 24 ++++- .../youtube/components/WaveScrubber.tsx | 98 +++++++++++++++---- 2 files changed, 101 insertions(+), 21 deletions(-) diff --git a/frontend/src/features/songs/components/VoteInterfaceProvider.tsx b/frontend/src/features/songs/components/VoteInterfaceProvider.tsx index bace7e7fc..d70a30a8c 100644 --- a/frontend/src/features/songs/components/VoteInterfaceProvider.tsx +++ b/frontend/src/features/songs/components/VoteInterfaceProvider.tsx @@ -11,6 +11,9 @@ interface VoteInterfaceContextProps extends VoteInterfaceProviderProps { updateKillingPartInterval: React.MouseEventHandler; plusPartInterval: () => void; minusPartInterval: () => void; + toggleAllPlay: () => void; + isAllPlay: boolean; + isVideoStatePlaying: boolean; } export const VoteInterfaceContext = createContext(null); @@ -29,7 +32,10 @@ export const VoteInterfaceProvider = ({ }: PropsWithChildren) => { const [interval, setInterval] = useState(10); const [partStartTime, setPartStartTime] = useState(0); - const { videoPlayer } = useVideoPlayerContext(); + const [isAllPlay, setIsAllPlay] = useState(false); + const { videoPlayer, playerState } = useVideoPlayerContext(); + + const isVideoStatePlaying = playerState === 1; const updateKillingPartInterval: React.MouseEventHandler = (e) => { const newInterval = Number(e.currentTarget.dataset['interval']) as number; @@ -43,6 +49,11 @@ export const VoteInterfaceProvider = ({ setInterval(newInterval); }; + const toggleAllPlay = () => { + setIsAllPlay((prev) => !prev); + videoPlayer.current?.playVideo(); + }; + const plusPartInterval = () => { setInterval((prevInterval) => { const currentInterval = prevInterval + 1; @@ -84,12 +95,16 @@ export const VoteInterfaceProvider = ({ }; useEffect(() => { + if (isAllPlay) return; + const timer = window.setInterval(() => { videoPlayer.current?.seekTo(partStartTime, true); }, interval * 1000); - return () => window.clearInterval(timer); - }, [videoPlayer.current, partStartTime, interval]); + return () => { + window.clearInterval(timer); + }; + }, [partStartTime, interval, isAllPlay]); return ( {children} diff --git a/frontend/src/features/youtube/components/WaveScrubber.tsx b/frontend/src/features/youtube/components/WaveScrubber.tsx index b0e10eb35..cb644dae8 100644 --- a/frontend/src/features/youtube/components/WaveScrubber.tsx +++ b/frontend/src/features/youtube/components/WaveScrubber.tsx @@ -1,22 +1,38 @@ import { useRef } from 'react'; import styled from 'styled-components'; +import fillPlayIcon from '@/assets/icon/fill-play.svg'; +import pauseIcon from '@/assets/icon/pause.svg'; import useVoteInterfaceContext from '@/features/songs/hooks/useVoteInterfaceContext'; -import ScrubberProgress from '@/features/youtube/components/ScrubberProgress'; +import PlayerBadge from '@/features/youtube/components/PlayerBadge'; +import ScrubberProgress, { + ScrubberProgressAllPlaying, +} from '@/features/youtube/components/ScrubberProgress'; import SoundWave from '@/features/youtube/components/SoundWave'; import useVideoPlayerContext from '@/features/youtube/hooks/useVideoPlayerContext'; +import Spacing from '@/shared/components/Spacing'; import useDebounceEffect from '@/shared/hooks/useDebounceEffect'; -import { secondsToMinSec } from '@/shared/utils/convertTime'; +import { secondsToMinSec, toMinSecText } from '@/shared/utils/convertTime'; const PROGRESS_WIDTH = 350; const WaveScrubber = () => { - const { interval, partStartTime, videoLength, updatePartStartTime } = useVoteInterfaceContext(); - const { playerState, seekTo } = useVideoPlayerContext(); + const { + interval, + partStartTime, + videoLength, + isVideoStatePlaying, + isAllPlay, + updatePartStartTime, + toggleAllPlay, + } = useVoteInterfaceContext(); + const { videoPlayer, seekTo } = useVideoPlayerContext(); const temptKey = useRef(0); const maxPartStartTime = videoLength - interval; const seekAndPlay = () => { seekTo(partStartTime); }; + const partStartTimeText = toMinSecText(partStartTime); + const changePartStartTime: React.UIEventHandler = (e) => { const { scrollWidth, scrollLeft } = e.currentTarget; const unit = (scrollWidth - PROGRESS_WIDTH) / maxPartStartTime; @@ -30,23 +46,51 @@ const WaveScrubber = () => { } }; + const playWhenTouch = () => { + if (isVideoStatePlaying) { + videoPlayer.current?.playVideo(); + } + }; + useDebounceEffect<[number, number]>(seekAndPlay, [partStartTime, interval], 300); return ( - - - - - - {playerState === 1 && ( - - )} - + <> + + {partStartTimeText} + + {isVideoStatePlaying ? ( + + ) : ( + + )} + + + + + + + + + + + + {isVideoStatePlaying && !isAllPlay && } + {isVideoStatePlaying && isAllPlay && } + + ); }; @@ -92,4 +136,22 @@ const Container = styled.div` flex-direction: column; align-items: center; justify-content: center; + + &:active { + transition: box-shadow 0.2s ease; + box-shadow: 0 0 0 1px inset pink; + } +`; + +const BadgeContainer = styled.div` + display: flex; + justify-content: flex-end; + column-gap: 14px; +`; + +const Button = styled.button` + width: 20px; + display: flex; + justify-content: center; + align-items: center; `; From 2e1795b255d76461565d5685695972cfe0601a7e Mon Sep 17 00:00:00 2001 From: ukkodeveloper Date: Wed, 11 Oct 2023 15:37:49 +0900 Subject: [PATCH 16/95] =?UTF-8?q?fix:=20WaveScrubber=20=EC=A0=95=EC=A7=80?= =?UTF-8?q?=20=EC=98=A4=EB=A5=98=20=EB=B0=8F=20=ED=84=B0=EC=B9=98=20?= =?UTF-8?q?=EC=8B=9C=20=EC=9E=AC=EC=83=9D=20=EC=98=A4=EB=A5=98=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/features/youtube/components/WaveScrubber.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/frontend/src/features/youtube/components/WaveScrubber.tsx b/frontend/src/features/youtube/components/WaveScrubber.tsx index cb644dae8..107ec9e96 100644 --- a/frontend/src/features/youtube/components/WaveScrubber.tsx +++ b/frontend/src/features/youtube/components/WaveScrubber.tsx @@ -47,7 +47,7 @@ const WaveScrubber = () => { }; const playWhenTouch = () => { - if (isVideoStatePlaying) { + if (!isVideoStatePlaying) { videoPlayer.current?.playVideo(); } }; @@ -60,7 +60,11 @@ const WaveScrubber = () => { {partStartTimeText} {isVideoStatePlaying ? ( - ) : ( From 0cd252e0e0d92623f1ea04e9a771adbfe9ed667f Mon Sep 17 00:00:00 2001 From: ukkodeveloper Date: Wed, 11 Oct 2023 17:05:25 +0900 Subject: [PATCH 17/95] =?UTF-8?q?design:=20body=20background=20black?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=ED=95=98=EC=97=AC=20=EC=8A=A4=ED=81=AC?= =?UTF-8?q?=EB=A1=A4=20=EB=81=9D=EC=97=90=20=ED=9D=B0=EC=83=89=20=EB=82=98?= =?UTF-8?q?=EC=98=A4=EC=A7=80=20=EC=95=8A=EB=8F=84=EB=A1=9D=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/shared/styles/GlobalStyles.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/src/shared/styles/GlobalStyles.ts b/frontend/src/shared/styles/GlobalStyles.ts index e34e3eed7..573db0bdf 100644 --- a/frontend/src/shared/styles/GlobalStyles.ts +++ b/frontend/src/shared/styles/GlobalStyles.ts @@ -67,6 +67,7 @@ const GlobalStyles = createGlobalStyle` body { font-family: 'Pretendard'; + background-color: black; } `; From 607ec8f56dcacae6c84a492bbe29333c68a4a64a Mon Sep 17 00:00:00 2001 From: ukkodeveloper Date: Wed, 11 Oct 2023 17:12:47 +0900 Subject: [PATCH 18/95] =?UTF-8?q?design:=20register=20=EB=B2=84=ED=8A=BC?= =?UTF-8?q?=20=EB=94=94=EC=9E=90=EC=9D=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../features/songs/components/VoteInterface.tsx | 15 ++++++++++----- frontend/src/pages/PartCollectingPage.tsx | 12 +++++++----- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/frontend/src/features/songs/components/VoteInterface.tsx b/frontend/src/features/songs/components/VoteInterface.tsx index ca7129835..d9558cd0e 100644 --- a/frontend/src/features/songs/components/VoteInterface.tsx +++ b/frontend/src/features/songs/components/VoteInterface.tsx @@ -39,13 +39,12 @@ const VoteInterface = () => { return ( 나만의 파트 저장하기 - - 같은 파트에 대한 여러 번의 등록은 한 번의 등록으로 처리됩니다. + + 같은 파트에 대한 중복 등록은 한 번의 등록으로 처리됩니다. - 등록 @@ -89,18 +88,24 @@ const RegisterTitle = styled.p` `; const Register = styled.button` - width: 100%; + width: 92%; font-weight: 700; color: ${({ theme: { color } }) => color.white}; background-color: ${({ theme: { color } }) => color.primary}; border: 2px solid ${({ theme: { color } }) => color.primary}; - padding: 4px 11px; + padding: 6px 11px; letter-spacing: 6px; border-radius: 10px; + + margin-top: 8px; + position: fixed; + left: 50%; + transform: translate(-50%, 0); + bottom: 8px; `; const ModalTitle = styled.h3``; diff --git a/frontend/src/pages/PartCollectingPage.tsx b/frontend/src/pages/PartCollectingPage.tsx index 1d60eb32c..29b8a1b76 100644 --- a/frontend/src/pages/PartCollectingPage.tsx +++ b/frontend/src/pages/PartCollectingPage.tsx @@ -45,14 +45,12 @@ const PartCollectingPage = () => { export default PartCollectingPage; const Container = styled.section` + flex: 0.4; display: flex; + justify-content: center; flex-direction: column; - background-color: ${({ theme: { color } }) => color.black300}; - - border-radius: 8px; width: 100%; - padding: 10px; margin-top: ${({ theme: { headerHeight } }) => headerHeight.desktop}; @media (max-width: ${({ theme }) => theme.breakPoints.xs}) { @@ -104,5 +102,9 @@ const Singer = styled.p` `; const Wrapper = styled.div` - position: relative; + display: flex; + flex-direction: column; + background-color: ${({ theme: { color } }) => color.black300}; + border-radius: 8px; + padding: 10px; `; From e054219ba93fa30af8dc041b7bb855e175d9d0b8 Mon Sep 17 00:00:00 2001 From: ukkodeveloper Date: Wed, 11 Oct 2023 17:49:56 +0900 Subject: [PATCH 19/95] =?UTF-8?q?fix:=20=ED=8C=8C=ED=8A=B8=20=EB=93=B1?= =?UTF-8?q?=EB=A1=9D=20=ED=8E=98=EC=9D=B4=EC=A7=80=EC=97=90=20AuthLayout?= =?UTF-8?q?=20=EC=9E=AC=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/router.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/frontend/src/router.tsx b/frontend/src/router.tsx index 33b267ea3..99e9b8af7 100644 --- a/frontend/src/router.tsx +++ b/frontend/src/router.tsx @@ -26,7 +26,11 @@ const router = createBrowserRouter([ }, { path: `${ROUTE_PATH.COLLECT}/:id`, - element: , + element: ( + + + + ), }, { path: `${ROUTE_PATH.SONG_DETAILS}/:id/:genre`, From e00119b508cb6c32608822ecdabececf77734187 Mon Sep 17 00:00:00 2001 From: ukkodeveloper Date: Sat, 14 Oct 2023 12:45:12 +0900 Subject: [PATCH 20/95] =?UTF-8?q?refactor:=20=EC=8B=9C=EC=9E=91,=20?= =?UTF-8?q?=EC=A0=95=EC=A7=80=20=EB=B2=84=ED=8A=BC=20=EB=B3=84=EB=8F=84?= =?UTF-8?q?=EC=9D=98=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=EB=A1=9C=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/PlayingToggleButton.tsx | 34 ++++++++++++ .../youtube/components/WaveScrubber.tsx | 53 +++++++++---------- 2 files changed, 58 insertions(+), 29 deletions(-) create mode 100644 frontend/src/features/youtube/components/PlayingToggleButton.tsx diff --git a/frontend/src/features/youtube/components/PlayingToggleButton.tsx b/frontend/src/features/youtube/components/PlayingToggleButton.tsx new file mode 100644 index 000000000..ba5c0efe9 --- /dev/null +++ b/frontend/src/features/youtube/components/PlayingToggleButton.tsx @@ -0,0 +1,34 @@ +import styled from 'styled-components'; +import fillPlayIcon from '@/assets/icon/fill-play.svg'; +import pauseIcon from '@/assets/icon/pause.svg'; + +interface PlayingToggleButtonProps { + pause: () => void; + play: () => void; + isPlaying: boolean; +} + +const PlayingToggleButton = ({ pause, play, isPlaying }: PlayingToggleButtonProps) => { + return ( + <> + {isPlaying ? ( + + ) : ( + + )} + + ); +}; + +export default PlayingToggleButton; + +const Button = styled.button` + width: 20px; + display: flex; + justify-content: center; + align-items: center; +`; diff --git a/frontend/src/features/youtube/components/WaveScrubber.tsx b/frontend/src/features/youtube/components/WaveScrubber.tsx index 107ec9e96..49df6e466 100644 --- a/frontend/src/features/youtube/components/WaveScrubber.tsx +++ b/frontend/src/features/youtube/components/WaveScrubber.tsx @@ -1,9 +1,8 @@ import { useRef } from 'react'; import styled from 'styled-components'; -import fillPlayIcon from '@/assets/icon/fill-play.svg'; -import pauseIcon from '@/assets/icon/pause.svg'; import useVoteInterfaceContext from '@/features/songs/hooks/useVoteInterfaceContext'; import PlayerBadge from '@/features/youtube/components/PlayerBadge'; +import PlayingToggleButton from '@/features/youtube/components/PlayingToggleButton'; import ScrubberProgress, { ScrubberProgressAllPlaying, } from '@/features/youtube/components/ScrubberProgress'; @@ -46,6 +45,18 @@ const WaveScrubber = () => { } }; + const clickPlay = () => { + if (isAllPlay) { + videoPlayer.current?.playVideo(); + } else { + seekTo(partStartTime); + } + }; + + const clickPause = () => { + videoPlayer.current?.pauseVideo(); + }; + const playWhenTouch = () => { if (!isVideoStatePlaying) { videoPlayer.current?.playVideo(); @@ -59,27 +70,11 @@ const WaveScrubber = () => { {partStartTimeText} - {isVideoStatePlaying ? ( - - ) : ( - - )} + @@ -147,15 +142,15 @@ const Container = styled.div` } `; -const BadgeContainer = styled.div` - display: flex; - justify-content: flex-end; - column-gap: 14px; -`; - const Button = styled.button` width: 20px; display: flex; justify-content: center; align-items: center; `; + +const BadgeContainer = styled.div` + display: flex; + justify-content: flex-end; + column-gap: 14px; +`; From eb8e960b3bc1360e1201793c45e6e3f20bc948a2 Mon Sep 17 00:00:00 2001 From: ukkodeveloper Date: Sat, 14 Oct 2023 13:05:13 +0900 Subject: [PATCH 21/95] =?UTF-8?q?refactor:=20Flex=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=20=EC=82=AC=EC=9A=A9=ED=95=98=EC=97=AC=20Wav?= =?UTF-8?q?eScrubber=20=EB=A6=AC=ED=8C=A9=ED=84=B0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../youtube/components/WaveScrubber.tsx | 29 ++++++++++--------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/frontend/src/features/youtube/components/WaveScrubber.tsx b/frontend/src/features/youtube/components/WaveScrubber.tsx index 49df6e466..3917a95d7 100644 --- a/frontend/src/features/youtube/components/WaveScrubber.tsx +++ b/frontend/src/features/youtube/components/WaveScrubber.tsx @@ -8,6 +8,7 @@ import ScrubberProgress, { } from '@/features/youtube/components/ScrubberProgress'; import SoundWave from '@/features/youtube/components/SoundWave'; import useVideoPlayerContext from '@/features/youtube/hooks/useVideoPlayerContext'; +import Flex from '@/shared/components/Flex/Flex'; import Spacing from '@/shared/components/Spacing'; import useDebounceEffect from '@/shared/hooks/useDebounceEffect'; import { secondsToMinSec, toMinSecText } from '@/shared/utils/convertTime'; @@ -82,7 +83,20 @@ const WaveScrubber = () => { - + @@ -95,19 +109,6 @@ const WaveScrubber = () => { export default WaveScrubber; -const Flex = styled.div` - z-index: 3; - display: flex; - column-gap: 8px; - background-color: transparent; - align-items: center; - overflow-x: scroll; - width: 100%; - height: 80px; - - padding: 0 calc((100% - 150px) / 2); -`; - const PlayingBox = styled.div` z-index: 1; width: 150px; From 709cdd5365963fe902dd4c6d7499f1b83d8b7b57 Mon Sep 17 00:00:00 2001 From: ukkodeveloper Date: Sat, 14 Oct 2023 15:43:16 +0900 Subject: [PATCH 22/95] =?UTF-8?q?feat:=20=ED=82=AC=EB=A7=81=ED=8C=8C?= =?UTF-8?q?=ED=8A=B8=20=EB=93=B1=EB=A1=9D=ED=95=98=EB=8A=94=20=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EB=B0=98=EC=9D=91=ED=98=95=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../songs/components/VoteInterface.tsx | 32 ++++++++++++++-- .../youtube/components/WaveScrubber.tsx | 1 + frontend/src/pages/PartCollectingPage.tsx | 37 ++++++++++--------- 3 files changed, 49 insertions(+), 21 deletions(-) diff --git a/frontend/src/features/songs/components/VoteInterface.tsx b/frontend/src/features/songs/components/VoteInterface.tsx index d9558cd0e..4f8a8d132 100644 --- a/frontend/src/features/songs/components/VoteInterface.tsx +++ b/frontend/src/features/songs/components/VoteInterface.tsx @@ -45,6 +45,7 @@ const VoteInterface = () => { + 등록 @@ -75,15 +76,21 @@ export default VoteInterface; const Container = styled.div` display: flex; flex-direction: column; + width: 100%; + + @media (min-width: ${({ theme }) => theme.breakPoints.md}) { + width: 320px; + padding: 16px; + } `; const RegisterTitle = styled.p` - font-size: 20px; + font-size: 18px; font-weight: 700; color: ${({ theme: { color } }) => color.white}; - @media (max-width: ${({ theme }) => theme.breakPoints.md}) { - font-size: 18px; + @media (min-width: ${({ theme }) => theme.breakPoints.md}) { + font-size: 24px; } `; @@ -106,6 +113,16 @@ const Register = styled.button` left: 50%; transform: translate(-50%, 0); bottom: 8px; + + @media (min-width: ${({ theme }) => theme.breakPoints.md}) { + width: 100%; + left: unset; + transform: unset; + position: static; + + padding: 11px 15px; + font-size: 18px; + } `; const ModalTitle = styled.h3``; @@ -155,4 +172,13 @@ const ButtonContainer = styled.div` const Warning = styled.div` font-size: 14px; color: ${({ theme: { color } }) => color.subText}; + + @media (min-width: ${({ theme }) => theme.breakPoints.md}) { + font-size: 18px; + } +`; + +const FlexibleSpacing = styled.div` + margin: 8px 0; + flex: 1; `; diff --git a/frontend/src/features/youtube/components/WaveScrubber.tsx b/frontend/src/features/youtube/components/WaveScrubber.tsx index 3917a95d7..76f0a49a5 100644 --- a/frontend/src/features/youtube/components/WaveScrubber.tsx +++ b/frontend/src/features/youtube/components/WaveScrubber.tsx @@ -133,6 +133,7 @@ const Container = styled.div` border-radius: 8px; display: flex; + position: relative; flex-direction: column; align-items: center; justify-content: center; diff --git a/frontend/src/pages/PartCollectingPage.tsx b/frontend/src/pages/PartCollectingPage.tsx index 29b8a1b76..2d8f88ac1 100644 --- a/frontend/src/pages/PartCollectingPage.tsx +++ b/frontend/src/pages/PartCollectingPage.tsx @@ -5,6 +5,7 @@ import VoteInterface from '@/features/songs/components/VoteInterface'; import { VoteInterfaceProvider } from '@/features/songs/components/VoteInterfaceProvider'; import { VideoPlayerProvider } from '@/features/youtube/components/VideoPlayerProvider'; import Youtube from '@/features/youtube/components/Youtube'; +import Flex from '@/shared/components/Flex/Flex'; import Spacing from '@/shared/components/Spacing'; import useFetch from '@/shared/hooks/useFetch'; import fetcher from '@/shared/remotes'; @@ -21,23 +22,25 @@ const PartCollectingPage = () => { return ( - - - - - {title} - {singer} - - - - - - + + + + + + + {title} + {singer} + + + + + + - - + + ); }; @@ -45,7 +48,7 @@ const PartCollectingPage = () => { export default PartCollectingPage; const Container = styled.section` - flex: 0.4; + flex: 0.6; display: flex; justify-content: center; flex-direction: column; @@ -101,9 +104,7 @@ const Singer = styled.p` } `; -const Wrapper = styled.div` - display: flex; - flex-direction: column; +const FlexWrapper = styled(Flex)` background-color: ${({ theme: { color } }) => color.black300}; border-radius: 8px; padding: 10px; From bd956bd0378a888fb360eb56d51ced12abba7dde Mon Sep 17 00:00:00 2001 From: ukkodeveloper Date: Sat, 14 Oct 2023 15:56:53 +0900 Subject: [PATCH 23/95] =?UTF-8?q?feat:=20=EC=9C=A0=ED=8A=9C=EB=B8=8C=20?= =?UTF-8?q?=EC=9E=AC=EC=83=9D=20effect=20=EC=9D=98=EC=A1=B4=EC=84=B1=20?= =?UTF-8?q?=EB=B0=B0=EC=97=B4=EC=97=90=20=ED=94=8C=EB=A0=88=EC=9D=B4?= =?UTF-8?q?=EC=96=B4=20=EC=83=81=ED=83=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/features/songs/components/VoteInterfaceProvider.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/features/songs/components/VoteInterfaceProvider.tsx b/frontend/src/features/songs/components/VoteInterfaceProvider.tsx index d70a30a8c..beddd51d7 100644 --- a/frontend/src/features/songs/components/VoteInterfaceProvider.tsx +++ b/frontend/src/features/songs/components/VoteInterfaceProvider.tsx @@ -104,7 +104,7 @@ export const VoteInterfaceProvider = ({ return () => { window.clearInterval(timer); }; - }, [partStartTime, interval, isAllPlay]); + }, [playerState, partStartTime, interval, isAllPlay]); return ( Date: Sat, 14 Oct 2023 16:20:12 +0900 Subject: [PATCH 24/95] =?UTF-8?q?design:=20=EC=82=AC=EC=9A=A9=ED=95=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EB=8A=94=20=EC=86=8D=EC=84=B1=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0=20=EB=B0=8F=20css=20=EC=86=8D=EC=84=B1=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../features/songs/components/VoteInterface.tsx | 17 +++++++---------- .../features/youtube/components/PlayerBadge.tsx | 9 +++++---- .../youtube/components/WaveScrubber.tsx | 13 ++----------- 3 files changed, 14 insertions(+), 25 deletions(-) diff --git a/frontend/src/features/songs/components/VoteInterface.tsx b/frontend/src/features/songs/components/VoteInterface.tsx index 4f8a8d132..c6d7ecc6c 100644 --- a/frontend/src/features/songs/components/VoteInterface.tsx +++ b/frontend/src/features/songs/components/VoteInterface.tsx @@ -39,11 +39,10 @@ const VoteInterface = () => { return ( 나만의 파트 저장하기 - 같은 파트에 대한 중복 등록은 한 번의 등록으로 처리됩니다. - + @@ -90,7 +89,7 @@ const RegisterTitle = styled.p` color: ${({ theme: { color } }) => color.white}; @media (min-width: ${({ theme }) => theme.breakPoints.md}) { - font-size: 24px; + font-size: 22px; } `; @@ -101,7 +100,6 @@ const Register = styled.button` color: ${({ theme: { color } }) => color.white}; background-color: ${({ theme: { color } }) => color.primary}; - border: 2px solid ${({ theme: { color } }) => color.primary}; padding: 6px 11px; letter-spacing: 6px; @@ -143,13 +141,9 @@ const ModalContent = styled.div` const Message = styled.div``; const Button = styled.button` - cursor: pointer; - height: 36px; - color: ${({ theme: { color } }) => color.white}; - border: none; border-radius: 10px; `; @@ -179,6 +173,9 @@ const Warning = styled.div` `; const FlexibleSpacing = styled.div` - margin: 8px 0; - flex: 1; + + @media (min-width: ${({theme}) => theme.breakPoints.md}) { + margin: 8px 0; + flex: 1; + } `; diff --git a/frontend/src/features/youtube/components/PlayerBadge.tsx b/frontend/src/features/youtube/components/PlayerBadge.tsx index 436676606..f2442d90f 100644 --- a/frontend/src/features/youtube/components/PlayerBadge.tsx +++ b/frontend/src/features/youtube/components/PlayerBadge.tsx @@ -17,14 +17,15 @@ const Badge = styled.span<{ $isActive: boolean }>` background-color: ${({ theme: { color }, $isActive }) => ($isActive ? 'pink' : color.disabled)}; color: ${({ theme: { color }, $isActive }) => ($isActive ? color.black : color.white)}; font-size: 13px; - padding: 0 8px; - line-height: 2.3; + padding: 0 10px; border-radius: 40px; - max-width: 50px; display: flex; - justify-content: center; align-items: center; transition: background-color 0.2s ease-in; + + @media (min-width: ${({ theme }) => theme.breakPoints.md}) { + font-size: 14px; + } `; diff --git a/frontend/src/features/youtube/components/WaveScrubber.tsx b/frontend/src/features/youtube/components/WaveScrubber.tsx index 76f0a49a5..503099ded 100644 --- a/frontend/src/features/youtube/components/WaveScrubber.tsx +++ b/frontend/src/features/youtube/components/WaveScrubber.tsx @@ -78,10 +78,10 @@ const WaveScrubber = () => { /> - + - + Date: Sat, 14 Oct 2023 16:22:31 +0900 Subject: [PATCH 25/95] =?UTF-8?q?style:=20styled=20lint=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/PartIntervalController.tsx | 15 +++++----- .../songs/components/VoteInterface.tsx | 29 +++++++++---------- .../youtube/components/PlayerBadge.tsx | 16 +++++----- .../components/PlayingToggleButton.tsx | 4 +-- .../youtube/components/ScrubberProgress.tsx | 27 ++++++++--------- .../features/youtube/components/SoundWave.tsx | 14 +++++---- .../youtube/components/WaveScrubber.tsx | 25 ++++++++-------- frontend/src/pages/PartCollectingPage.tsx | 8 ++--- 8 files changed, 71 insertions(+), 67 deletions(-) diff --git a/frontend/src/features/songs/components/PartIntervalController.tsx b/frontend/src/features/songs/components/PartIntervalController.tsx index 90241477e..4e8cceabf 100644 --- a/frontend/src/features/songs/components/PartIntervalController.tsx +++ b/frontend/src/features/songs/components/PartIntervalController.tsx @@ -28,21 +28,21 @@ const ControlButton = styled.button` flex: 1; min-width: 50px; - margin: 0; padding: 4px 11px; + font-weight: '700'; + line-height: 1.8; color: ${({ theme: { color } }) => color.white}; text-align: center; - line-height: 1.8; background-color: ${({ theme: { color } }) => color.secondary}; border: none; border-radius: 10px; &:active { - transition: box-shadow 0.2s ease; background-color: ${({ theme: { color } }) => color.disabled}; + transition: box-shadow 0.2s ease; } `; @@ -50,20 +50,19 @@ const IntervalItem = styled.p` flex: 1; min-width: 50px; - padding: 4px 11px; margin: 0; - - text-align: center; - line-height: 1.8; + padding: 4px 11px; font-weight: '700'; + line-height: 1.8; color: ${({ theme: { color } }) => color.black}; + text-align: center; background-color: ${({ theme: { color } }) => color.white}; border: none; border-radius: 10px; &:active { - transition: box-shadow 0.1s ease; box-shadow: 0 0 0 1px inset pink; + transition: box-shadow 0.1s ease; } `; diff --git a/frontend/src/features/songs/components/VoteInterface.tsx b/frontend/src/features/songs/components/VoteInterface.tsx index c6d7ecc6c..737338bf4 100644 --- a/frontend/src/features/songs/components/VoteInterface.tsx +++ b/frontend/src/features/songs/components/VoteInterface.tsx @@ -94,31 +94,30 @@ const RegisterTitle = styled.p` `; const Register = styled.button` + position: fixed; + bottom: 8px; + left: 50%; + transform: translate(-50%, 0); + width: 92%; + margin-top: 8px; + padding: 6px 11px; font-weight: 700; color: ${({ theme: { color } }) => color.white}; - - background-color: ${({ theme: { color } }) => color.primary}; - padding: 6px 11px; - letter-spacing: 6px; + background-color: ${({ theme: { color } }) => color.primary}; border-radius: 10px; - margin-top: 8px; - position: fixed; - left: 50%; - transform: translate(-50%, 0); - bottom: 8px; - @media (min-width: ${({ theme }) => theme.breakPoints.md}) { - width: 100%; + position: static; left: unset; transform: unset; - position: static; + width: 100%; padding: 11px 15px; + font-size: 18px; } `; @@ -143,7 +142,6 @@ const Message = styled.div``; const Button = styled.button` height: 36px; color: ${({ theme: { color } }) => color.white}; - border-radius: 10px; `; @@ -173,9 +171,8 @@ const Warning = styled.div` `; const FlexibleSpacing = styled.div` - - @media (min-width: ${({theme}) => theme.breakPoints.md}) { + @media (min-width: ${({ theme }) => theme.breakPoints.md}) { + flex: 1; margin: 8px 0; - flex: 1; } `; diff --git a/frontend/src/features/youtube/components/PlayerBadge.tsx b/frontend/src/features/youtube/components/PlayerBadge.tsx index f2442d90f..21d845137 100644 --- a/frontend/src/features/youtube/components/PlayerBadge.tsx +++ b/frontend/src/features/youtube/components/PlayerBadge.tsx @@ -12,16 +12,18 @@ const PlayerBadge = ({ isActive = false, children }: PlayerBadge) => { export default PlayerBadge; const Badge = styled.span<{ $isActive: boolean }>` - text-align: center; + display: flex; + align-items: center; + height: 30px; - background-color: ${({ theme: { color }, $isActive }) => ($isActive ? 'pink' : color.disabled)}; - color: ${({ theme: { color }, $isActive }) => ($isActive ? color.black : color.white)}; - font-size: 13px; padding: 0 10px; - border-radius: 40px; - display: flex; - align-items: center; + font-size: 13px; + color: ${({ theme: { color }, $isActive }) => ($isActive ? color.black : color.white)}; + text-align: center; + + background-color: ${({ theme: { color }, $isActive }) => ($isActive ? 'pink' : color.disabled)}; + border-radius: 40px; transition: background-color 0.2s ease-in; diff --git a/frontend/src/features/youtube/components/PlayingToggleButton.tsx b/frontend/src/features/youtube/components/PlayingToggleButton.tsx index ba5c0efe9..82a0def0a 100644 --- a/frontend/src/features/youtube/components/PlayingToggleButton.tsx +++ b/frontend/src/features/youtube/components/PlayingToggleButton.tsx @@ -27,8 +27,8 @@ const PlayingToggleButton = ({ pause, play, isPlaying }: PlayingToggleButtonProp export default PlayingToggleButton; const Button = styled.button` - width: 20px; display: flex; - justify-content: center; align-items: center; + justify-content: center; + width: 20px; `; diff --git a/frontend/src/features/youtube/components/ScrubberProgress.tsx b/frontend/src/features/youtube/components/ScrubberProgress.tsx index 1fe587f85..d0d365fa1 100644 --- a/frontend/src/features/youtube/components/ScrubberProgress.tsx +++ b/frontend/src/features/youtube/components/ScrubberProgress.tsx @@ -20,21 +20,22 @@ const fillAnimation = keyframes` `; const PlayingBoxBackground = styled.div<{ interval: number }>` - z-index: 0; - width: 150px; - height: 50px; - border-radius: 5px; + pointer-events: none; position: absolute; + z-index: 0; left: 50%; transform: translateX(-50%); + width: 150px; + height: 50px; + background: linear-gradient(to left, transparent 50%, pink 50%); background-size: ${({ interval }) => 200 + (30 - interval)}%; + border-radius: 5px; + transition: 10s linear; animation: ${fillAnimation} ${({ interval }) => interval}s linear infinite; - - pointer-events: none; `; export const ScrubberProgressAllPlaying = () => { @@ -72,19 +73,19 @@ const animate = keyframes` `; const AllPlayingBackground = styled.div` - z-index: 0; - width: 150px; - height: 50px; - border-radius: 5px; + pointer-events: none; position: absolute; + z-index: 0; left: 50%; transform: translateX(-50%); + width: 150px; + height: 50px; + background: linear-gradient(to left, deeppink, pink); - transition: 10s linear; + border-radius: 5px; + transition: 10s linear; animation: ${animate} 4s ease-in-out infinite; - - pointer-events: none; `; diff --git a/frontend/src/features/youtube/components/SoundWave.tsx b/frontend/src/features/youtube/components/SoundWave.tsx index e0c692153..85b7041d7 100644 --- a/frontend/src/features/youtube/components/SoundWave.tsx +++ b/frontend/src/features/youtube/components/SoundWave.tsx @@ -17,17 +17,21 @@ export default SoundWave; const LongBar = styled.div` z-index: 2; - height: 24px; + left: calc(50%); + width: 4px; - border-radius: 5px; + height: 24px; + background-color: ${({ theme: { color } }) => color.white}; - left: calc(50%); + border-radius: 5px; `; const ShortBar = styled.div` z-index: 2; - height: 15px; + width: 4px; - border-radius: 5px; + height: 15px; + background-color: ${({ theme: { color } }) => color.white}; + border-radius: 5px; `; diff --git a/frontend/src/features/youtube/components/WaveScrubber.tsx b/frontend/src/features/youtube/components/WaveScrubber.tsx index 503099ded..3e2b965dd 100644 --- a/frontend/src/features/youtube/components/WaveScrubber.tsx +++ b/frontend/src/features/youtube/components/WaveScrubber.tsx @@ -110,40 +110,41 @@ const WaveScrubber = () => { export default WaveScrubber; const PlayingBox = styled.div` + position: absolute; z-index: 1; + left: 50%; + transform: translateX(-50%); + width: 150px; height: 50px; border: transparent; border-radius: 4px; - box-shadow: 0 0 0 2px inset ${({ theme: { color } }) => color.white}; - position: absolute; - left: 50%; - transform: translateX(-50%); `; const Container = styled.div` - width: 100%; - background-color: ${({ theme: { color } }) => color.secondary}; - - margin: auto; - border-radius: 8px; + position: relative; display: flex; - position: relative; flex-direction: column; align-items: center; justify-content: center; + width: 100%; + margin: auto; + + background-color: ${({ theme: { color } }) => color.secondary}; + border-radius: 8px; + &:active { - transition: box-shadow 0.2s ease; box-shadow: 0 0 0 1px inset pink; + transition: box-shadow 0.2s ease; } `; const BadgeContainer = styled.div` display: flex; - justify-content: flex-end; column-gap: 14px; + justify-content: flex-end; `; diff --git a/frontend/src/pages/PartCollectingPage.tsx b/frontend/src/pages/PartCollectingPage.tsx index 2d8f88ac1..17fa09cec 100644 --- a/frontend/src/pages/PartCollectingPage.tsx +++ b/frontend/src/pages/PartCollectingPage.tsx @@ -48,12 +48,12 @@ const PartCollectingPage = () => { export default PartCollectingPage; const Container = styled.section` - flex: 0.6; display: flex; - justify-content: center; + flex: 0.6; flex-direction: column; - width: 100%; + justify-content: center; + width: 100%; margin-top: ${({ theme: { headerHeight } }) => headerHeight.desktop}; @media (max-width: ${({ theme }) => theme.breakPoints.xs}) { @@ -105,7 +105,7 @@ const Singer = styled.p` `; const FlexWrapper = styled(Flex)` + padding: 10px; background-color: ${({ theme: { color } }) => color.black300}; border-radius: 8px; - padding: 10px; `; From 251865d2edd1af7006c87613f8e1c42e925c73ab Mon Sep 17 00:00:00 2001 From: ukkodeveloper Date: Sat, 14 Oct 2023 20:37:38 +0900 Subject: [PATCH 26/95] =?UTF-8?q?refactor:=20WaveScrubber=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=EC=97=90=20=EC=9E=88=EC=96=B4=20=ED=95=84=EC=9A=94?= =?UTF-8?q?=EC=97=86=EB=8A=94=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C=20=EB=B0=8F=20=ED=95=9C=20=EB=8D=B0=20?= =?UTF-8?q?=EB=91=90=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../youtube/components/ScrubberProgress.tsx | 91 --------- .../features/youtube/components/SoundWave.tsx | 21 +- .../youtube/components/WaveScrubber.tsx | 186 ++++++++++-------- 3 files changed, 118 insertions(+), 180 deletions(-) delete mode 100644 frontend/src/features/youtube/components/ScrubberProgress.tsx diff --git a/frontend/src/features/youtube/components/ScrubberProgress.tsx b/frontend/src/features/youtube/components/ScrubberProgress.tsx deleted file mode 100644 index d0d365fa1..000000000 --- a/frontend/src/features/youtube/components/ScrubberProgress.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import styled, { keyframes } from 'styled-components'; - -interface ScrubberProgressProps { - interval: number; -} - -const ScrubberProgress = ({ interval }: ScrubberProgressProps) => { - return ; -}; - -export default ScrubberProgress; - -const fillAnimation = keyframes` - 0% { - background-position: right; - } - 100% { - background-position: left; - } -`; - -const PlayingBoxBackground = styled.div<{ interval: number }>` - pointer-events: none; - - position: absolute; - z-index: 0; - left: 50%; - transform: translateX(-50%); - - width: 150px; - height: 50px; - - background: linear-gradient(to left, transparent 50%, pink 50%); - background-size: ${({ interval }) => 200 + (30 - interval)}%; - border-radius: 5px; - - transition: 10s linear; - animation: ${fillAnimation} ${({ interval }) => interval}s linear infinite; -`; - -export const ScrubberProgressAllPlaying = () => { - return ; -}; - -const animate = keyframes` - 0%, 100% { - clip-path: polygon( - 0% 45%, - 16% 44%, - 33% 50%, - 54% 60%, - 70% 61%, - 84% 59%, - 100% 52%, - 100% 100%, - 0% 100% - ); - } - - 50% { - clip-path: polygon( - 0% 60%, - 15% 65%, - 34% 66%, - 51% 62%, - 67% 50%, - 84% 45%, - 100% 46%, - 100% 100%, - 0% 100% - ); - } -`; - -const AllPlayingBackground = styled.div` - pointer-events: none; - - position: absolute; - z-index: 0; - left: 50%; - transform: translateX(-50%); - - width: 150px; - height: 50px; - - background: linear-gradient(to left, deeppink, pink); - border-radius: 5px; - - transition: 10s linear; - animation: ${animate} 4s ease-in-out infinite; -`; diff --git a/frontend/src/features/youtube/components/SoundWave.tsx b/frontend/src/features/youtube/components/SoundWave.tsx index 85b7041d7..715a1423f 100644 --- a/frontend/src/features/youtube/components/SoundWave.tsx +++ b/frontend/src/features/youtube/components/SoundWave.tsx @@ -1,23 +1,24 @@ import styled from 'styled-components'; interface SoundWaveProps { - waveLength: number; + length: number; } - -const SoundWave = ({ waveLength }: SoundWaveProps) => { - return Array.from({ length: waveLength }, () => ( - <> - - - - )); +const SoundWave = ({ length }: SoundWaveProps) => { + return Array.from({ length }, (_, index) => { + return ( + <> + + + + ); + }); }; export default SoundWave; const LongBar = styled.div` z-index: 2; - left: calc(50%); + left: 50%; width: 4px; height: 24px; diff --git a/frontend/src/features/youtube/components/WaveScrubber.tsx b/frontend/src/features/youtube/components/WaveScrubber.tsx index 3e2b965dd..970c1e67f 100644 --- a/frontend/src/features/youtube/components/WaveScrubber.tsx +++ b/frontend/src/features/youtube/components/WaveScrubber.tsx @@ -1,126 +1,70 @@ -import { useRef } from 'react'; -import styled from 'styled-components'; +import styled, { keyframes } from 'styled-components'; import useVoteInterfaceContext from '@/features/songs/hooks/useVoteInterfaceContext'; -import PlayerBadge from '@/features/youtube/components/PlayerBadge'; -import PlayingToggleButton from '@/features/youtube/components/PlayingToggleButton'; -import ScrubberProgress, { - ScrubberProgressAllPlaying, -} from '@/features/youtube/components/ScrubberProgress'; import SoundWave from '@/features/youtube/components/SoundWave'; import useVideoPlayerContext from '@/features/youtube/hooks/useVideoPlayerContext'; import Flex from '@/shared/components/Flex/Flex'; -import Spacing from '@/shared/components/Spacing'; import useDebounceEffect from '@/shared/hooks/useDebounceEffect'; -import { secondsToMinSec, toMinSecText } from '@/shared/utils/convertTime'; +import { secondsToMinSec } from '@/shared/utils/convertTime'; -const PROGRESS_WIDTH = 350; const WaveScrubber = () => { const { - interval, partStartTime, + interval, videoLength, isVideoStatePlaying, isAllPlay, updatePartStartTime, - toggleAllPlay, } = useVoteInterfaceContext(); const { videoPlayer, seekTo } = useVideoPlayerContext(); - const temptKey = useRef(0); const maxPartStartTime = videoLength - interval; - const seekAndPlay = () => { - seekTo(partStartTime); - }; - - const partStartTimeText = toMinSecText(partStartTime); const changePartStartTime: React.UIEventHandler = (e) => { const { scrollWidth, scrollLeft } = e.currentTarget; - const unit = (scrollWidth - PROGRESS_WIDTH) / maxPartStartTime; + const unit = (scrollWidth - 350) / maxPartStartTime; const partStartTimeToChange = Math.floor(scrollLeft / unit); if (partStartTimeToChange >= 0 && partStartTimeToChange <= maxPartStartTime) { const { minute, second } = secondsToMinSec(partStartTimeToChange); updatePartStartTime('minute', minute); updatePartStartTime('second', second); - temptKey.current += temptKey.current + 1; } }; - const clickPlay = () => { - if (isAllPlay) { - videoPlayer.current?.playVideo(); - } else { - seekTo(partStartTime); - } - }; - - const clickPause = () => { - videoPlayer.current?.pauseVideo(); - }; - const playWhenTouch = () => { if (!isVideoStatePlaying) { videoPlayer.current?.playVideo(); } }; - useDebounceEffect<[number, number]>(seekAndPlay, [partStartTime, interval], 300); + useDebounceEffect<[number, number]>(() => seekTo(partStartTime), [partStartTime, interval], 300); return ( <> - - {partStartTimeText} - - - - - - - - - - - - - {isVideoStatePlaying && !isAllPlay && } - {isVideoStatePlaying && isAllPlay && } + + + + {isVideoStatePlaying && !isAllPlay && } + {isVideoStatePlaying && isAllPlay && } ); }; export default WaveScrubber; - -const PlayingBox = styled.div` - position: absolute; - z-index: 1; - left: 50%; - transform: translateX(-50%); - - width: 150px; - height: 50px; - - border: transparent; - border-radius: 4px; - box-shadow: 0 0 0 2px inset ${({ theme: { color } }) => color.white}; +const WaveWrapper = styled(Flex)` + z-index: 3; + background-color: transparent; + overflow-x: scroll; + width: 100%; + height: 75px; + padding: 0 calc((100% - 150px) / 2); `; const Container = styled.div` @@ -143,8 +87,92 @@ const Container = styled.div` } `; -const BadgeContainer = styled.div` - display: flex; - column-gap: 14px; - justify-content: flex-end; +const ProgressFrame = styled.div` + position: absolute; + z-index: 1; + left: 50%; + transform: translateX(-50%); + + width: 150px; + height: 50px; + + border: transparent; + border-radius: 4px; + box-shadow: 0 0 0 2px inset ${({ theme: { color } }) => color.white}; +`; + +const fillAnimation = keyframes` + 0% { + background-position: right; + } + 100% { + background-position: left; + } +`; + +const ProgressFill = styled.div<{ $interval: number }>` + pointer-events: none; + + position: absolute; + z-index: 0; + left: 50%; + transform: translateX(-50%); + + width: 150px; + height: 50px; + + background: linear-gradient(to left, transparent 50%, pink 50%); + background-size: ${({ $interval }) => 200 + (30 - $interval)}%; + border-radius: 5px; + + transition: 10s linear; + animation: ${fillAnimation} ${({ $interval }) => $interval}s linear infinite; +`; + +const waveFillAnimate = keyframes` + 0%, 100% { + clip-path: polygon( + 0% 45%, + 16% 44%, + 33% 50%, + 54% 60%, + 70% 61%, + 84% 59%, + 100% 52%, + 100% 100%, + 0% 100% + ); + } + + 50% { + clip-path: polygon( + 0% 60%, + 15% 65%, + 34% 66%, + 51% 62%, + 67% 50%, + 84% 45%, + 100% 46%, + 100% 100%, + 0% 100% + ); + } +`; + +const WaveFill = styled.div` + pointer-events: none; + + position: absolute; + z-index: 0; + left: 50%; + transform: translateX(-50%); + + width: 150px; + height: 50px; + + background: linear-gradient(to left, deeppink, pink); + border-radius: 5px; + + transition: 10s linear; + animation: ${waveFillAnimate} 4s ease-in-out infinite; `; From 3be1447367e65003870ef5b9c0ecaae1f8302e88 Mon Sep 17 00:00:00 2001 From: ukkodeveloper Date: Sat, 14 Oct 2023 20:38:32 +0900 Subject: [PATCH 27/95] =?UTF-8?q?refactor:=20video=20=EC=9E=AC=EC=83=9D=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20badge=20=EC=A0=95=EB=B3=B4=EB=93=A4=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=EB=A1=9C=20=EB=AD=89?= =?UTF-8?q?=EC=B9=98=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../youtube/components/PlayerBadge.tsx | 33 ----------- .../components/PlayingToggleButton.tsx | 34 ----------- .../youtube/components/VideoBadges.tsx | 59 +++++++++++++++++++ 3 files changed, 59 insertions(+), 67 deletions(-) delete mode 100644 frontend/src/features/youtube/components/PlayerBadge.tsx delete mode 100644 frontend/src/features/youtube/components/PlayingToggleButton.tsx create mode 100644 frontend/src/features/youtube/components/VideoBadges.tsx diff --git a/frontend/src/features/youtube/components/PlayerBadge.tsx b/frontend/src/features/youtube/components/PlayerBadge.tsx deleted file mode 100644 index 21d845137..000000000 --- a/frontend/src/features/youtube/components/PlayerBadge.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import styled from 'styled-components'; -import type { PropsWithChildren } from 'react'; - -interface PlayerBadge extends PropsWithChildren { - isActive?: boolean; -} - -const PlayerBadge = ({ isActive = false, children }: PlayerBadge) => { - return {children}; -}; - -export default PlayerBadge; - -const Badge = styled.span<{ $isActive: boolean }>` - display: flex; - align-items: center; - - height: 30px; - padding: 0 10px; - - font-size: 13px; - color: ${({ theme: { color }, $isActive }) => ($isActive ? color.black : color.white)}; - text-align: center; - - background-color: ${({ theme: { color }, $isActive }) => ($isActive ? 'pink' : color.disabled)}; - border-radius: 40px; - - transition: background-color 0.2s ease-in; - - @media (min-width: ${({ theme }) => theme.breakPoints.md}) { - font-size: 14px; - } -`; diff --git a/frontend/src/features/youtube/components/PlayingToggleButton.tsx b/frontend/src/features/youtube/components/PlayingToggleButton.tsx deleted file mode 100644 index 82a0def0a..000000000 --- a/frontend/src/features/youtube/components/PlayingToggleButton.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import styled from 'styled-components'; -import fillPlayIcon from '@/assets/icon/fill-play.svg'; -import pauseIcon from '@/assets/icon/pause.svg'; - -interface PlayingToggleButtonProps { - pause: () => void; - play: () => void; - isPlaying: boolean; -} - -const PlayingToggleButton = ({ pause, play, isPlaying }: PlayingToggleButtonProps) => { - return ( - <> - {isPlaying ? ( - - ) : ( - - )} - - ); -}; - -export default PlayingToggleButton; - -const Button = styled.button` - display: flex; - align-items: center; - justify-content: center; - width: 20px; -`; diff --git a/frontend/src/features/youtube/components/VideoBadges.tsx b/frontend/src/features/youtube/components/VideoBadges.tsx new file mode 100644 index 000000000..ad881009d --- /dev/null +++ b/frontend/src/features/youtube/components/VideoBadges.tsx @@ -0,0 +1,59 @@ +import styled from 'styled-components'; +import playIcon from '@/assets/icon/fill-play.svg'; +import pauseIcon from '@/assets/icon/pause.svg'; +import useVoteInterfaceContext from '@/features/songs/hooks/useVoteInterfaceContext'; +import useVideoPlayerContext from '@/features/youtube/hooks/useVideoPlayerContext'; +import Flex from '@/shared/components/Flex/Flex'; +import { toMinSecText } from '@/shared/utils/convertTime'; + +const VideoBadges = () => { + const { partStartTime, isAllPlay, isVideoStatePlaying, toggleAllPlay } = + useVoteInterfaceContext(); + const { videoPlayer, seekTo } = useVideoPlayerContext(); + const partStartTimeText = toMinSecText(partStartTime); + + const clickPlay = () => { + if (isAllPlay) { + videoPlayer.current?.playVideo(); + } else { + seekTo(partStartTime); + } + }; + const clickPause = () => { + videoPlayer.current?.pauseVideo(); + }; + + return ( + + {partStartTimeText} + + + + + 전체 듣기 + + + ); +}; +export default VideoBadges; + +const Badge = styled.span<{ $isActive?: boolean }>` + display: flex; + align-items: center; + + height: 30px; + padding: 0 10px; + + font-size: 13px; + color: ${({ theme: { color }, $isActive }) => ($isActive ? color.black : color.white)}; + text-align: center; + + background-color: ${({ theme: { color }, $isActive }) => ($isActive ? 'pink' : color.disabled)}; + border-radius: 40px; + + transition: background-color 0.2s ease-in; + + @media (min-width: ${({ theme }) => theme.breakPoints.md}) { + font-size: 14px; + } +`; From 271da9a0da66ff952852b7515a9b35c1f57b9b09 Mon Sep 17 00:00:00 2001 From: ukkodeveloper Date: Sat, 14 Oct 2023 20:40:09 +0900 Subject: [PATCH 28/95] =?UTF-8?q?refactor:=20=EA=B5=AC=EA=B0=84=20?= =?UTF-8?q?=EC=B4=88=20=EC=84=A4=EC=A0=95=ED=95=98=EB=8A=94=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EB=B0=98=EB=B3=B5=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0=20=EB=B0=8F=20=EC=8A=A4=ED=83=80=EC=9D=BC=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...ontroller.tsx => VideoIntervalStepper.tsx} | 51 ++++++++----------- 1 file changed, 20 insertions(+), 31 deletions(-) rename frontend/src/features/songs/components/{PartIntervalController.tsx => VideoIntervalStepper.tsx} (63%) diff --git a/frontend/src/features/songs/components/PartIntervalController.tsx b/frontend/src/features/songs/components/VideoIntervalStepper.tsx similarity index 63% rename from frontend/src/features/songs/components/PartIntervalController.tsx rename to frontend/src/features/songs/components/VideoIntervalStepper.tsx index 4e8cceabf..aa4a976f6 100644 --- a/frontend/src/features/songs/components/PartIntervalController.tsx +++ b/frontend/src/features/songs/components/VideoIntervalStepper.tsx @@ -1,44 +1,42 @@ -import { styled } from 'styled-components'; +import { css, styled } from 'styled-components'; import useVoteInterfaceContext from '@/features/songs/hooks/useVoteInterfaceContext'; +import Flex from '@/shared/components/Flex/Flex'; -const PartIntervalController = () => { +const VideoIntervalStepper = () => { const { interval, plusPartInterval, minusPartInterval } = useVoteInterfaceContext(); return ( - + - - {`${interval} 초`} + {`${interval} 초`} + - + ); }; -export default PartIntervalController; +export default VideoIntervalStepper; -const Container = styled.div` - display: flex; - flex-direction: row; - gap: 20px; - justify-content: center; - - width: 100%; -`; - -const ControlButton = styled.button` +const StepperElementStyle = css` flex: 1; min-width: 50px; margin: 0; padding: 4px 11px; - font-weight: '700'; + font-weight: 700; line-height: 1.8; - color: ${({ theme: { color } }) => color.white}; + text-align: center; - background-color: ${({ theme: { color } }) => color.secondary}; border: none; border-radius: 10px; +`; + +const ControlButton = styled.button` + ${StepperElementStyle}; + + color: ${({ theme: { color } }) => color.white}; + background-color: ${({ theme: { color } }) => color.secondary}; &:active { background-color: ${({ theme: { color } }) => color.disabled}; @@ -46,21 +44,12 @@ const ControlButton = styled.button` } `; -const IntervalItem = styled.p` - flex: 1; - - min-width: 50px; - margin: 0; - padding: 4px 11px; +const CountText = styled.p` + ${StepperElementStyle}; - font-weight: '700'; - line-height: 1.8; color: ${({ theme: { color } }) => color.black}; - text-align: center; - background-color: ${({ theme: { color } }) => color.white}; - border: none; - border-radius: 10px; + &:active { box-shadow: 0 0 0 1px inset pink; transition: box-shadow 0.1s ease; From 0d83fa989e92e942c431200b8dc6c7683e64c4e3 Mon Sep 17 00:00:00 2001 From: ukkodeveloper Date: Sat, 14 Oct 2023 20:41:12 +0900 Subject: [PATCH 29/95] =?UTF-8?q?refactor:=20=ED=8E=98=EC=9D=B4=EC=A7=80?= =?UTF-8?q?=20=ED=8C=8C=ED=8A=B8=20=EC=A0=95=ED=95=98=EA=B8=B0=20=EC=95=88?= =?UTF-8?q?=EB=82=B4=20=EB=A9=94=EC=84=B8=EC=A7=80=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=EB=A1=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/CollectingInformation.tsx | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 frontend/src/features/killingParts/components/CollectingInformation.tsx diff --git a/frontend/src/features/killingParts/components/CollectingInformation.tsx b/frontend/src/features/killingParts/components/CollectingInformation.tsx new file mode 100644 index 000000000..91b0441cc --- /dev/null +++ b/frontend/src/features/killingParts/components/CollectingInformation.tsx @@ -0,0 +1,33 @@ +import { styled } from 'styled-components'; + +const CollectingInformation = () => { + return ( + + 나만의 파트 저장하기 + 같은 파트에 대한 중복 등록은 한 번의 등록으로 처리됩니다. + + ); +}; + +export default CollectingInformation; + +const Container = styled.div``; + +const RegisterTitle = styled.p` + font-size: 18px; + font-weight: 700; + color: ${({ theme: { color } }) => color.white}; + + @media (min-width: ${({ theme }) => theme.breakPoints.md}) { + font-size: 22px; + } +`; + +const Warning = styled.div` + font-size: 14px; + color: ${({ theme: { color } }) => color.subText}; + + @media (min-width: ${({ theme }) => theme.breakPoints.md}) { + font-size: 18px; + } +`; From b85f8b88750445159eeb7e20fd1d6f96935ed8b6 Mon Sep 17 00:00:00 2001 From: ukkodeveloper Date: Sat, 14 Oct 2023 20:41:56 +0900 Subject: [PATCH 30/95] =?UTF-8?q?refactor:=20=EB=93=B1=EB=A1=9D=20?= =?UTF-8?q?=EC=9A=94=EC=B2=AD=ED=95=98=EB=8A=94=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../killingParts/components/RegisterPart.tsx | 122 ++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 frontend/src/features/killingParts/components/RegisterPart.tsx diff --git a/frontend/src/features/killingParts/components/RegisterPart.tsx b/frontend/src/features/killingParts/components/RegisterPart.tsx new file mode 100644 index 000000000..9d7a17436 --- /dev/null +++ b/frontend/src/features/killingParts/components/RegisterPart.tsx @@ -0,0 +1,122 @@ +import styled from 'styled-components'; +import { useAuthContext } from '@/features/auth/components/AuthProvider'; +import useVoteInterfaceContext from '@/features/songs/hooks/useVoteInterfaceContext'; +import { usePostKillingPart } from '@/features/songs/remotes/usePostKillingPart'; +import useVideoPlayerContext from '@/features/youtube/hooks/useVideoPlayerContext'; +import useModal from '@/shared/components/Modal/hooks/useModal'; +import Modal from '@/shared/components/Modal/Modal'; +import useToastContext from '@/shared/components/Toast/hooks/useToastContext'; +import { toPlayingTimeText } from '@/shared/utils/convertTime'; +import copyClipboard from '@/shared/utils/copyClipBoard'; + +const RegisterPart = () => { + const { isOpen, openModal, closeModal } = useModal(); + const { showToast } = useToastContext(); + const { user } = useAuthContext(); + const { interval, partStartTime, songId, songVideoId } = useVoteInterfaceContext(); + const { createKillingPart } = usePostKillingPart(); + const { videoPlayer } = useVideoPlayerContext(); + + const submitKillingPart = async () => { + videoPlayer.current?.pauseVideo(); + await createKillingPart(songId, { startSecond: partStartTime, length: interval }); + openModal(); + }; + + const voteTimeText = toPlayingTimeText(partStartTime, partStartTime + interval); + + const copyPartVideoUrl = async () => { + await copyClipboard(`https://www.youtube.com/watch?v=${songVideoId}`); + closeModal(); + showToast('클립보드에 영상링크가 복사되었습니다.'); + }; + + return ( + <> + 등록 + + + {user?.nickname}님의 + 킬링파트 등록을 완료했습니다. + + + {voteTimeText} + 파트를 공유해 보세요😀 + + + + 확인 + + + 공유하기 + + + + + ); +}; + +export default RegisterPart; + +const RegisterButton = styled.button` + width: 100%; + margin-top: 8px; + padding: 8px 11px; + + font-weight: 700; + color: ${({ theme: { color } }) => color.white}; + letter-spacing: 6px; + + background-color: ${({ theme: { color } }) => color.primary}; + border-radius: 6px; + + @media (min-width: ${({ theme }) => theme.breakPoints.md}) { + position: static; + left: unset; + transform: unset; + + width: 100%; + padding: 11px 15px; + + font-size: 18px; + } +`; + +const ModalTitle = styled.h3``; + +const TitleColumn = styled.div` + text-align: center; +`; + +const ModalContent = styled.div` + padding: 16px 0; + + font-size: 16px; + color: #b5b3bc; + text-align: center; + white-space: pre-line; +`; + +const Message = styled.div``; + +const Button = styled.button` + height: 36px; + color: ${({ theme: { color } }) => color.white}; + border-radius: 10px; +`; + +const Confirm = styled(Button)` + flex: 1; + background-color: ${({ theme: { color } }) => color.secondary}; +`; + +const Share = styled(Button)` + flex: 1; + background-color: ${({ theme: { color } }) => color.primary}; +`; + +const ButtonContainer = styled.div` + display: flex; + gap: 16px; + width: 100%; +`; From dfa6528163323854a0c88757eaf9fb5e182080e3 Mon Sep 17 00:00:00 2001 From: ukkodeveloper Date: Sat, 14 Oct 2023 20:43:47 +0900 Subject: [PATCH 31/95] =?UTF-8?q?refactor:=20=EC=95=A8=EB=B2=94=20?= =?UTF-8?q?=EC=9E=90=EC=BC=93,=20=EA=B0=80=EC=88=98,=20=EC=A0=9C=EB=AA=A9?= =?UTF-8?q?=20=EC=BB=A8=ED=85=90=EC=B8=A0=EB=A5=BC=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=EB=A1=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../songs/components/SongInformation.tsx | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 frontend/src/features/songs/components/SongInformation.tsx diff --git a/frontend/src/features/songs/components/SongInformation.tsx b/frontend/src/features/songs/components/SongInformation.tsx new file mode 100644 index 000000000..979b0f64b --- /dev/null +++ b/frontend/src/features/songs/components/SongInformation.tsx @@ -0,0 +1,56 @@ +import { styled } from 'styled-components'; +import Thumbnail from '@/features/songs/components/Thumbnail'; +import Flex from '@/shared/components/Flex/Flex'; + +interface SongInformationProps { + albumCoverUrl: string; + title: string; + singer: string; +} + +const SongInformation = ({ albumCoverUrl, title, singer }: SongInformationProps) => { + return ( + + + + {title} + {singer} + + + ); +}; + +export default SongInformation; + +const SongTextContainer = styled.div` + overflow: hidden; + max-width: 100%; +`; + +const SongTitle = styled.p` + overflow: hidden; + + font-size: 22px; + font-weight: 700; + color: ${({ theme: { color } }) => color.white}; + text-overflow: ellipsis; + white-space: nowrap; + + @media (max-width: ${({ theme }) => theme.breakPoints.md}) { + font-size: 18px; + } +`; + +const Singer = styled.p` + overflow: hidden; + + font-size: 16px; + font-weight: 700; + color: ${({ theme: { color } }) => color.subText}; + text-overflow: ellipsis; + white-space: nowrap; + + @media (max-width: ${({ theme }) => theme.breakPoints.md}) { + font-size: 14px; + } +`; From ed1a7627d028f18b841a18dfbfc0ecf21b8c5b50 Mon Sep 17 00:00:00 2001 From: ukkodeveloper Date: Sat, 14 Oct 2023 20:44:31 +0900 Subject: [PATCH 32/95] =?UTF-8?q?refactor:=20=EB=B9=84=EB=94=94=EC=96=B4?= =?UTF-8?q?=EB=A5=BC=20=EC=A0=9C=EC=96=B4=ED=95=98=EA=B3=A0=20=ED=8C=8C?= =?UTF-8?q?=ED=8A=B8=EB=A5=BC=20=EA=B3=A0=EB=A5=B4=EB=8A=94=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../youtube/components/VideoController.tsx | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 frontend/src/features/youtube/components/VideoController.tsx diff --git a/frontend/src/features/youtube/components/VideoController.tsx b/frontend/src/features/youtube/components/VideoController.tsx new file mode 100644 index 000000000..24d82aa50 --- /dev/null +++ b/frontend/src/features/youtube/components/VideoController.tsx @@ -0,0 +1,18 @@ +import VideoIntervalStepper from '@/features/songs/components/VideoIntervalStepper'; +import VideoBadges from '@/features/youtube/components/VideoBadges'; +import WaveScrubber from '@/features/youtube/components/WaveScrubber'; +import Spacing from '@/shared/components/Spacing'; + +const VideoController = () => { + return ( +
+ + + + + +
+ ); +}; + +export default VideoController; From 7ec406c678059982dd8183a5e6651235f17f8fb3 Mon Sep 17 00:00:00 2001 From: ukkodeveloper Date: Sat, 14 Oct 2023 20:45:20 +0900 Subject: [PATCH 33/95] =?UTF-8?q?feat:=20=ED=8E=98=EC=9D=B4=EC=A7=80=20?= =?UTF-8?q?=EB=8B=A8=EC=97=90=EC=84=9C=20=EC=BB=B4=ED=8F=AC=EB=84=8C?= =?UTF-8?q?=ED=8A=B8=20=EC=9C=84=EA=B3=84=EA=B0=80=20=EB=B3=B4=EC=9D=B4?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../songs/components/VoteInterface.tsx | 178 ------------------ frontend/src/pages/PartCollectingPage.tsx | 92 +++------ 2 files changed, 31 insertions(+), 239 deletions(-) delete mode 100644 frontend/src/features/songs/components/VoteInterface.tsx diff --git a/frontend/src/features/songs/components/VoteInterface.tsx b/frontend/src/features/songs/components/VoteInterface.tsx deleted file mode 100644 index 737338bf4..000000000 --- a/frontend/src/features/songs/components/VoteInterface.tsx +++ /dev/null @@ -1,178 +0,0 @@ -import { styled } from 'styled-components'; -import { useAuthContext } from '@/features/auth/components/AuthProvider'; -import PartIntervalController from '@/features/songs/components/PartIntervalController'; -import useVoteInterfaceContext from '@/features/songs/hooks/useVoteInterfaceContext'; -import WaveScrubber from '@/features/youtube/components/WaveScrubber'; -import useVideoPlayerContext from '@/features/youtube/hooks/useVideoPlayerContext'; -import useModal from '@/shared/components/Modal/hooks/useModal'; -import Modal from '@/shared/components/Modal/Modal'; -import Spacing from '@/shared/components/Spacing'; -import useToastContext from '@/shared/components/Toast/hooks/useToastContext'; -import { toPlayingTimeText } from '@/shared/utils/convertTime'; -import copyClipboard from '@/shared/utils/copyClipBoard'; -import { usePostKillingPart } from '../remotes/usePostKillingPart'; - -const VoteInterface = () => { - const { showToast } = useToastContext(); - const { interval, partStartTime, songId, songVideoId } = useVoteInterfaceContext(); - const { videoPlayer } = useVideoPlayerContext(); - - const { createKillingPart } = usePostKillingPart(); - const { isOpen, openModal, closeModal } = useModal(); - - const { user } = useAuthContext(); - - const voteTimeText = toPlayingTimeText(partStartTime, partStartTime + interval); - - const submitKillingPart = async () => { - videoPlayer.current?.pauseVideo(); - await createKillingPart(songId, { startSecond: partStartTime, length: interval }); - openModal(); - }; - - const copyPartVideoUrl = async () => { - await copyClipboard(`https://www.youtube.com/watch?v=${songVideoId}`); - closeModal(); - showToast('클립보드에 영상링크가 복사되었습니다.'); - }; - - return ( - - 나만의 파트 저장하기 - 같은 파트에 대한 중복 등록은 한 번의 등록으로 처리됩니다. - - - - - - - 등록 - - - - {user?.nickname}님의 - 킬링파트 등록을 완료했습니다. - - - {voteTimeText} - 파트를 공유해 보세요😀 - - - - 확인 - - - 공유하기 - - - - - ); -}; - -export default VoteInterface; - -const Container = styled.div` - display: flex; - flex-direction: column; - width: 100%; - - @media (min-width: ${({ theme }) => theme.breakPoints.md}) { - width: 320px; - padding: 16px; - } -`; - -const RegisterTitle = styled.p` - font-size: 18px; - font-weight: 700; - color: ${({ theme: { color } }) => color.white}; - - @media (min-width: ${({ theme }) => theme.breakPoints.md}) { - font-size: 22px; - } -`; - -const Register = styled.button` - position: fixed; - bottom: 8px; - left: 50%; - transform: translate(-50%, 0); - - width: 92%; - margin-top: 8px; - padding: 6px 11px; - - font-weight: 700; - color: ${({ theme: { color } }) => color.white}; - letter-spacing: 6px; - - background-color: ${({ theme: { color } }) => color.primary}; - border-radius: 10px; - - @media (min-width: ${({ theme }) => theme.breakPoints.md}) { - position: static; - left: unset; - transform: unset; - - width: 100%; - padding: 11px 15px; - - font-size: 18px; - } -`; - -const ModalTitle = styled.h3``; - -const TitleColumn = styled.div` - text-align: center; -`; - -const ModalContent = styled.div` - padding: 16px 0; - - font-size: 16px; - color: #b5b3bc; - text-align: center; - white-space: pre-line; -`; - -const Message = styled.div``; - -const Button = styled.button` - height: 36px; - color: ${({ theme: { color } }) => color.white}; - border-radius: 10px; -`; - -const Confirm = styled(Button)` - flex: 1; - background-color: ${({ theme: { color } }) => color.secondary}; -`; - -const Share = styled(Button)` - flex: 1; - background-color: ${({ theme: { color } }) => color.primary}; -`; - -const ButtonContainer = styled.div` - display: flex; - gap: 16px; - width: 100%; -`; - -const Warning = styled.div` - font-size: 14px; - color: ${({ theme: { color } }) => color.subText}; - - @media (min-width: ${({ theme }) => theme.breakPoints.md}) { - font-size: 18px; - } -`; - -const FlexibleSpacing = styled.div` - @media (min-width: ${({ theme }) => theme.breakPoints.md}) { - flex: 1; - margin: 8px 0; - } -`; diff --git a/frontend/src/pages/PartCollectingPage.tsx b/frontend/src/pages/PartCollectingPage.tsx index 17fa09cec..9a845053a 100644 --- a/frontend/src/pages/PartCollectingPage.tsx +++ b/frontend/src/pages/PartCollectingPage.tsx @@ -1,18 +1,20 @@ import { useParams } from 'react-router-dom'; import { styled } from 'styled-components'; -import Thumbnail from '@/features/songs/components/Thumbnail'; -import VoteInterface from '@/features/songs/components/VoteInterface'; +import CollectingInformation from '@/features/killingParts/components/CollectingInformation'; +import RegisterPart from '@/features/killingParts/components/RegisterPart'; +import SongInformation from '@/features/songs/components/SongInformation'; import { VoteInterfaceProvider } from '@/features/songs/components/VoteInterfaceProvider'; +import VideoController from '@/features/youtube/components/VideoController'; import { VideoPlayerProvider } from '@/features/youtube/components/VideoPlayerProvider'; import Youtube from '@/features/youtube/components/Youtube'; import Flex from '@/shared/components/Flex/Flex'; -import Spacing from '@/shared/components/Spacing'; import useFetch from '@/shared/hooks/useFetch'; import fetcher from '@/shared/remotes'; import type { VotingSongList } from '@/shared/types/song'; const PartCollectingPage = () => { const { id: songId } = useParams(); + // TODO: 조회 API 만들어야함. const { data: votingSongs } = useFetch(() => fetcher(`/voting-songs/${songId}`, 'GET') ); @@ -21,27 +23,23 @@ const PartCollectingPage = () => { const { id, title, singer, videoLength, songVideoId, albumCoverUrl } = votingSongs.currentSong; return ( - - - - - - - - {title} - {singer} - - - - - - - - - - - - + + + + + + + + + + + + + + + + + ); }; @@ -65,47 +63,19 @@ const Container = styled.section` } `; -const SongInfoContainer = styled.div` - display: flex; - gap: 12px; - align-items: center; -`; - -const Info = styled.div` - overflow: hidden; - flex: 1; -`; - -const SongTitle = styled.p` - overflow: hidden; - - font-size: 22px; - font-weight: 700; - color: ${({ theme: { color } }) => color.white}; - text-overflow: ellipsis; - white-space: nowrap; - - @media (max-width: ${({ theme }) => theme.breakPoints.md}) { - font-size: 18px; +const FlexControlInterface = styled(Flex)` + @media (min-width: ${({ theme }) => theme.breakPoints.md}) { + width: 320px; + padding: 16px; } `; -const Singer = styled.p` - overflow: hidden; - - font-size: 16px; - font-weight: 700; - color: ${({ theme: { color } }) => color.subText}; - text-overflow: ellipsis; - white-space: nowrap; - - @media (max-width: ${({ theme }) => theme.breakPoints.md}) { - font-size: 14px; - } -`; - -const FlexWrapper = styled(Flex)` +const FlexPage = styled(Flex)` padding: 10px; background-color: ${({ theme: { color } }) => color.black300}; border-radius: 8px; `; + +const FlexPlayer = styled(Flex)` + flex: 1; +`; From 7780231bba47763239dda0ab5c65537a7f7b229e Mon Sep 17 00:00:00 2001 From: ukkodeveloper Date: Sun, 15 Oct 2023 16:23:13 +0900 Subject: [PATCH 34/95] =?UTF-8?q?design:=20=EB=8D=B0=EC=8A=A4=ED=81=AC?= =?UTF-8?q?=ED=83=91=20=EC=82=AC=EC=9D=B4=EC=A6=88=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EB=B2=84=ED=8A=BC=20=ED=95=98=EB=8B=A8=EC=97=90=20=EC=98=A4?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/features/killingParts/components/RegisterPart.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/features/killingParts/components/RegisterPart.tsx b/frontend/src/features/killingParts/components/RegisterPart.tsx index 9d7a17436..c0ee11b13 100644 --- a/frontend/src/features/killingParts/components/RegisterPart.tsx +++ b/frontend/src/features/killingParts/components/RegisterPart.tsx @@ -60,7 +60,7 @@ export default RegisterPart; const RegisterButton = styled.button` width: 100%; - margin-top: 8px; + margin-top: auto; padding: 8px 11px; font-weight: 700; From 598fe136ad22821fa336c40d1d47d9122bb6efc0 Mon Sep 17 00:00:00 2001 From: ukkodeveloper Date: Sun, 15 Oct 2023 16:24:23 +0900 Subject: [PATCH 35/95] =?UTF-8?q?design:=20flex=20=EB=B0=8F=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EB=84=8C=ED=8A=B8=20container=20=EB=94=94=EC=9E=90?= =?UTF-8?q?=EC=9D=B8=20=EC=84=B8=EB=B6=80=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../songs/components/VideoIntervalStepper.tsx | 2 +- .../youtube/components/VideoController.tsx | 8 ++- .../youtube/components/WaveScrubber.tsx | 28 +++++----- frontend/src/pages/PartCollectingPage.tsx | 54 ++++++++----------- 4 files changed, 40 insertions(+), 52 deletions(-) diff --git a/frontend/src/features/songs/components/VideoIntervalStepper.tsx b/frontend/src/features/songs/components/VideoIntervalStepper.tsx index aa4a976f6..d22377adb 100644 --- a/frontend/src/features/songs/components/VideoIntervalStepper.tsx +++ b/frontend/src/features/songs/components/VideoIntervalStepper.tsx @@ -6,7 +6,7 @@ const VideoIntervalStepper = () => { const { interval, plusPartInterval, minusPartInterval } = useVoteInterfaceContext(); return ( - + - {`${interval} 초`} + diff --git a/frontend/src/features/youtube/components/VideoController.tsx b/frontend/src/features/youtube/components/VideoController.tsx index 24d82aa50..e94ded1bc 100644 --- a/frontend/src/features/youtube/components/VideoController.tsx +++ b/frontend/src/features/youtube/components/VideoController.tsx @@ -1,17 +1,15 @@ import VideoIntervalStepper from '@/features/songs/components/VideoIntervalStepper'; import VideoBadges from '@/features/youtube/components/VideoBadges'; import WaveScrubber from '@/features/youtube/components/WaveScrubber'; -import Spacing from '@/shared/components/Spacing'; +import Flex from '@/shared/components/Flex/Flex'; const VideoController = () => { return ( -
+ - - -
+
); }; diff --git a/frontend/src/features/youtube/components/WaveScrubber.tsx b/frontend/src/features/youtube/components/WaveScrubber.tsx index 970c1e67f..b49067793 100644 --- a/frontend/src/features/youtube/components/WaveScrubber.tsx +++ b/frontend/src/features/youtube/components/WaveScrubber.tsx @@ -39,21 +39,19 @@ const WaveScrubber = () => { useDebounceEffect<[number, number]>(() => seekTo(partStartTime), [partStartTime, interval], 300); return ( - <> - - - - - - {isVideoStatePlaying && !isAllPlay && } - {isVideoStatePlaying && isAllPlay && } - - + + + + + + {isVideoStatePlaying && !isAllPlay && } + {isVideoStatePlaying && isAllPlay && } + ); }; diff --git a/frontend/src/pages/PartCollectingPage.tsx b/frontend/src/pages/PartCollectingPage.tsx index 9a845053a..8e35f0bd0 100644 --- a/frontend/src/pages/PartCollectingPage.tsx +++ b/frontend/src/pages/PartCollectingPage.tsx @@ -25,19 +25,17 @@ const PartCollectingPage = () => { return ( - - - - - - - - - - - - - + + + + + + + + + + + ); @@ -45,21 +43,18 @@ const PartCollectingPage = () => { export default PartCollectingPage; -const Container = styled.section` - display: flex; - flex: 0.6; - flex-direction: column; - justify-content: center; +const PageFlex = styled(Flex)` + padding: 10px; + background-color: ${({ theme: { color } }) => color.black300}; + border-radius: 8px; width: 100%; - margin-top: ${({ theme: { headerHeight } }) => headerHeight.desktop}; + margin: auto; + transform: translateY(30px); - @media (max-width: ${({ theme }) => theme.breakPoints.xs}) { - margin-top: ${({ theme: { headerHeight } }) => headerHeight.mobile}; - } - - @media (max-width: ${({ theme }) => theme.breakPoints.xxs}) { - margin-top: ${({ theme: { headerHeight } }) => headerHeight.xxs}; + @media (min-width: ${({ theme }) => theme.breakPoints.md}) { + padding: 16px; + transform: translateY(40px); } `; @@ -70,12 +65,9 @@ const FlexControlInterface = styled(Flex)` } `; -const FlexPage = styled(Flex)` - padding: 10px; - background-color: ${({ theme: { color } }) => color.black300}; - border-radius: 8px; -`; - const FlexPlayer = styled(Flex)` flex: 1; + @media (min-width: ${({ theme }) => theme.breakPoints.md}) { + max-width: calc(100% - 320px); + } `; From 20e8cdf392f42fc3283e4bbfc824571e3e7a6cc3 Mon Sep 17 00:00:00 2001 From: ukkodeveloper Date: Sun, 15 Oct 2023 16:25:18 +0900 Subject: [PATCH 36/95] =?UTF-8?q?design:=20=EA=B8=B4=20=EA=B8=80=EC=9E=90?= =?UTF-8?q?=20=EC=98=AC=20=EA=B2=BD=EC=9A=B0=20=EB=8C=80=EB=B9=84=ED=95=98?= =?UTF-8?q?=EC=97=AC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/features/songs/components/SongInformation.tsx | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/frontend/src/features/songs/components/SongInformation.tsx b/frontend/src/features/songs/components/SongInformation.tsx index 979b0f64b..62721df30 100644 --- a/frontend/src/features/songs/components/SongInformation.tsx +++ b/frontend/src/features/songs/components/SongInformation.tsx @@ -23,27 +23,24 @@ const SongInformation = ({ albumCoverUrl, title, singer }: SongInformationProps) export default SongInformation; const SongTextContainer = styled.div` - overflow: hidden; - max-width: 100%; + max-width: calc(100% - 60px); `; const SongTitle = styled.p` overflow: hidden; - - font-size: 22px; + font-size: 18px; font-weight: 700; color: ${({ theme: { color } }) => color.white}; text-overflow: ellipsis; white-space: nowrap; - @media (max-width: ${({ theme }) => theme.breakPoints.md}) { - font-size: 18px; + @media (min-width: ${({ theme }) => theme.breakPoints.md}) { + font-size: 22px; } `; const Singer = styled.p` overflow: hidden; - font-size: 16px; font-weight: 700; color: ${({ theme: { color } }) => color.subText}; From 71f54538a34bde5fcc55b3ffbbbf30a12d39fce2 Mon Sep 17 00:00:00 2001 From: ukkodeveloper Date: Sun, 15 Oct 2023 22:21:50 +0900 Subject: [PATCH 37/95] =?UTF-8?q?chore:=20=EC=82=AC=EC=9A=A9=ED=95=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EB=8A=94=20=EC=BB=B4=ED=8F=AC=EB=84=8C?= =?UTF-8?q?=ED=8A=B8=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/IntervalInput.stories.tsx | 37 ----- .../songs/components/IntervalInput.tsx | 155 ------------------ .../components/KillingPartToggleGroup.tsx | 71 -------- .../youtube/components/VideoSlider.tsx | 129 --------------- 4 files changed, 392 deletions(-) delete mode 100644 frontend/src/features/songs/components/IntervalInput.stories.tsx delete mode 100644 frontend/src/features/songs/components/IntervalInput.tsx delete mode 100644 frontend/src/features/songs/components/KillingPartToggleGroup.tsx delete mode 100644 frontend/src/features/youtube/components/VideoSlider.tsx diff --git a/frontend/src/features/songs/components/IntervalInput.stories.tsx b/frontend/src/features/songs/components/IntervalInput.stories.tsx deleted file mode 100644 index 3e9c6f78f..000000000 --- a/frontend/src/features/songs/components/IntervalInput.stories.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { useState } from 'react'; -import { VideoPlayerProvider } from '@/features/youtube/components/VideoPlayerProvider'; -import IntervalInput from './IntervalInput'; -import { VoteInterfaceProvider } from './VoteInterfaceProvider'; -import type { Meta, StoryObj } from '@storybook/react'; - -const meta = { - component: IntervalInput, - title: 'IntervalInput', - decorators: [ - (Story) => ( - - - - - - ), - ], -} satisfies Meta; - -export default meta; - -type Story = StoryObj; - -const TestIntervalInput = () => { - const [errorMessage, setErrorMessage] = useState(''); - - const onChangeErrorMessage = (message: string) => { - setErrorMessage(message); - }; - - return ; -}; - -export const Default = { - render: () => , -} satisfies Story; diff --git a/frontend/src/features/songs/components/IntervalInput.tsx b/frontend/src/features/songs/components/IntervalInput.tsx deleted file mode 100644 index 7a3a22f56..000000000 --- a/frontend/src/features/songs/components/IntervalInput.tsx +++ /dev/null @@ -1,155 +0,0 @@ -import { useState } from 'react'; -import { css, styled } from 'styled-components'; -import { secondsToMinSec } from '@/shared/utils/convertTime'; -import { isValidMinSec } from '@/shared/utils/validateTime'; -import ERROR_MESSAGE from '../constants/errorMessage'; -import useVoteInterfaceContext from '../hooks/useVoteInterfaceContext'; -import { isInputName } from '../types/IntervalInput.type'; -import type { IntervalInputType } from '../types/IntervalInput.type'; - -export interface IntervalInputProps { - errorMessage: string; - onChangeErrorMessage: (message: string) => void; -} - -const IntervalInput = ({ errorMessage, onChangeErrorMessage }: IntervalInputProps) => { - const { interval, partStartTime, videoLength, updatePartStartTime } = useVoteInterfaceContext(); - - const [activeInput, setActiveInput] = useState(null); - - const partEndTime = partStartTime + interval; - const { minute: startMinute, second: startSecond } = secondsToMinSec(partStartTime); - const { minute: endMinute, second: endSecond } = secondsToMinSec(partEndTime); - - const onChangeIntervalStart: React.ChangeEventHandler = ({ - currentTarget: { name: timeUnit, value, valueAsNumber }, - }) => { - if (!isValidMinSec(value)) { - onChangeErrorMessage(ERROR_MESSAGE.MIN_SEC); - - return; - } - - onChangeErrorMessage(''); - updatePartStartTime(timeUnit, valueAsNumber); - }; - - const onFocusIntervalStart: React.FocusEventHandler = ({ - currentTarget: { name }, - }) => { - if (isInputName(name)) { - setActiveInput(name); - } - }; - - const onBlurIntervalStart = () => { - if (partStartTime + interval > videoLength) { - const { minute: songMin, second: songSec } = secondsToMinSec(videoLength - interval); - - onChangeErrorMessage(ERROR_MESSAGE.SONG_RANGE(songMin, songSec)); - return; - } - - onChangeErrorMessage(''); - setActiveInput(null); - }; - - return ( - - - - : - - ~ - - : - - - {errorMessage} - - ); -}; - -export default IntervalInput; - -const IntervalContainer = styled.div` - display: flex; - flex-direction: column; - justify-content: space-between; - - padding: 0 24px; - - font-size: 16px; - color: ${({ theme: { color } }) => color.white}; -`; - -const Flex = styled.div` - display: flex; -`; - -const ErrorMessage = styled.p` - margin: 8px 0; - font-size: 12px; - color: ${({ theme: { color } }) => color.error}; -`; - -const Separator = styled.span<{ $inactive?: boolean }>` - flex: none; - - margin: 0 8px; - padding-bottom: 8px; - - color: ${({ $inactive, theme: { color } }) => $inactive && color.subText}; - text-align: center; -`; - -const inputBase = css` - flex: 1; - - width: 16px; - margin: 0 8px; - margin: 0; - padding: 0; - - text-align: center; - - background-color: transparent; - border: none; - border-bottom: 1px solid white; - outline: none; - -webkit-box-shadow: none; - box-shadow: none; -`; - -const InputStart = styled.input<{ $active: boolean }>` - ${inputBase} - color: ${({ theme: { color } }) => color.white}; - border-bottom: 1px solid - ${({ $active, theme: { color } }) => ($active ? color.primary : color.white)}; -`; - -const InputEnd = styled.input` - ${inputBase} - color: ${({ theme: { color } }) => color.subText}; - border-bottom: 1px solid ${({ theme: { color } }) => color.subText}; -`; diff --git a/frontend/src/features/songs/components/KillingPartToggleGroup.tsx b/frontend/src/features/songs/components/KillingPartToggleGroup.tsx deleted file mode 100644 index 899a4a86f..000000000 --- a/frontend/src/features/songs/components/KillingPartToggleGroup.tsx +++ /dev/null @@ -1,71 +0,0 @@ -import { styled } from 'styled-components'; -import { KILLING_PART_INTERVAL } from '../constants/killingPartInterval'; -import useVoteInterfaceContext from '../hooks/useVoteInterfaceContext'; - -const KillingPartToggleGroup = () => { - const { interval, updateKillingPartInterval } = useVoteInterfaceContext(); - - return ( - - - {KILLING_PART_INTERVAL.FIVE}초 - - - {KILLING_PART_INTERVAL.TEN}초 - - - {KILLING_PART_INTERVAL.FIFTEEN}초 - - - ); -}; - -export default KillingPartToggleGroup; - -const ToggleGroup = styled.div` - display: flex; - flex-direction: row; - gap: 20px; - justify-content: center; - - width: 100%; -`; - -const ToggleGroupItem = styled.button<{ $active: boolean }>` - cursor: pointer; - - flex: 1; - - min-width: 50px; - height: 30px; - margin: 0; - padding: 0; - - font-weight: ${({ $active }) => ($active ? '700' : '500')}; - color: ${({ $active, theme: { color } }) => ($active ? color.black : color.white)}; - - background-color: ${({ $active, theme: { color } }) => ($active ? color.white : color.secondary)}; - border: none; - border-radius: 10px; -`; diff --git a/frontend/src/features/youtube/components/VideoSlider.tsx b/frontend/src/features/youtube/components/VideoSlider.tsx deleted file mode 100644 index e69279bd1..000000000 --- a/frontend/src/features/youtube/components/VideoSlider.tsx +++ /dev/null @@ -1,129 +0,0 @@ -import { styled } from 'styled-components'; -import useVoteInterfaceContext from '@/features/songs/hooks/useVoteInterfaceContext'; -import useVideoPlayerContext from '@/features/youtube/hooks/useVideoPlayerContext'; -import { secondsToMinSec, toMinSecText } from '@/shared/utils/convertTime'; -import type { ChangeEventHandler } from 'react'; - -const VideoSlider = () => { - const { interval, partStartTime, videoLength, updatePartStartTime } = useVoteInterfaceContext(); - const { videoPlayer } = useVideoPlayerContext(); - - const partEndTime = partStartTime + interval; - const partStartTimeText = toMinSecText(partStartTime); - const partEndTimeText = toMinSecText(partEndTime); - - const changeTime: ChangeEventHandler = ({ - currentTarget: { valueAsNumber: currentSelectedTime }, - }) => { - const { minute, second } = secondsToMinSec(currentSelectedTime); - - // TODO: 시간 단위 통일 - updatePartStartTime('minute', minute); - updatePartStartTime('second', second); - - videoPlayer.current?.seekTo(currentSelectedTime, false); - }; - - const seekToTime = () => { - videoPlayer.current?.seekTo(partStartTime, true); - videoPlayer.current?.playVideo(); - }; - - return ( - - - {partStartTimeText} - - {partEndTimeText} - - - ); -}; - -export default VideoSlider; - -const SliderWrapper = styled.div` - display: flex; - align-items: center; - justify-content: center; -`; - -export const SliderBox = styled.div` - position: relative; - - display: flex; - align-items: center; - justify-content: center; - - width: 90%; -`; - -export const PartStartTime = styled.span` - position: absolute; - top: -14px; - left: -14px; - - font-size: 14px; - font-weight: 700; -`; - -export const PartEndTime = styled.span` - position: absolute; - top: -14px; - right: -14px; - - font-size: 14px; - font-weight: 700; -`; - -const Slider = styled.input<{ interval: number }>` - cursor: pointer; - - width: 100%; - height: 40px; - - -webkit-appearance: none; - background: transparent; - - &::-webkit-slider-thumb { - position: relative; - top: -4px; - - width: ${({ interval }) => interval * 6}px; - height: 16px; - - -webkit-appearance: none; - background-color: ${({ theme: { color } }) => color.primary}; - border: none; - border-radius: 20px; - } - - &:active { - cursor: grabbing; - } - - &:focus { - outline: none; - } - - // TODO: webkit, moz cross browsing - // https://css-tricks.com/styling-cross-browser-compatible-range-inputs-css/ - &::-webkit-slider-runnable-track { - width: 100%; - height: 8px; - - background-color: gray; - border: none; - border-radius: 5px; - } -`; From 318b79b01d84790014b998532f8c6521fe99f21b Mon Sep 17 00:00:00 2001 From: ukkodeveloper Date: Sun, 15 Oct 2023 22:22:35 +0900 Subject: [PATCH 38/95] =?UTF-8?q?refactor:=20hook=20=EC=A0=95=EB=A6=AC=20?= =?UTF-8?q?=EB=B0=8F=20=EC=9D=B4=EB=A6=84=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../killingParts/components/RegisterPart.tsx | 8 +- .../components/CollectingPartProvider.tsx | 88 ++++++++++++ .../songs/components/VideoIntervalStepper.tsx | 4 +- .../components/VoteInterfaceProvider.tsx | 129 ------------------ .../songs/hooks/useCollectingPartContext.ts | 14 ++ .../songs/hooks/useVoteInterfaceContext.ts | 11 -- .../youtube/components/VideoBadges.tsx | 21 ++- .../components/VideoPlayerProvider.tsx | 2 + .../youtube/components/WaveScrubber.tsx | 33 ++--- frontend/src/pages/PartCollectingPage.tsx | 6 +- 10 files changed, 137 insertions(+), 179 deletions(-) create mode 100644 frontend/src/features/songs/components/CollectingPartProvider.tsx delete mode 100644 frontend/src/features/songs/components/VoteInterfaceProvider.tsx create mode 100644 frontend/src/features/songs/hooks/useCollectingPartContext.ts delete mode 100644 frontend/src/features/songs/hooks/useVoteInterfaceContext.ts diff --git a/frontend/src/features/killingParts/components/RegisterPart.tsx b/frontend/src/features/killingParts/components/RegisterPart.tsx index c0ee11b13..a65ae84e7 100644 --- a/frontend/src/features/killingParts/components/RegisterPart.tsx +++ b/frontend/src/features/killingParts/components/RegisterPart.tsx @@ -1,6 +1,6 @@ import styled from 'styled-components'; import { useAuthContext } from '@/features/auth/components/AuthProvider'; -import useVoteInterfaceContext from '@/features/songs/hooks/useVoteInterfaceContext'; +import useCollectingPartContext from '@/features/songs/hooks/useCollectingPartContext'; import { usePostKillingPart } from '@/features/songs/remotes/usePostKillingPart'; import useVideoPlayerContext from '@/features/youtube/hooks/useVideoPlayerContext'; import useModal from '@/shared/components/Modal/hooks/useModal'; @@ -13,12 +13,12 @@ const RegisterPart = () => { const { isOpen, openModal, closeModal } = useModal(); const { showToast } = useToastContext(); const { user } = useAuthContext(); - const { interval, partStartTime, songId, songVideoId } = useVoteInterfaceContext(); + const { interval, partStartTime, songId, songVideoId } = useCollectingPartContext(); + const video = useVideoPlayerContext(); const { createKillingPart } = usePostKillingPart(); - const { videoPlayer } = useVideoPlayerContext(); const submitKillingPart = async () => { - videoPlayer.current?.pauseVideo(); + video.pause(); await createKillingPart(songId, { startSecond: partStartTime, length: interval }); openModal(); }; diff --git a/frontend/src/features/songs/components/CollectingPartProvider.tsx b/frontend/src/features/songs/components/CollectingPartProvider.tsx new file mode 100644 index 000000000..b2c72b912 --- /dev/null +++ b/frontend/src/features/songs/components/CollectingPartProvider.tsx @@ -0,0 +1,88 @@ +import { createContext, useEffect, useState } from 'react'; +import { MAX_PART_INTERVAL, MIN_PART_INTERVAL } from '@/features/songs/constants/partInterval'; +import useVideoPlayerContext from '@/features/youtube/hooks/useVideoPlayerContext'; +import type { PropsWithChildren } from 'react'; + +interface CollectingPartProviderProps { + videoLength: number; + songId: number; + songVideoId: string; +} + +interface CollectingPartContextProps extends CollectingPartProviderProps { + partStartTime: number; + interval: number; + isPlayingEntire: boolean; + setPartStartTime: React.Dispatch>; + plusPartInterval: () => void; + minusPartInterval: () => void; + toggleEntirePlaying: () => void; +} + +export const CollectingPartContext = createContext(null); +export const CollectingPartProvider = ({ + children, + videoLength, + songId, + songVideoId, +}: PropsWithChildren) => { + const [interval, setInterval] = useState(10); + const [partStartTime, setPartStartTime] = useState(0); + const [isPlayingEntire, setIsPlayingEntire] = useState(false); + const { playerState, seekTo } = useVideoPlayerContext(); + + const toggleEntirePlaying = () => { + setIsPlayingEntire((prev) => !prev); + }; + + const plusPartInterval = () => { + setInterval((prevInterval) => { + const currentInterval = prevInterval + 1; + if (currentInterval >= MIN_PART_INTERVAL && currentInterval <= MAX_PART_INTERVAL) { + return currentInterval; + } + return prevInterval; + }); + }; + + const minusPartInterval = () => { + setInterval((prevInterval) => { + const currentInterval = prevInterval - 1; + if (currentInterval >= MIN_PART_INTERVAL && currentInterval <= MAX_PART_INTERVAL) { + return currentInterval; + } + return prevInterval; + }); + }; + + useEffect(() => { + if (isPlayingEntire || playerState === 2) return; + + const timer = window.setInterval(() => { + seekTo(partStartTime); + }, interval * 1000); + + return () => { + window.clearInterval(timer); + }; + }, [playerState, partStartTime, interval, isPlayingEntire]); + + return ( + + {children} + + ); +}; diff --git a/frontend/src/features/songs/components/VideoIntervalStepper.tsx b/frontend/src/features/songs/components/VideoIntervalStepper.tsx index d22377adb..c22c3900f 100644 --- a/frontend/src/features/songs/components/VideoIntervalStepper.tsx +++ b/frontend/src/features/songs/components/VideoIntervalStepper.tsx @@ -1,9 +1,9 @@ import { css, styled } from 'styled-components'; -import useVoteInterfaceContext from '@/features/songs/hooks/useVoteInterfaceContext'; +import useCollectingPartContext from '@/features/songs/hooks/useCollectingPartContext'; import Flex from '@/shared/components/Flex/Flex'; const VideoIntervalStepper = () => { - const { interval, plusPartInterval, minusPartInterval } = useVoteInterfaceContext(); + const { interval, plusPartInterval, minusPartInterval } = useCollectingPartContext(); return ( diff --git a/frontend/src/features/songs/components/VoteInterfaceProvider.tsx b/frontend/src/features/songs/components/VoteInterfaceProvider.tsx deleted file mode 100644 index beddd51d7..000000000 --- a/frontend/src/features/songs/components/VoteInterfaceProvider.tsx +++ /dev/null @@ -1,129 +0,0 @@ -import { createContext, useEffect, useState } from 'react'; -import { MAX_PART_INTERVAL, MIN_PART_INTERVAL } from '@/features/songs/constants/partInterval'; -import useVideoPlayerContext from '@/features/youtube/hooks/useVideoPlayerContext'; -import type { PropsWithChildren } from 'react'; - -interface VoteInterfaceContextProps extends VoteInterfaceProviderProps { - partStartTime: number; - interval: number; - // NOTE: Why both setState and eventHandler have same naming convention? - updatePartStartTime: (timeUnit: string, value: number) => void; - updateKillingPartInterval: React.MouseEventHandler; - plusPartInterval: () => void; - minusPartInterval: () => void; - toggleAllPlay: () => void; - isAllPlay: boolean; - isVideoStatePlaying: boolean; -} - -export const VoteInterfaceContext = createContext(null); - -interface VoteInterfaceProviderProps { - videoLength: number; - songId: number; - songVideoId: string; -} - -export const VoteInterfaceProvider = ({ - children, - videoLength, - songId, - songVideoId, -}: PropsWithChildren) => { - const [interval, setInterval] = useState(10); - const [partStartTime, setPartStartTime] = useState(0); - const [isAllPlay, setIsAllPlay] = useState(false); - const { videoPlayer, playerState } = useVideoPlayerContext(); - - const isVideoStatePlaying = playerState === 1; - - const updateKillingPartInterval: React.MouseEventHandler = (e) => { - const newInterval = Number(e.currentTarget.dataset['interval']) as number; - const partEndTime = partStartTime + newInterval; - - if (partEndTime > videoLength) { - const overflowedSeconds = partEndTime - videoLength; - setPartStartTime(partStartTime - overflowedSeconds); - } - - setInterval(newInterval); - }; - - const toggleAllPlay = () => { - setIsAllPlay((prev) => !prev); - videoPlayer.current?.playVideo(); - }; - - const plusPartInterval = () => { - setInterval((prevInterval) => { - const currentInterval = prevInterval + 1; - if (currentInterval >= MIN_PART_INTERVAL && currentInterval <= MAX_PART_INTERVAL) { - return currentInterval; - } - return prevInterval; - }); - }; - - const minusPartInterval = () => { - setInterval((prevInterval) => { - const currentInterval = prevInterval - 1; - if (currentInterval >= MIN_PART_INTERVAL && currentInterval <= MAX_PART_INTERVAL) { - return currentInterval; - } - return prevInterval; - }); - }; - - const updatePartStartTime = (timeUnit: string, value: number) => { - if (timeUnit === 'minute') { - setPartStartTime((prev) => { - const minute = 60 * value; - const second = prev % 60; - - return minute + second; - }); - } - - if (timeUnit === 'second') { - setPartStartTime((prev) => { - const minute = 60 * Math.floor(prev / 60); - const second = value; - - return minute + second; - }); - } - }; - - useEffect(() => { - if (isAllPlay) return; - - const timer = window.setInterval(() => { - videoPlayer.current?.seekTo(partStartTime, true); - }, interval * 1000); - - return () => { - window.clearInterval(timer); - }; - }, [playerState, partStartTime, interval, isAllPlay]); - - return ( - - {children} - - ); -}; diff --git a/frontend/src/features/songs/hooks/useCollectingPartContext.ts b/frontend/src/features/songs/hooks/useCollectingPartContext.ts new file mode 100644 index 000000000..185cb4ed8 --- /dev/null +++ b/frontend/src/features/songs/hooks/useCollectingPartContext.ts @@ -0,0 +1,14 @@ +import { useContext } from 'react'; +import { CollectingPartContext } from '../components/CollectingPartProvider'; + +const useCollectingPartContext = () => { + const collectingPartValues = useContext(CollectingPartContext); + + if (!collectingPartValues) { + throw new Error('CollectingPartContext의 value가 제공되지 않았습니다.'); + } + + return { ...collectingPartValues }; +}; + +export default useCollectingPartContext; diff --git a/frontend/src/features/songs/hooks/useVoteInterfaceContext.ts b/frontend/src/features/songs/hooks/useVoteInterfaceContext.ts deleted file mode 100644 index 1e726e7b2..000000000 --- a/frontend/src/features/songs/hooks/useVoteInterfaceContext.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { useContext } from 'react'; -import { VoteInterfaceContext } from '../components/VoteInterfaceProvider'; - -const useVoteInterfaceContext = () => { - const voteInterfaceValues = useContext(VoteInterfaceContext); - if (!voteInterfaceValues) throw new Error('VoteInterfaceContext에 value가 제공되지 않았습니다.'); - - return { ...voteInterfaceValues }; -}; - -export default useVoteInterfaceContext; diff --git a/frontend/src/features/youtube/components/VideoBadges.tsx b/frontend/src/features/youtube/components/VideoBadges.tsx index ad881009d..e649b2224 100644 --- a/frontend/src/features/youtube/components/VideoBadges.tsx +++ b/frontend/src/features/youtube/components/VideoBadges.tsx @@ -1,35 +1,34 @@ import styled from 'styled-components'; import playIcon from '@/assets/icon/fill-play.svg'; import pauseIcon from '@/assets/icon/pause.svg'; -import useVoteInterfaceContext from '@/features/songs/hooks/useVoteInterfaceContext'; +import useCollectingPartContext from '@/features/songs/hooks/useCollectingPartContext'; import useVideoPlayerContext from '@/features/youtube/hooks/useVideoPlayerContext'; import Flex from '@/shared/components/Flex/Flex'; import { toMinSecText } from '@/shared/utils/convertTime'; const VideoBadges = () => { - const { partStartTime, isAllPlay, isVideoStatePlaying, toggleAllPlay } = - useVoteInterfaceContext(); - const { videoPlayer, seekTo } = useVideoPlayerContext(); + const { partStartTime, isPlayingEntire, toggleEntirePlaying } = useCollectingPartContext(); + const video = useVideoPlayerContext(); const partStartTimeText = toMinSecText(partStartTime); const clickPlay = () => { - if (isAllPlay) { - videoPlayer.current?.playVideo(); + if (isPlayingEntire) { + video.play(); } else { - seekTo(partStartTime); + video.seekTo(partStartTime); } }; const clickPause = () => { - videoPlayer.current?.pauseVideo(); + video.pause(); }; return ( {partStartTimeText} - - + + - + 전체 듣기 diff --git a/frontend/src/features/youtube/components/VideoPlayerProvider.tsx b/frontend/src/features/youtube/components/VideoPlayerProvider.tsx index b3f517a46..a98e7de41 100644 --- a/frontend/src/features/youtube/components/VideoPlayerProvider.tsx +++ b/frontend/src/features/youtube/components/VideoPlayerProvider.tsx @@ -4,6 +4,7 @@ import type { PropsWithChildren } from 'react'; interface VideoPlayerContextProps { videoPlayer: React.MutableRefObject; playerState: YT.PlayerState | null; + play: () => void; seekTo: (start: number) => void; pause: () => void; initPlayer: YT.PlayerEventHandler; @@ -43,6 +44,7 @@ export const VideoPlayerProvider = ({ children }: PropsWithChildren) => { videoPlayer, playerState, seekTo, + play, pause, initPlayer, bindUpdatePlayerStateEvent, diff --git a/frontend/src/features/youtube/components/WaveScrubber.tsx b/frontend/src/features/youtube/components/WaveScrubber.tsx index b49067793..75c347803 100644 --- a/frontend/src/features/youtube/components/WaveScrubber.tsx +++ b/frontend/src/features/youtube/components/WaveScrubber.tsx @@ -1,21 +1,14 @@ import styled, { keyframes } from 'styled-components'; -import useVoteInterfaceContext from '@/features/songs/hooks/useVoteInterfaceContext'; +import useCollectingPartContext from '@/features/songs/hooks/useCollectingPartContext'; import SoundWave from '@/features/youtube/components/SoundWave'; import useVideoPlayerContext from '@/features/youtube/hooks/useVideoPlayerContext'; import Flex from '@/shared/components/Flex/Flex'; import useDebounceEffect from '@/shared/hooks/useDebounceEffect'; -import { secondsToMinSec } from '@/shared/utils/convertTime'; const WaveScrubber = () => { - const { - partStartTime, - interval, - videoLength, - isVideoStatePlaying, - isAllPlay, - updatePartStartTime, - } = useVoteInterfaceContext(); - const { videoPlayer, seekTo } = useVideoPlayerContext(); + const { partStartTime, interval, videoLength, setPartStartTime, isPlayingEntire } = + useCollectingPartContext(); + const video = useVideoPlayerContext(); const maxPartStartTime = videoLength - interval; const changePartStartTime: React.UIEventHandler = (e) => { @@ -24,19 +17,21 @@ const WaveScrubber = () => { const partStartTimeToChange = Math.floor(scrollLeft / unit); if (partStartTimeToChange >= 0 && partStartTimeToChange <= maxPartStartTime) { - const { minute, second } = secondsToMinSec(partStartTimeToChange); - updatePartStartTime('minute', minute); - updatePartStartTime('second', second); + setPartStartTime(partStartTimeToChange); } }; const playWhenTouch = () => { - if (!isVideoStatePlaying) { - videoPlayer.current?.playVideo(); + if (video.playerState !== 1) { + video.play(); } }; - useDebounceEffect<[number, number]>(() => seekTo(partStartTime), [partStartTime, interval], 300); + useDebounceEffect<[number, number]>( + () => video.seekTo(partStartTime), + [partStartTime, interval], + 300 + ); return ( @@ -49,8 +44,8 @@ const WaveScrubber = () => { - {isVideoStatePlaying && !isAllPlay && } - {isVideoStatePlaying && isAllPlay && } + {video.playerState === 1 && !isPlayingEntire && } + {video.playerState === 1 && isPlayingEntire && } ); }; diff --git a/frontend/src/pages/PartCollectingPage.tsx b/frontend/src/pages/PartCollectingPage.tsx index 8e35f0bd0..dde91808f 100644 --- a/frontend/src/pages/PartCollectingPage.tsx +++ b/frontend/src/pages/PartCollectingPage.tsx @@ -2,8 +2,8 @@ import { useParams } from 'react-router-dom'; import { styled } from 'styled-components'; import CollectingInformation from '@/features/killingParts/components/CollectingInformation'; import RegisterPart from '@/features/killingParts/components/RegisterPart'; +import { CollectingPartProvider } from '@/features/songs/components/CollectingPartProvider'; import SongInformation from '@/features/songs/components/SongInformation'; -import { VoteInterfaceProvider } from '@/features/songs/components/VoteInterfaceProvider'; import VideoController from '@/features/youtube/components/VideoController'; import { VideoPlayerProvider } from '@/features/youtube/components/VideoPlayerProvider'; import Youtube from '@/features/youtube/components/Youtube'; @@ -24,7 +24,7 @@ const PartCollectingPage = () => { return ( - + @@ -36,7 +36,7 @@ const PartCollectingPage = () => { - + ); }; From f4f614545c9799c6d4e710c3080a55db12245455 Mon Sep 17 00:00:00 2001 From: ukkodeveloper Date: Sun, 15 Oct 2023 23:03:02 +0900 Subject: [PATCH 39/95] =?UTF-8?q?feat:=20=EC=9B=B9=20=EC=A0=91=EA=B7=BC?= =?UTF-8?q?=EC=84=B1=20=EB=B0=8F=20=EC=8B=9C=EB=A9=98=ED=8B=B1=20=ED=83=9C?= =?UTF-8?q?=EA=B7=B8=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/CollectingInformation.tsx | 2 +- .../killingParts/components/RegisterPart.tsx | 4 ++- .../youtube/components/VideoBadges.tsx | 6 ++-- .../youtube/components/WaveScrubber.tsx | 10 ++---- frontend/src/pages/PartCollectingPage.tsx | 34 +++++++++++-------- 5 files changed, 29 insertions(+), 27 deletions(-) diff --git a/frontend/src/features/killingParts/components/CollectingInformation.tsx b/frontend/src/features/killingParts/components/CollectingInformation.tsx index 91b0441cc..a634f1791 100644 --- a/frontend/src/features/killingParts/components/CollectingInformation.tsx +++ b/frontend/src/features/killingParts/components/CollectingInformation.tsx @@ -13,7 +13,7 @@ export default CollectingInformation; const Container = styled.div``; -const RegisterTitle = styled.p` +const RegisterTitle = styled.h2` font-size: 18px; font-weight: 700; color: ${({ theme: { color } }) => color.white}; diff --git a/frontend/src/features/killingParts/components/RegisterPart.tsx b/frontend/src/features/killingParts/components/RegisterPart.tsx index a65ae84e7..300b1d7c6 100644 --- a/frontend/src/features/killingParts/components/RegisterPart.tsx +++ b/frontend/src/features/killingParts/components/RegisterPart.tsx @@ -33,7 +33,9 @@ const RegisterPart = () => { return ( <> - 등록 + + 등록 + {user?.nickname}님의 diff --git a/frontend/src/features/youtube/components/VideoBadges.tsx b/frontend/src/features/youtube/components/VideoBadges.tsx index e649b2224..e328a19ab 100644 --- a/frontend/src/features/youtube/components/VideoBadges.tsx +++ b/frontend/src/features/youtube/components/VideoBadges.tsx @@ -25,10 +25,10 @@ const VideoBadges = () => { return ( {partStartTimeText} - - + + {'재생 - + 전체 듣기 diff --git a/frontend/src/features/youtube/components/WaveScrubber.tsx b/frontend/src/features/youtube/components/WaveScrubber.tsx index 75c347803..1ff33f6f9 100644 --- a/frontend/src/features/youtube/components/WaveScrubber.tsx +++ b/frontend/src/features/youtube/components/WaveScrubber.tsx @@ -13,6 +13,7 @@ const WaveScrubber = () => { const changePartStartTime: React.UIEventHandler = (e) => { const { scrollWidth, scrollLeft } = e.currentTarget; + // ProgressFrame의 width: 350 const unit = (scrollWidth - 350) / maxPartStartTime; const partStartTimeToChange = Math.floor(scrollLeft / unit); @@ -21,7 +22,7 @@ const WaveScrubber = () => { } }; - const playWhenTouch = () => { + const playVideo = () => { if (video.playerState !== 1) { video.play(); } @@ -35,12 +36,7 @@ const WaveScrubber = () => { return ( - + diff --git a/frontend/src/pages/PartCollectingPage.tsx b/frontend/src/pages/PartCollectingPage.tsx index dde91808f..6c9e54c24 100644 --- a/frontend/src/pages/PartCollectingPage.tsx +++ b/frontend/src/pages/PartCollectingPage.tsx @@ -8,6 +8,7 @@ import VideoController from '@/features/youtube/components/VideoController'; import { VideoPlayerProvider } from '@/features/youtube/components/VideoPlayerProvider'; import Youtube from '@/features/youtube/components/Youtube'; import Flex from '@/shared/components/Flex/Flex'; +import SRHeading from '@/shared/components/SRHeading'; import useFetch from '@/shared/hooks/useFetch'; import fetcher from '@/shared/remotes'; import type { VotingSongList } from '@/shared/types/song'; @@ -23,21 +24,24 @@ const PartCollectingPage = () => { const { id, title, singer, videoLength, songVideoId, albumCoverUrl } = votingSongs.currentSong; return ( - - - - - - - - - - - - - - - + <> + 파트 등록 페이지 + + + + + + + + + + + + + + + + ); }; From ac59d228ddef7aeccca1192406c2eed140b7dc46 Mon Sep 17 00:00:00 2001 From: ukkodeveloper Date: Sun, 15 Oct 2023 23:22:21 +0900 Subject: [PATCH 40/95] =?UTF-8?q?fix:=20=EB=93=B1=EB=A1=9D=20=EA=B3=B5?= =?UTF-8?q?=EC=9C=A0=20url=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/features/killingParts/components/RegisterPart.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/frontend/src/features/killingParts/components/RegisterPart.tsx b/frontend/src/features/killingParts/components/RegisterPart.tsx index 300b1d7c6..7cd02b00a 100644 --- a/frontend/src/features/killingParts/components/RegisterPart.tsx +++ b/frontend/src/features/killingParts/components/RegisterPart.tsx @@ -17,6 +17,9 @@ const RegisterPart = () => { const video = useVideoPlayerContext(); const { createKillingPart } = usePostKillingPart(); + // 현재 useMutation 훅이 response 객체를 리턴하지 않고 내부적으로 처리합니다. + // 때문에 컴포넌트 단에서 createKillingPart 성공 여부에 따라 등록 완료 만료를 처리를 할 수 없어요! + // 현재 비로그인 시에 등록을 누르면 두 개의 모달이 뜹니다. const submitKillingPart = async () => { video.pause(); await createKillingPart(songId, { startSecond: partStartTime, length: interval }); @@ -26,7 +29,7 @@ const RegisterPart = () => { const voteTimeText = toPlayingTimeText(partStartTime, partStartTime + interval); const copyPartVideoUrl = async () => { - await copyClipboard(`https://www.youtube.com/watch?v=${songVideoId}`); + await copyClipboard(`https://www.youtube.com/watch?v=${songVideoId}&t=${partStartTime}s`); closeModal(); showToast('클립보드에 영상링크가 복사되었습니다.'); }; From bd026d0b28bcafb4ac189621741dcfb48070e9be Mon Sep 17 00:00:00 2001 From: ukkodeveloper Date: Sun, 15 Oct 2023 23:45:10 +0900 Subject: [PATCH 41/95] =?UTF-8?q?design:=20theme=EC=97=90=20=EB=A7=88?= =?UTF-8?q?=EC=A0=A0=ED=83=80=20=EC=83=89=20=ED=8C=94=EB=A0=88=ED=8A=B8=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=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 --- frontend/src/features/youtube/components/VideoBadges.tsx | 3 ++- .../src/features/youtube/components/WaveScrubber.tsx | 9 ++++++--- frontend/src/shared/styles/theme.ts | 9 +++++++++ 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/frontend/src/features/youtube/components/VideoBadges.tsx b/frontend/src/features/youtube/components/VideoBadges.tsx index e328a19ab..5a65e6f5f 100644 --- a/frontend/src/features/youtube/components/VideoBadges.tsx +++ b/frontend/src/features/youtube/components/VideoBadges.tsx @@ -47,7 +47,8 @@ const Badge = styled.span<{ $isActive?: boolean }>` color: ${({ theme: { color }, $isActive }) => ($isActive ? color.black : color.white)}; text-align: center; - background-color: ${({ theme: { color }, $isActive }) => ($isActive ? 'pink' : color.disabled)}; + background-color: ${({ theme: { color }, $isActive }) => + $isActive ? color.magenta200 : color.disabled}; border-radius: 40px; transition: background-color 0.2s ease-in; diff --git a/frontend/src/features/youtube/components/WaveScrubber.tsx b/frontend/src/features/youtube/components/WaveScrubber.tsx index 1ff33f6f9..3e7c865ea 100644 --- a/frontend/src/features/youtube/components/WaveScrubber.tsx +++ b/frontend/src/features/youtube/components/WaveScrubber.tsx @@ -71,7 +71,7 @@ const Container = styled.div` border-radius: 8px; &:active { - box-shadow: 0 0 0 1px inset pink; + box-shadow: 0 0 0 1px inset ${({ theme: { color } }) => color.magenta300}; transition: box-shadow 0.2s ease; } `; @@ -110,7 +110,9 @@ const ProgressFill = styled.div<{ $interval: number }>` width: 150px; height: 50px; - background: linear-gradient(to left, transparent 50%, pink 50%); + background: ${({ theme: { color } }) => + `linear-gradient(to left, transparent 50%, ${color.magenta300} 50%)`}; + background-size: ${({ $interval }) => 200 + (30 - $interval)}%; border-radius: 5px; @@ -159,7 +161,8 @@ const WaveFill = styled.div` width: 150px; height: 50px; - background: linear-gradient(to left, deeppink, pink); + background: ${({ theme: { color } }) => + `linear-gradient(to left, ${color.magenta100}, ${color.magenta400})`}; border-radius: 5px; transition: 10s linear; diff --git a/frontend/src/shared/styles/theme.ts b/frontend/src/shared/styles/theme.ts index 06172117b..c048866bc 100644 --- a/frontend/src/shared/styles/theme.ts +++ b/frontend/src/shared/styles/theme.ts @@ -19,6 +19,15 @@ const theme = { kakao: '#fee500', google: '#ffffff', }, + magenta100: '#fff0f6', + magenta200: '#ffd6e7', + magenta300: '#ffadd2', + magenta400: '#ff85c0', + magenta500: '#f759ab', + magenta600: '#eb2f96', + magenta700: '#c41d7f', + magenta800: '#9e1068', + magenta900: '#520339', }, breakPoints: { From 225a6886af511bed3d75695b566a659806ceb492 Mon Sep 17 00:00:00 2001 From: ukkodeveloper Date: Sun, 15 Oct 2023 23:51:39 +0900 Subject: [PATCH 42/95] =?UTF-8?q?refactor:=20Player=20=EC=83=81=ED=83=9C?= =?UTF-8?q?=20enum=EA=B0=92=20=EC=A0=81=EC=9A=A9=20=EB=B0=8F=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=ED=95=A8=EC=88=98=EB=AA=85=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../songs/components/CollectingPartProvider.tsx | 2 +- .../src/features/youtube/components/VideoBadges.tsx | 10 ++++++---- .../src/features/youtube/components/WaveScrubber.tsx | 10 ++++++---- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/frontend/src/features/songs/components/CollectingPartProvider.tsx b/frontend/src/features/songs/components/CollectingPartProvider.tsx index b2c72b912..649b6e1fd 100644 --- a/frontend/src/features/songs/components/CollectingPartProvider.tsx +++ b/frontend/src/features/songs/components/CollectingPartProvider.tsx @@ -56,7 +56,7 @@ export const CollectingPartProvider = ({ }; useEffect(() => { - if (isPlayingEntire || playerState === 2) return; + if (isPlayingEntire || playerState === YT.PlayerState.PAUSED) return; const timer = window.setInterval(() => { seekTo(partStartTime); diff --git a/frontend/src/features/youtube/components/VideoBadges.tsx b/frontend/src/features/youtube/components/VideoBadges.tsx index 5a65e6f5f..7889f9cce 100644 --- a/frontend/src/features/youtube/components/VideoBadges.tsx +++ b/frontend/src/features/youtube/components/VideoBadges.tsx @@ -11,22 +11,24 @@ const VideoBadges = () => { const video = useVideoPlayerContext(); const partStartTimeText = toMinSecText(partStartTime); - const clickPlay = () => { + const isPlaying = video.playerState === YT.PlayerState.PLAYING; + + const videoPlay = () => { if (isPlayingEntire) { video.play(); } else { video.seekTo(partStartTime); } }; - const clickPause = () => { + const videoPause = () => { video.pause(); }; return ( {partStartTimeText} - - {'재생 + + {'재생 전체 듣기 diff --git a/frontend/src/features/youtube/components/WaveScrubber.tsx b/frontend/src/features/youtube/components/WaveScrubber.tsx index 3e7c865ea..2ea37c5c4 100644 --- a/frontend/src/features/youtube/components/WaveScrubber.tsx +++ b/frontend/src/features/youtube/components/WaveScrubber.tsx @@ -9,8 +9,10 @@ const WaveScrubber = () => { const { partStartTime, interval, videoLength, setPartStartTime, isPlayingEntire } = useCollectingPartContext(); const video = useVideoPlayerContext(); - const maxPartStartTime = videoLength - interval; + const maxPartStartTime = videoLength - interval; + const isInterval = video.playerState === YT.PlayerState.PLAYING && !isPlayingEntire; + const isEntire = video.playerState === YT.PlayerState.PLAYING && isPlayingEntire; const changePartStartTime: React.UIEventHandler = (e) => { const { scrollWidth, scrollLeft } = e.currentTarget; // ProgressFrame의 width: 350 @@ -23,7 +25,7 @@ const WaveScrubber = () => { }; const playVideo = () => { - if (video.playerState !== 1) { + if (video.playerState === YT.PlayerState.PLAYING) { video.play(); } }; @@ -40,8 +42,8 @@ const WaveScrubber = () => { - {video.playerState === 1 && !isPlayingEntire && } - {video.playerState === 1 && isPlayingEntire && } + {isInterval && } + {isEntire && } ); }; From df40820e808b2b1289b4946c32333a9fa6ba75d4 Mon Sep 17 00:00:00 2001 From: ukkodeveloper Date: Sun, 15 Oct 2023 23:54:50 +0900 Subject: [PATCH 43/95] =?UTF-8?q?style:=20style=20lint=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../features/songs/components/SongInformation.tsx | 2 ++ .../songs/components/VideoIntervalStepper.tsx | 3 --- .../src/features/youtube/components/WaveScrubber.tsx | 6 ++++-- frontend/src/pages/PartCollectingPage.tsx | 12 +++++++----- 4 files changed, 13 insertions(+), 10 deletions(-) diff --git a/frontend/src/features/songs/components/SongInformation.tsx b/frontend/src/features/songs/components/SongInformation.tsx index 62721df30..ebcf3ddc0 100644 --- a/frontend/src/features/songs/components/SongInformation.tsx +++ b/frontend/src/features/songs/components/SongInformation.tsx @@ -28,6 +28,7 @@ const SongTextContainer = styled.div` const SongTitle = styled.p` overflow: hidden; + font-size: 18px; font-weight: 700; color: ${({ theme: { color } }) => color.white}; @@ -41,6 +42,7 @@ const SongTitle = styled.p` const Singer = styled.p` overflow: hidden; + font-size: 16px; font-weight: 700; color: ${({ theme: { color } }) => color.subText}; diff --git a/frontend/src/features/songs/components/VideoIntervalStepper.tsx b/frontend/src/features/songs/components/VideoIntervalStepper.tsx index c22c3900f..e63859a50 100644 --- a/frontend/src/features/songs/components/VideoIntervalStepper.tsx +++ b/frontend/src/features/songs/components/VideoIntervalStepper.tsx @@ -25,7 +25,6 @@ const StepperElementStyle = css` font-weight: 700; line-height: 1.8; - text-align: center; border: none; @@ -34,7 +33,6 @@ const StepperElementStyle = css` const ControlButton = styled.button` ${StepperElementStyle}; - color: ${({ theme: { color } }) => color.white}; background-color: ${({ theme: { color } }) => color.secondary}; @@ -46,7 +44,6 @@ const ControlButton = styled.button` const CountText = styled.p` ${StepperElementStyle}; - color: ${({ theme: { color } }) => color.black}; background-color: ${({ theme: { color } }) => color.white}; diff --git a/frontend/src/features/youtube/components/WaveScrubber.tsx b/frontend/src/features/youtube/components/WaveScrubber.tsx index 2ea37c5c4..b3b85b3e1 100644 --- a/frontend/src/features/youtube/components/WaveScrubber.tsx +++ b/frontend/src/features/youtube/components/WaveScrubber.tsx @@ -51,11 +51,14 @@ const WaveScrubber = () => { export default WaveScrubber; const WaveWrapper = styled(Flex)` z-index: 3; - background-color: transparent; + overflow-x: scroll; + width: 100%; height: 75px; padding: 0 calc((100% - 150px) / 2); + + background-color: transparent; `; const Container = styled.div` @@ -114,7 +117,6 @@ const ProgressFill = styled.div<{ $interval: number }>` background: ${({ theme: { color } }) => `linear-gradient(to left, transparent 50%, ${color.magenta300} 50%)`}; - background-size: ${({ $interval }) => 200 + (30 - $interval)}%; border-radius: 5px; diff --git a/frontend/src/pages/PartCollectingPage.tsx b/frontend/src/pages/PartCollectingPage.tsx index 6c9e54c24..47d8461b5 100644 --- a/frontend/src/pages/PartCollectingPage.tsx +++ b/frontend/src/pages/PartCollectingPage.tsx @@ -48,17 +48,18 @@ const PartCollectingPage = () => { export default PartCollectingPage; const PageFlex = styled(Flex)` - padding: 10px; - background-color: ${({ theme: { color } }) => color.black300}; - border-radius: 8px; + transform: translateY(30px); width: 100%; margin: auto; - transform: translateY(30px); + padding: 10px; + + background-color: ${({ theme: { color } }) => color.black300}; + border-radius: 8px; @media (min-width: ${({ theme }) => theme.breakPoints.md}) { - padding: 16px; transform: translateY(40px); + padding: 16px; } `; @@ -71,6 +72,7 @@ const FlexControlInterface = styled(Flex)` const FlexPlayer = styled(Flex)` flex: 1; + @media (min-width: ${({ theme }) => theme.breakPoints.md}) { max-width: calc(100% - 320px); } From a1f8881961e19219eb0ac57aceda7748780a7aef Mon Sep 17 00:00:00 2001 From: ukkodeveloper Date: Mon, 16 Oct 2023 00:57:23 +0900 Subject: [PATCH 44/95] =?UTF-8?q?refactor:=20Flex=20suffix=EB=A1=9C=20?= =?UTF-8?q?=ED=86=B5=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/pages/PartCollectingPage.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/frontend/src/pages/PartCollectingPage.tsx b/frontend/src/pages/PartCollectingPage.tsx index 47d8461b5..07a57e96e 100644 --- a/frontend/src/pages/PartCollectingPage.tsx +++ b/frontend/src/pages/PartCollectingPage.tsx @@ -29,15 +29,15 @@ const PartCollectingPage = () => { - + - - + + - + @@ -63,14 +63,14 @@ const PageFlex = styled(Flex)` } `; -const FlexControlInterface = styled(Flex)` +const SongPlayerFlex = styled(Flex)` @media (min-width: ${({ theme }) => theme.breakPoints.md}) { width: 320px; padding: 16px; } `; -const FlexPlayer = styled(Flex)` +const ControllerFlex = styled(Flex)` flex: 1; @media (min-width: ${({ theme }) => theme.breakPoints.md}) { From c32c44dc042429c3e2b8b4eafc1cdaa3b98cb110 Mon Sep 17 00:00:00 2001 From: ukkodeveloper Date: Mon, 16 Oct 2023 01:42:07 +0900 Subject: [PATCH 45/95] =?UTF-8?q?refactor:=20color=20theme=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/features/songs/components/VideoIntervalStepper.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/features/songs/components/VideoIntervalStepper.tsx b/frontend/src/features/songs/components/VideoIntervalStepper.tsx index e63859a50..7b49e1978 100644 --- a/frontend/src/features/songs/components/VideoIntervalStepper.tsx +++ b/frontend/src/features/songs/components/VideoIntervalStepper.tsx @@ -48,7 +48,7 @@ const CountText = styled.p` background-color: ${({ theme: { color } }) => color.white}; &:active { - box-shadow: 0 0 0 1px inset pink; + box-shadow: 0 0 0 1px inset ${({ theme: { color } }) => color.magenta300}; transition: box-shadow 0.1s ease; } `; From 676ad409683f29e6c8612e683f92ec0f7c391ec9 Mon Sep 17 00:00:00 2001 From: ukkodeveloper Date: Mon, 16 Oct 2023 11:54:51 +0900 Subject: [PATCH 46/95] =?UTF-8?q?fix:=20=EC=BB=B4=ED=8F=AC=EB=84=8C?= =?UTF-8?q?=ED=8A=B8=20=EB=A0=88=EC=9D=B4=EC=95=84=EC=9B=83=20=EC=9C=A0?= =?UTF-8?q?=ED=8A=9C=EB=B8=8C,=20=EC=BB=A8=ED=8A=B8=EB=A1=A4=EB=9F=AC=20?= =?UTF-8?q?=EB=B6=80=EB=B6=84=20=EC=86=8D=EC=84=B1=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/pages/PartCollectingPage.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/pages/PartCollectingPage.tsx b/frontend/src/pages/PartCollectingPage.tsx index 07a57e96e..6a8af4704 100644 --- a/frontend/src/pages/PartCollectingPage.tsx +++ b/frontend/src/pages/PartCollectingPage.tsx @@ -63,14 +63,14 @@ const PageFlex = styled(Flex)` } `; -const SongPlayerFlex = styled(Flex)` +const ControllerFlex = styled(Flex)` @media (min-width: ${({ theme }) => theme.breakPoints.md}) { width: 320px; padding: 16px; } `; -const ControllerFlex = styled(Flex)` +const SongPlayerFlex = styled(Flex)` flex: 1; @media (min-width: ${({ theme }) => theme.breakPoints.md}) { From b9f75b958fe3d67059b3f44807b458f8d40742cf Mon Sep 17 00:00:00 2001 From: ukkodeveloper Date: Mon, 16 Oct 2023 15:09:14 +0900 Subject: [PATCH 47/95] =?UTF-8?q?feat:=20=ED=94=84=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EB=A0=88=EC=8A=A4=20=EB=B0=94=20=EB=B6=80=EB=93=9C=EB=9F=BD?= =?UTF-8?q?=EA=B2=8C=20=EC=82=AC=EC=9A=A9=ED=95=A0=20=EC=88=98=20=EC=9E=88?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/features/youtube/components/WaveScrubber.tsx | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/frontend/src/features/youtube/components/WaveScrubber.tsx b/frontend/src/features/youtube/components/WaveScrubber.tsx index b3b85b3e1..ed5d61246 100644 --- a/frontend/src/features/youtube/components/WaveScrubber.tsx +++ b/frontend/src/features/youtube/components/WaveScrubber.tsx @@ -43,7 +43,7 @@ const WaveScrubber = () => { {isInterval && } - {isEntire && } + {isPlayingEntire && }
); }; @@ -117,10 +117,9 @@ const ProgressFill = styled.div<{ $interval: number }>` background: ${({ theme: { color } }) => `linear-gradient(to left, transparent 50%, ${color.magenta300} 50%)`}; - background-size: ${({ $interval }) => 200 + (30 - $interval)}%; + background-size: 200%; border-radius: 5px; - transition: 10s linear; animation: ${fillAnimation} ${({ $interval }) => $interval}s linear infinite; `; @@ -154,7 +153,7 @@ const waveFillAnimate = keyframes` } `; -const WaveFill = styled.div` +const WaveFill = styled.div<{ $isRunning: boolean }>` pointer-events: none; position: absolute; @@ -169,6 +168,6 @@ const WaveFill = styled.div` `linear-gradient(to left, ${color.magenta100}, ${color.magenta400})`}; border-radius: 5px; - transition: 10s linear; animation: ${waveFillAnimate} 4s ease-in-out infinite; + animation-play-state: ${({ $isRunning }) => ($isRunning ? 'running' : 'paused')}; `; From 192451b09871b80e914e443df493a9c3def18276 Mon Sep 17 00:00:00 2001 From: ukkodeveloper Date: Mon, 16 Oct 2023 16:15:01 +0900 Subject: [PATCH 48/95] =?UTF-8?q?fix:=20=EC=9E=AC=EC=83=9D,=20=EC=A0=95?= =?UTF-8?q?=EC=A7=80=EB=B2=84=ED=8A=BC=20=EA=B3=84=EC=86=8D=20=EA=B9=9C?= =?UTF-8?q?=EB=B9=A1=EA=B1=B0=EB=A6=AC=EB=8A=94=20=EB=AC=B8=EC=A0=9C=20?= =?UTF-8?q?=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/features/youtube/components/VideoBadges.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/frontend/src/features/youtube/components/VideoBadges.tsx b/frontend/src/features/youtube/components/VideoBadges.tsx index 7889f9cce..b7c80402c 100644 --- a/frontend/src/features/youtube/components/VideoBadges.tsx +++ b/frontend/src/features/youtube/components/VideoBadges.tsx @@ -11,7 +11,7 @@ const VideoBadges = () => { const video = useVideoPlayerContext(); const partStartTimeText = toMinSecText(partStartTime); - const isPlaying = video.playerState === YT.PlayerState.PLAYING; + const isPaused = video.playerState === YT.PlayerState.PAUSED; const videoPlay = () => { if (isPlayingEntire) { @@ -27,8 +27,8 @@ const VideoBadges = () => { return ( {partStartTimeText} - - {'재생 + + {'재생 전체 듣기 @@ -41,8 +41,10 @@ export default VideoBadges; const Badge = styled.span<{ $isActive?: boolean }>` display: flex; align-items: center; + justify-content: center; height: 30px; + min-width: 40px; padding: 0 10px; font-size: 13px; From 1f728904a1af42e54ae57dce8c7b7b1d2b37d2bb Mon Sep 17 00:00:00 2001 From: ukkodeveloper Date: Mon, 16 Oct 2023 16:15:22 +0900 Subject: [PATCH 49/95] =?UTF-8?q?fix:=20Fragment=EC=97=90=20key=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/features/youtube/components/SoundWave.tsx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/frontend/src/features/youtube/components/SoundWave.tsx b/frontend/src/features/youtube/components/SoundWave.tsx index 715a1423f..a5b243491 100644 --- a/frontend/src/features/youtube/components/SoundWave.tsx +++ b/frontend/src/features/youtube/components/SoundWave.tsx @@ -1,3 +1,4 @@ +import React from 'react'; import styled from 'styled-components'; interface SoundWaveProps { @@ -6,10 +7,10 @@ interface SoundWaveProps { const SoundWave = ({ length }: SoundWaveProps) => { return Array.from({ length }, (_, index) => { return ( - <> - - - + + + + ); }); }; From 78eead59275b1b17c72c1c8b0d67a825331e1ace Mon Sep 17 00:00:00 2001 From: ukkodeveloper Date: Mon, 16 Oct 2023 21:55:51 +0900 Subject: [PATCH 50/95] =?UTF-8?q?design:=20HeaderSpacing=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20=EB=B0=8F=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=ED=8C=A8?= =?UTF-8?q?=EB=94=A9=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/pages/PartCollectingPage.tsx | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/frontend/src/pages/PartCollectingPage.tsx b/frontend/src/pages/PartCollectingPage.tsx index 6a8af4704..95730978a 100644 --- a/frontend/src/pages/PartCollectingPage.tsx +++ b/frontend/src/pages/PartCollectingPage.tsx @@ -8,6 +8,7 @@ import VideoController from '@/features/youtube/components/VideoController'; import { VideoPlayerProvider } from '@/features/youtube/components/VideoPlayerProvider'; import Youtube from '@/features/youtube/components/Youtube'; import Flex from '@/shared/components/Flex/Flex'; +import Spacing from '@/shared/components/Spacing'; import SRHeading from '@/shared/components/SRHeading'; import useFetch from '@/shared/hooks/useFetch'; import fetcher from '@/shared/remotes'; @@ -26,6 +27,7 @@ const PartCollectingPage = () => { return ( <> 파트 등록 페이지 + @@ -47,9 +49,13 @@ const PartCollectingPage = () => { export default PartCollectingPage; -const PageFlex = styled(Flex)` - transform: translateY(30px); +const HeaderSpacing = styled(Spacing)` + @media (min-width: ${({ theme }) => theme.breakPoints.xs}) { + min-height: 80px; + } +`; +const PageFlex = styled(Flex)` width: 100%; margin: auto; padding: 10px; @@ -59,14 +65,13 @@ const PageFlex = styled(Flex)` @media (min-width: ${({ theme }) => theme.breakPoints.md}) { transform: translateY(40px); - padding: 16px; + gap: 16px; } `; const ControllerFlex = styled(Flex)` @media (min-width: ${({ theme }) => theme.breakPoints.md}) { width: 320px; - padding: 16px; } `; From 3784a9a9fdbf3471b2e8e35e850bd4d775a76458 Mon Sep 17 00:00:00 2001 From: ukkodeveloper Date: Mon, 16 Oct 2023 22:01:44 +0900 Subject: [PATCH 51/95] =?UTF-8?q?chore:=20=ED=8C=8C=ED=8A=B8=20=EB=93=B1?= =?UTF-8?q?=EB=A1=9D=20=EA=B4=80=EB=A0=A8=20=EB=AA=A8=EB=93=88=20=ED=82=AC?= =?UTF-8?q?=EB=A7=81=ED=8C=8C=ED=8A=B8=20=EB=94=94=EB=A0=89=ED=84=B0?= =?UTF-8?q?=EB=A6=AC=EB=A1=9C=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/features/killingParts/components/RegisterPart.tsx | 4 ++-- .../{youtube => killingParts}/components/SoundWave.tsx | 0 .../{youtube => killingParts}/components/VideoBadges.tsx | 2 +- .../components/VideoController.tsx | 6 +++--- .../components/VideoIntervalStepper.tsx | 2 +- .../{youtube => killingParts}/components/WaveScrubber.tsx | 4 ++-- .../hooks/useCollectingPartContext.ts | 2 +- .../{songs => killingParts}/remotes/usePostKillingPart.ts | 0 frontend/src/pages/PartCollectingPage.tsx | 2 +- 9 files changed, 11 insertions(+), 11 deletions(-) rename frontend/src/features/{youtube => killingParts}/components/SoundWave.tsx (100%) rename frontend/src/features/{youtube => killingParts}/components/VideoBadges.tsx (95%) rename frontend/src/features/{youtube => killingParts}/components/VideoController.tsx (52%) rename frontend/src/features/{songs => killingParts}/components/VideoIntervalStepper.tsx (93%) rename frontend/src/features/{youtube => killingParts}/components/WaveScrubber.tsx (96%) rename frontend/src/features/{songs => killingParts}/hooks/useCollectingPartContext.ts (80%) rename frontend/src/features/{songs => killingParts}/remotes/usePostKillingPart.ts (100%) diff --git a/frontend/src/features/killingParts/components/RegisterPart.tsx b/frontend/src/features/killingParts/components/RegisterPart.tsx index 7cd02b00a..dbe469708 100644 --- a/frontend/src/features/killingParts/components/RegisterPart.tsx +++ b/frontend/src/features/killingParts/components/RegisterPart.tsx @@ -1,7 +1,7 @@ import styled from 'styled-components'; import { useAuthContext } from '@/features/auth/components/AuthProvider'; -import useCollectingPartContext from '@/features/songs/hooks/useCollectingPartContext'; -import { usePostKillingPart } from '@/features/songs/remotes/usePostKillingPart'; +import useCollectingPartContext from '@/features/killingParts/hooks/useCollectingPartContext'; +import { usePostKillingPart } from '@/features/killingParts/remotes/usePostKillingPart'; import useVideoPlayerContext from '@/features/youtube/hooks/useVideoPlayerContext'; import useModal from '@/shared/components/Modal/hooks/useModal'; import Modal from '@/shared/components/Modal/Modal'; diff --git a/frontend/src/features/youtube/components/SoundWave.tsx b/frontend/src/features/killingParts/components/SoundWave.tsx similarity index 100% rename from frontend/src/features/youtube/components/SoundWave.tsx rename to frontend/src/features/killingParts/components/SoundWave.tsx diff --git a/frontend/src/features/youtube/components/VideoBadges.tsx b/frontend/src/features/killingParts/components/VideoBadges.tsx similarity index 95% rename from frontend/src/features/youtube/components/VideoBadges.tsx rename to frontend/src/features/killingParts/components/VideoBadges.tsx index b7c80402c..ec1fa9539 100644 --- a/frontend/src/features/youtube/components/VideoBadges.tsx +++ b/frontend/src/features/killingParts/components/VideoBadges.tsx @@ -1,7 +1,7 @@ import styled from 'styled-components'; import playIcon from '@/assets/icon/fill-play.svg'; import pauseIcon from '@/assets/icon/pause.svg'; -import useCollectingPartContext from '@/features/songs/hooks/useCollectingPartContext'; +import useCollectingPartContext from '@/features/killingParts/hooks/useCollectingPartContext'; import useVideoPlayerContext from '@/features/youtube/hooks/useVideoPlayerContext'; import Flex from '@/shared/components/Flex/Flex'; import { toMinSecText } from '@/shared/utils/convertTime'; diff --git a/frontend/src/features/youtube/components/VideoController.tsx b/frontend/src/features/killingParts/components/VideoController.tsx similarity index 52% rename from frontend/src/features/youtube/components/VideoController.tsx rename to frontend/src/features/killingParts/components/VideoController.tsx index e94ded1bc..9b3a3d311 100644 --- a/frontend/src/features/youtube/components/VideoController.tsx +++ b/frontend/src/features/killingParts/components/VideoController.tsx @@ -1,6 +1,6 @@ -import VideoIntervalStepper from '@/features/songs/components/VideoIntervalStepper'; -import VideoBadges from '@/features/youtube/components/VideoBadges'; -import WaveScrubber from '@/features/youtube/components/WaveScrubber'; +import VideoBadges from '@/features/killingParts/components/VideoBadges'; +import VideoIntervalStepper from '@/features/killingParts/components/VideoIntervalStepper'; +import WaveScrubber from '@/features/killingParts/components/WaveScrubber'; import Flex from '@/shared/components/Flex/Flex'; const VideoController = () => { diff --git a/frontend/src/features/songs/components/VideoIntervalStepper.tsx b/frontend/src/features/killingParts/components/VideoIntervalStepper.tsx similarity index 93% rename from frontend/src/features/songs/components/VideoIntervalStepper.tsx rename to frontend/src/features/killingParts/components/VideoIntervalStepper.tsx index 7b49e1978..77593d6e8 100644 --- a/frontend/src/features/songs/components/VideoIntervalStepper.tsx +++ b/frontend/src/features/killingParts/components/VideoIntervalStepper.tsx @@ -1,5 +1,5 @@ import { css, styled } from 'styled-components'; -import useCollectingPartContext from '@/features/songs/hooks/useCollectingPartContext'; +import useCollectingPartContext from '@/features/killingParts/hooks/useCollectingPartContext'; import Flex from '@/shared/components/Flex/Flex'; const VideoIntervalStepper = () => { diff --git a/frontend/src/features/youtube/components/WaveScrubber.tsx b/frontend/src/features/killingParts/components/WaveScrubber.tsx similarity index 96% rename from frontend/src/features/youtube/components/WaveScrubber.tsx rename to frontend/src/features/killingParts/components/WaveScrubber.tsx index ed5d61246..b9021a8b8 100644 --- a/frontend/src/features/youtube/components/WaveScrubber.tsx +++ b/frontend/src/features/killingParts/components/WaveScrubber.tsx @@ -1,6 +1,6 @@ import styled, { keyframes } from 'styled-components'; -import useCollectingPartContext from '@/features/songs/hooks/useCollectingPartContext'; -import SoundWave from '@/features/youtube/components/SoundWave'; +import SoundWave from '@/features/killingParts/components/SoundWave'; +import useCollectingPartContext from '@/features/killingParts/hooks/useCollectingPartContext'; import useVideoPlayerContext from '@/features/youtube/hooks/useVideoPlayerContext'; import Flex from '@/shared/components/Flex/Flex'; import useDebounceEffect from '@/shared/hooks/useDebounceEffect'; diff --git a/frontend/src/features/songs/hooks/useCollectingPartContext.ts b/frontend/src/features/killingParts/hooks/useCollectingPartContext.ts similarity index 80% rename from frontend/src/features/songs/hooks/useCollectingPartContext.ts rename to frontend/src/features/killingParts/hooks/useCollectingPartContext.ts index 185cb4ed8..0958bbe55 100644 --- a/frontend/src/features/songs/hooks/useCollectingPartContext.ts +++ b/frontend/src/features/killingParts/hooks/useCollectingPartContext.ts @@ -1,5 +1,5 @@ import { useContext } from 'react'; -import { CollectingPartContext } from '../components/CollectingPartProvider'; +import { CollectingPartContext } from '../../songs/components/CollectingPartProvider'; const useCollectingPartContext = () => { const collectingPartValues = useContext(CollectingPartContext); diff --git a/frontend/src/features/songs/remotes/usePostKillingPart.ts b/frontend/src/features/killingParts/remotes/usePostKillingPart.ts similarity index 100% rename from frontend/src/features/songs/remotes/usePostKillingPart.ts rename to frontend/src/features/killingParts/remotes/usePostKillingPart.ts diff --git a/frontend/src/pages/PartCollectingPage.tsx b/frontend/src/pages/PartCollectingPage.tsx index 95730978a..d1cffe94e 100644 --- a/frontend/src/pages/PartCollectingPage.tsx +++ b/frontend/src/pages/PartCollectingPage.tsx @@ -2,9 +2,9 @@ import { useParams } from 'react-router-dom'; import { styled } from 'styled-components'; import CollectingInformation from '@/features/killingParts/components/CollectingInformation'; import RegisterPart from '@/features/killingParts/components/RegisterPart'; +import VideoController from '@/features/killingParts/components/VideoController'; import { CollectingPartProvider } from '@/features/songs/components/CollectingPartProvider'; import SongInformation from '@/features/songs/components/SongInformation'; -import VideoController from '@/features/youtube/components/VideoController'; import { VideoPlayerProvider } from '@/features/youtube/components/VideoPlayerProvider'; import Youtube from '@/features/youtube/components/Youtube'; import Flex from '@/shared/components/Flex/Flex'; From adf5d4c1c4609566c607c83d4bd849f1f5f5a9a6 Mon Sep 17 00:00:00 2001 From: ukkodeveloper Date: Mon, 16 Oct 2023 22:19:37 +0900 Subject: [PATCH 52/95] =?UTF-8?q?refactor:=20interval=20=EC=83=81=ED=83=9C?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20=ED=9B=85=20=EC=BD=94=EB=93=9C=20=EA=B0=9C?= =?UTF-8?q?=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/VideoIntervalStepper.tsx | 6 ++-- .../components/CollectingPartProvider.tsx | 32 +++++++------------ 2 files changed, 15 insertions(+), 23 deletions(-) diff --git a/frontend/src/features/killingParts/components/VideoIntervalStepper.tsx b/frontend/src/features/killingParts/components/VideoIntervalStepper.tsx index 77593d6e8..354f13c31 100644 --- a/frontend/src/features/killingParts/components/VideoIntervalStepper.tsx +++ b/frontend/src/features/killingParts/components/VideoIntervalStepper.tsx @@ -3,13 +3,13 @@ import useCollectingPartContext from '@/features/killingParts/hooks/useCollectin import Flex from '@/shared/components/Flex/Flex'; const VideoIntervalStepper = () => { - const { interval, plusPartInterval, minusPartInterval } = useCollectingPartContext(); + const { interval, increasePartInterval, decreasePartInterval } = useCollectingPartContext(); return ( - - + - {`${interval} 초`} - + + + ); }; diff --git a/frontend/src/features/songs/components/CollectingPartProvider.tsx b/frontend/src/features/songs/components/CollectingPartProvider.tsx index 649b6e1fd..b7e6ee0b5 100644 --- a/frontend/src/features/songs/components/CollectingPartProvider.tsx +++ b/frontend/src/features/songs/components/CollectingPartProvider.tsx @@ -14,8 +14,8 @@ interface CollectingPartContextProps extends CollectingPartProviderProps { interval: number; isPlayingEntire: boolean; setPartStartTime: React.Dispatch>; - plusPartInterval: () => void; - minusPartInterval: () => void; + increasePartInterval: () => void; + decreasePartInterval: () => void; toggleEntirePlaying: () => void; } @@ -35,24 +35,16 @@ export const CollectingPartProvider = ({ setIsPlayingEntire((prev) => !prev); }; - const plusPartInterval = () => { - setInterval((prevInterval) => { - const currentInterval = prevInterval + 1; - if (currentInterval >= MIN_PART_INTERVAL && currentInterval <= MAX_PART_INTERVAL) { - return currentInterval; - } - return prevInterval; - }); + const increasePartInterval = () => { + if (interval === MAX_PART_INTERVAL) return; + + setInterval(interval + 1); }; - const minusPartInterval = () => { - setInterval((prevInterval) => { - const currentInterval = prevInterval - 1; - if (currentInterval >= MIN_PART_INTERVAL && currentInterval <= MAX_PART_INTERVAL) { - return currentInterval; - } - return prevInterval; - }); + const decreasePartInterval = () => { + if (interval === MIN_PART_INTERVAL) return; + + setInterval(interval - 1); }; useEffect(() => { @@ -77,8 +69,8 @@ export const CollectingPartProvider = ({ songVideoId, isPlayingEntire, setPartStartTime, - plusPartInterval, - minusPartInterval, + increasePartInterval, + decreasePartInterval, toggleEntirePlaying, }} > From b63c47ea240ff92fc8b856857a5c1f23e209acd0 Mon Sep 17 00:00:00 2001 From: ukkodeveloper Date: Mon, 16 Oct 2023 22:21:29 +0900 Subject: [PATCH 53/95] =?UTF-8?q?design:=20desktop=20=EA=B8=B0=EC=A4=80=20?= =?UTF-8?q?padding=2016px=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/pages/PartCollectingPage.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/src/pages/PartCollectingPage.tsx b/frontend/src/pages/PartCollectingPage.tsx index d1cffe94e..f25994595 100644 --- a/frontend/src/pages/PartCollectingPage.tsx +++ b/frontend/src/pages/PartCollectingPage.tsx @@ -66,6 +66,7 @@ const PageFlex = styled(Flex)` @media (min-width: ${({ theme }) => theme.breakPoints.md}) { transform: translateY(40px); gap: 16px; + padding: 16px; } `; From ee81ed7ff669aae598c7c3e36b958eabe45d6c79 Mon Sep 17 00:00:00 2001 From: ukkodeveloper Date: Mon, 16 Oct 2023 22:31:29 +0900 Subject: [PATCH 54/95] =?UTF-8?q?refactor:=20useDebounceEffect=20=ED=9B=85?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../features/killingParts/components/WaveScrubber.tsx | 10 +--------- .../songs/components/CollectingPartProvider.tsx | 3 +++ 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/frontend/src/features/killingParts/components/WaveScrubber.tsx b/frontend/src/features/killingParts/components/WaveScrubber.tsx index b9021a8b8..ea6fa7a03 100644 --- a/frontend/src/features/killingParts/components/WaveScrubber.tsx +++ b/frontend/src/features/killingParts/components/WaveScrubber.tsx @@ -3,11 +3,9 @@ import SoundWave from '@/features/killingParts/components/SoundWave'; import useCollectingPartContext from '@/features/killingParts/hooks/useCollectingPartContext'; import useVideoPlayerContext from '@/features/youtube/hooks/useVideoPlayerContext'; import Flex from '@/shared/components/Flex/Flex'; -import useDebounceEffect from '@/shared/hooks/useDebounceEffect'; const WaveScrubber = () => { - const { partStartTime, interval, videoLength, setPartStartTime, isPlayingEntire } = - useCollectingPartContext(); + const { interval, videoLength, setPartStartTime, isPlayingEntire } = useCollectingPartContext(); const video = useVideoPlayerContext(); const maxPartStartTime = videoLength - interval; @@ -30,12 +28,6 @@ const WaveScrubber = () => { } }; - useDebounceEffect<[number, number]>( - () => video.seekTo(partStartTime), - [partStartTime, interval], - 300 - ); - return ( diff --git a/frontend/src/features/songs/components/CollectingPartProvider.tsx b/frontend/src/features/songs/components/CollectingPartProvider.tsx index b7e6ee0b5..40bed9b2f 100644 --- a/frontend/src/features/songs/components/CollectingPartProvider.tsx +++ b/frontend/src/features/songs/components/CollectingPartProvider.tsx @@ -1,6 +1,7 @@ import { createContext, useEffect, useState } from 'react'; import { MAX_PART_INTERVAL, MIN_PART_INTERVAL } from '@/features/songs/constants/partInterval'; import useVideoPlayerContext from '@/features/youtube/hooks/useVideoPlayerContext'; +import useDebounceEffect from '@/shared/hooks/useDebounceEffect'; import type { PropsWithChildren } from 'react'; interface CollectingPartProviderProps { @@ -47,6 +48,8 @@ export const CollectingPartProvider = ({ setInterval(interval - 1); }; + useDebounceEffect(() => seekTo(partStartTime), [partStartTime, interval, isPlayingEntire], 300); + useEffect(() => { if (isPlayingEntire || playerState === YT.PlayerState.PAUSED) return; From f681587322b2f12e4610e5888731d60f8567453f Mon Sep 17 00:00:00 2001 From: ukkodeveloper Date: Mon, 16 Oct 2023 22:34:44 +0900 Subject: [PATCH 55/95] =?UTF-8?q?fix:=20page=20translateY=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/pages/PartCollectingPage.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/frontend/src/pages/PartCollectingPage.tsx b/frontend/src/pages/PartCollectingPage.tsx index f25994595..20d44e022 100644 --- a/frontend/src/pages/PartCollectingPage.tsx +++ b/frontend/src/pages/PartCollectingPage.tsx @@ -64,7 +64,6 @@ const PageFlex = styled(Flex)` border-radius: 8px; @media (min-width: ${({ theme }) => theme.breakPoints.md}) { - transform: translateY(40px); gap: 16px; padding: 16px; } From ce3d7c16e7f1eced01f5fbd5e15e834485d5d685 Mon Sep 17 00:00:00 2001 From: ukkodeveloper Date: Mon, 16 Oct 2023 22:43:55 +0900 Subject: [PATCH 56/95] =?UTF-8?q?fix:=20=EC=A0=84=EC=B2=B4=EB=93=A3?= =?UTF-8?q?=EA=B8=B0=20toggle=20=ED=95=B4=EC=A0=9C=20=EC=8B=9C=EC=97=90=20?= =?UTF-8?q?=EA=B5=AC=EA=B0=84=20=EC=B2=AB=20=EC=8B=9C=EC=9E=91=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../features/songs/components/CollectingPartProvider.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/frontend/src/features/songs/components/CollectingPartProvider.tsx b/frontend/src/features/songs/components/CollectingPartProvider.tsx index 40bed9b2f..68145f340 100644 --- a/frontend/src/features/songs/components/CollectingPartProvider.tsx +++ b/frontend/src/features/songs/components/CollectingPartProvider.tsx @@ -33,7 +33,10 @@ export const CollectingPartProvider = ({ const { playerState, seekTo } = useVideoPlayerContext(); const toggleEntirePlaying = () => { - setIsPlayingEntire((prev) => !prev); + if (isPlayingEntire) { + seekTo(partStartTime); + } + setIsPlayingEntire(!isPlayingEntire); }; const increasePartInterval = () => { @@ -48,7 +51,7 @@ export const CollectingPartProvider = ({ setInterval(interval - 1); }; - useDebounceEffect(() => seekTo(partStartTime), [partStartTime, interval, isPlayingEntire], 300); + useDebounceEffect(() => seekTo(partStartTime), [partStartTime, interval], 300); useEffect(() => { if (isPlayingEntire || playerState === YT.PlayerState.PAUSED) return; From 9800dca7c8673d55df9995e3382be2ab2c348dd1 Mon Sep 17 00:00:00 2001 From: ukkodeveloper Date: Tue, 17 Oct 2023 16:38:30 +0900 Subject: [PATCH 57/95] =?UTF-8?q?fix:=2010=EC=B4=88=EB=92=A4=EC=97=90=20?= =?UTF-8?q?=EC=9E=90=EB=8F=99=EC=8B=9C=EC=9E=91=EB=90=98=EB=8A=94=20?= =?UTF-8?q?=ED=98=84=EC=83=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/features/songs/components/CollectingPartProvider.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/features/songs/components/CollectingPartProvider.tsx b/frontend/src/features/songs/components/CollectingPartProvider.tsx index 68145f340..6cc3ea19c 100644 --- a/frontend/src/features/songs/components/CollectingPartProvider.tsx +++ b/frontend/src/features/songs/components/CollectingPartProvider.tsx @@ -54,7 +54,7 @@ export const CollectingPartProvider = ({ useDebounceEffect(() => seekTo(partStartTime), [partStartTime, interval], 300); useEffect(() => { - if (isPlayingEntire || playerState === YT.PlayerState.PAUSED) return; + if (isPlayingEntire || playerState !== YT.PlayerState.PLAYING) return; const timer = window.setInterval(() => { seekTo(partStartTime); From 7c4d0d7dd379d598bff890279c0c09f2fa2c8707 Mon Sep 17 00:00:00 2001 From: ukkodeveloper Date: Tue, 17 Oct 2023 16:41:11 +0900 Subject: [PATCH 58/95] =?UTF-8?q?feat:=20=EB=A7=88=EC=9A=B0=EC=8A=A4=20?= =?UTF-8?q?=ED=9C=A0=EB=A1=9C=20scrubber=20=ED=83=90=EC=83=89=ED=95=A0=20?= =?UTF-8?q?=EC=88=98=20=EC=9E=88=EB=8F=84=EB=A1=9D=20=EC=9D=B4=EB=B2=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../killingParts/components/WaveScrubber.tsx | 25 ++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/frontend/src/features/killingParts/components/WaveScrubber.tsx b/frontend/src/features/killingParts/components/WaveScrubber.tsx index ea6fa7a03..d69fcfcb6 100644 --- a/frontend/src/features/killingParts/components/WaveScrubber.tsx +++ b/frontend/src/features/killingParts/components/WaveScrubber.tsx @@ -1,3 +1,4 @@ +import { useRef } from 'react'; import styled, { keyframes } from 'styled-components'; import SoundWave from '@/features/killingParts/components/SoundWave'; import useCollectingPartContext from '@/features/killingParts/hooks/useCollectingPartContext'; @@ -7,6 +8,7 @@ import Flex from '@/shared/components/Flex/Flex'; const WaveScrubber = () => { const { interval, videoLength, setPartStartTime, isPlayingEntire } = useCollectingPartContext(); const video = useVideoPlayerContext(); + const ref = useRef(null); const maxPartStartTime = videoLength - interval; const isInterval = video.playerState === YT.PlayerState.PLAYING && !isPlayingEntire; @@ -16,21 +18,38 @@ const WaveScrubber = () => { // ProgressFrame의 width: 350 const unit = (scrollWidth - 350) / maxPartStartTime; const partStartTimeToChange = Math.floor(scrollLeft / unit); - if (partStartTimeToChange >= 0 && partStartTimeToChange <= maxPartStartTime) { setPartStartTime(partStartTimeToChange); } }; + const wheelPartStartTime: React.WheelEventHandler = (e) => { + if (!ref.current) return; + + if (Math.abs(e.deltaX) < Math.abs(e.deltaY)) { + e.currentTarget?.scrollTo({ + left: e.deltaY / 1.2 + ref.current?.scrollLeft, + behavior: 'smooth', + }); + } + }; + const playVideo = () => { - if (video.playerState === YT.PlayerState.PLAYING) { + if (video.playerState !== YT.PlayerState.PLAYING) { video.play(); } }; return ( - + From 22c605f6372173289c449eea6f527e5b6a5a6cfa Mon Sep 17 00:00:00 2001 From: ukkodeveloper Date: Tue, 17 Oct 2023 17:40:46 +0900 Subject: [PATCH 59/95] =?UTF-8?q?fix:=20=EB=84=88=EB=B9=84=EC=97=90=20?= =?UTF-8?q?=EB=A7=9E=EA=B2=8C=20=EC=8B=9C=EA=B0=84=20=EC=B4=88=20=EA=B3=84?= =?UTF-8?q?=EC=82=B0=ED=95=A0=20=EC=88=98=20=EC=9E=88=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../killingParts/components/WaveScrubber.tsx | 33 ++++++++++++++----- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/frontend/src/features/killingParts/components/WaveScrubber.tsx b/frontend/src/features/killingParts/components/WaveScrubber.tsx index d69fcfcb6..8c45df3a3 100644 --- a/frontend/src/features/killingParts/components/WaveScrubber.tsx +++ b/frontend/src/features/killingParts/components/WaveScrubber.tsx @@ -1,5 +1,5 @@ -import { useRef } from 'react'; -import styled, { keyframes } from 'styled-components'; +import { useEffect, useRef } from 'react'; +import styled, { css, keyframes } from 'styled-components'; import SoundWave from '@/features/killingParts/components/SoundWave'; import useCollectingPartContext from '@/features/killingParts/hooks/useCollectingPartContext'; import useVideoPlayerContext from '@/features/youtube/hooks/useVideoPlayerContext'; @@ -9,14 +9,22 @@ const WaveScrubber = () => { const { interval, videoLength, setPartStartTime, isPlayingEntire } = useCollectingPartContext(); const video = useVideoPlayerContext(); const ref = useRef(null); + // const progressWidth = 100 + (interval - 5) * 5; + // const [, set] = useState(); const maxPartStartTime = videoLength - interval; const isInterval = video.playerState === YT.PlayerState.PLAYING && !isPlayingEntire; const isEntire = video.playerState === YT.PlayerState.PLAYING && isPlayingEntire; + const changePartStartTime: React.UIEventHandler = (e) => { const { scrollWidth, scrollLeft } = e.currentTarget; - // ProgressFrame의 width: 350 - const unit = (scrollWidth - 350) / maxPartStartTime; + // WaverScrubber: 350 + // Frame: 100 + // calc((100% - 150px) / 2) + // Padding: WaverScrubber - 150 + if (!ref.current) return; + const clientWidth = ref.current?.clientWidth; + const unit = (scrollWidth - clientWidth) / maxPartStartTime; const partStartTimeToChange = Math.floor(scrollLeft / unit); if (partStartTimeToChange >= 0 && partStartTimeToChange <= maxPartStartTime) { setPartStartTime(partStartTimeToChange); @@ -40,6 +48,11 @@ const WaveScrubber = () => { } }; + // TODO: remove + useEffect(() => { + console.log(ref.current?.clientWidth); + }, []); + return ( { }; export default WaveScrubber; + +const ProgressWidth = css` + width: 50px; +`; const WaveWrapper = styled(Flex)` z-index: 3; @@ -67,7 +84,7 @@ const WaveWrapper = styled(Flex)` width: 100%; height: 75px; - padding: 0 calc((100% - 150px) / 2); + padding: 0 calc((100% - 50px) / 2); background-color: transparent; `; @@ -93,12 +110,12 @@ const Container = styled.div` `; const ProgressFrame = styled.div` + ${ProgressWidth}; position: absolute; z-index: 1; left: 50%; transform: translateX(-50%); - width: 150px; height: 50px; border: transparent; @@ -116,6 +133,7 @@ const fillAnimation = keyframes` `; const ProgressFill = styled.div<{ $interval: number }>` + ${ProgressWidth}; pointer-events: none; position: absolute; @@ -123,7 +141,6 @@ const ProgressFill = styled.div<{ $interval: number }>` left: 50%; transform: translateX(-50%); - width: 150px; height: 50px; background: ${({ theme: { color } }) => @@ -165,6 +182,7 @@ const waveFillAnimate = keyframes` `; const WaveFill = styled.div<{ $isRunning: boolean }>` + ${ProgressWidth}; pointer-events: none; position: absolute; @@ -172,7 +190,6 @@ const WaveFill = styled.div<{ $isRunning: boolean }>` left: 50%; transform: translateX(-50%); - width: 150px; height: 50px; background: ${({ theme: { color } }) => From d15f4bf3aaa136c43f6d1190034a979774788a7f Mon Sep 17 00:00:00 2001 From: ukkodeveloper Date: Tue, 17 Oct 2023 18:39:45 +0900 Subject: [PATCH 60/95] =?UTF-8?q?feat:=20=EA=B5=AC=EA=B0=84=20=EA=B8=B8?= =?UTF-8?q?=EC=9D=B4=EC=97=90=20=EB=94=B0=EB=9D=BC=EC=84=9C=20progress=20b?= =?UTF-8?q?ar=20=EA=B8=B8=EC=9D=B4=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../killingParts/components/WaveScrubber.tsx | 43 +++++++------------ 1 file changed, 16 insertions(+), 27 deletions(-) diff --git a/frontend/src/features/killingParts/components/WaveScrubber.tsx b/frontend/src/features/killingParts/components/WaveScrubber.tsx index 8c45df3a3..430b3841a 100644 --- a/frontend/src/features/killingParts/components/WaveScrubber.tsx +++ b/frontend/src/features/killingParts/components/WaveScrubber.tsx @@ -1,5 +1,5 @@ -import { useEffect, useRef } from 'react'; -import styled, { css, keyframes } from 'styled-components'; +import { useRef } from 'react'; +import styled, { keyframes } from 'styled-components'; import SoundWave from '@/features/killingParts/components/SoundWave'; import useCollectingPartContext from '@/features/killingParts/hooks/useCollectingPartContext'; import useVideoPlayerContext from '@/features/youtube/hooks/useVideoPlayerContext'; @@ -9,19 +9,15 @@ const WaveScrubber = () => { const { interval, videoLength, setPartStartTime, isPlayingEntire } = useCollectingPartContext(); const video = useVideoPlayerContext(); const ref = useRef(null); - // const progressWidth = 100 + (interval - 5) * 5; - // const [, set] = useState(); const maxPartStartTime = videoLength - interval; + const progressWidth = 100 + (interval - 5) * 5; const isInterval = video.playerState === YT.PlayerState.PLAYING && !isPlayingEntire; const isEntire = video.playerState === YT.PlayerState.PLAYING && isPlayingEntire; const changePartStartTime: React.UIEventHandler = (e) => { const { scrollWidth, scrollLeft } = e.currentTarget; - // WaverScrubber: 350 - // Frame: 100 - // calc((100% - 150px) / 2) - // Padding: WaverScrubber - 150 + if (!ref.current) return; const clientWidth = ref.current?.clientWidth; const unit = (scrollWidth - clientWidth) / maxPartStartTime; @@ -48,43 +44,36 @@ const WaveScrubber = () => { } }; - // TODO: remove - useEffect(() => { - console.log(ref.current?.clientWidth); - }, []); - return ( - - {isInterval && } - {isPlayingEntire && } + + {isInterval && } + {isPlayingEntire && } ); }; export default WaveScrubber; -const ProgressWidth = css` - width: 50px; -`; -const WaveWrapper = styled(Flex)` +const WaveWrapper = styled(Flex)<{ $progressWidth: number }>` z-index: 3; overflow-x: scroll; width: 100%; height: 75px; - padding: 0 calc((100% - 50px) / 2); + padding: ${({ $progressWidth }) => `0 calc((100% - ${$progressWidth}px) / 2)`}; background-color: transparent; `; @@ -109,14 +98,14 @@ const Container = styled.div` } `; -const ProgressFrame = styled.div` - ${ProgressWidth}; +const ProgressFrame = styled.div<{ $progressWidth: number }>` position: absolute; z-index: 1; left: 50%; transform: translateX(-50%); height: 50px; + width: ${({ $progressWidth }) => $progressWidth}px; border: transparent; border-radius: 4px; @@ -132,8 +121,7 @@ const fillAnimation = keyframes` } `; -const ProgressFill = styled.div<{ $interval: number }>` - ${ProgressWidth}; +const ProgressFill = styled.div<{ $progressWidth: number; $interval: number }>` pointer-events: none; position: absolute; @@ -142,6 +130,7 @@ const ProgressFill = styled.div<{ $interval: number }>` transform: translateX(-50%); height: 50px; + width: ${({ $progressWidth }) => $progressWidth}px; background: ${({ theme: { color } }) => `linear-gradient(to left, transparent 50%, ${color.magenta300} 50%)`}; @@ -181,8 +170,7 @@ const waveFillAnimate = keyframes` } `; -const WaveFill = styled.div<{ $isRunning: boolean }>` - ${ProgressWidth}; +const WaveFill = styled.div<{ $progressWidth: number; $isRunning: boolean }>` pointer-events: none; position: absolute; @@ -191,6 +179,7 @@ const WaveFill = styled.div<{ $isRunning: boolean }>` transform: translateX(-50%); height: 50px; + width: ${({ $progressWidth }) => $progressWidth}px; background: ${({ theme: { color } }) => `linear-gradient(to left, ${color.magenta100}, ${color.magenta400})`}; From f7b923f5de9fc72808c1ec982f3d4fa0740f8eb2 Mon Sep 17 00:00:00 2001 From: ukkodeveloper Date: Tue, 17 Oct 2023 18:45:10 +0900 Subject: [PATCH 61/95] =?UTF-8?q?feat:=20Youtube=20=EC=A1=B0=EC=9E=91=20?= =?UTF-8?q?=EC=97=AC=EB=B6=80=20props=EB=A1=9C=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?=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 --- frontend/src/features/youtube/components/Youtube.tsx | 5 +++-- frontend/src/pages/PartCollectingPage.tsx | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/frontend/src/features/youtube/components/Youtube.tsx b/frontend/src/features/youtube/components/Youtube.tsx index 9357afa4e..88d7edc35 100644 --- a/frontend/src/features/youtube/components/Youtube.tsx +++ b/frontend/src/features/youtube/components/Youtube.tsx @@ -7,9 +7,10 @@ import useVideoPlayerContext from '../hooks/useVideoPlayerContext'; interface YoutubeProps { start?: number; videoId: string; + controls?: YT.Controls; } -const Youtube = ({ start = 0, videoId }: YoutubeProps) => { +const Youtube = ({ start = 0, videoId, controls = 1 }: YoutubeProps) => { const { initPlayer, bindUpdatePlayerStateEvent } = useVideoPlayerContext(); const [loading, setLoading] = useState(true); @@ -22,7 +23,7 @@ const Youtube = ({ start = 0, videoId }: YoutubeProps) => { videoId, width: '100%', height: '100%', - playerVars: { start, rel: 0, fs: 0 }, + playerVars: { start, rel: 0, fs: 0, controls }, events: { onReady: (e) => { bindUpdatePlayerStateEvent(e); diff --git a/frontend/src/pages/PartCollectingPage.tsx b/frontend/src/pages/PartCollectingPage.tsx index 20d44e022..afde2b025 100644 --- a/frontend/src/pages/PartCollectingPage.tsx +++ b/frontend/src/pages/PartCollectingPage.tsx @@ -33,7 +33,7 @@ const PartCollectingPage = () => { - + From 4225145ce35fe782005497bccaca14a451018cac Mon Sep 17 00:00:00 2001 From: ukkodeveloper Date: Tue, 17 Oct 2023 18:52:51 +0900 Subject: [PATCH 62/95] =?UTF-8?q?feat:=20=EB=8D=B0=EC=8A=A4=ED=81=AC?= =?UTF-8?q?=ED=83=91=20=ED=99=98=EA=B2=BD=EC=97=90=EC=84=9C=20=EC=86=8C?= =?UTF-8?q?=EC=A0=9C=EB=AA=A9=20=EC=A0=9C=EA=B3=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../killingParts/components/VideoController.tsx | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/frontend/src/features/killingParts/components/VideoController.tsx b/frontend/src/features/killingParts/components/VideoController.tsx index 9b3a3d311..b6f0acd3f 100644 --- a/frontend/src/features/killingParts/components/VideoController.tsx +++ b/frontend/src/features/killingParts/components/VideoController.tsx @@ -1,3 +1,4 @@ +import styled from 'styled-components'; import VideoBadges from '@/features/killingParts/components/VideoBadges'; import VideoIntervalStepper from '@/features/killingParts/components/VideoIntervalStepper'; import WaveScrubber from '@/features/killingParts/components/WaveScrubber'; @@ -6,7 +7,10 @@ import Flex from '@/shared/components/Flex/Flex'; const VideoController = () => { return ( + 길이 선택 + + 구간 지정 @@ -14,3 +18,13 @@ const VideoController = () => { }; export default VideoController; + +const SubHeading = styled.div` + display: none; + + @media (min-width: ${({ theme }) => theme.breakPoints.md}) { + display: unset; + font-size: 20px; + margin-top: 8px; + } +`; From 482ca98ff6032a8a84e32820a896a6b0d2251e60 Mon Sep 17 00:00:00 2001 From: ukkodeveloper Date: Tue, 17 Oct 2023 21:28:08 +0900 Subject: [PATCH 63/95] =?UTF-8?q?feat:=20waveScrubber=20=EB=93=9C=EB=9E=98?= =?UTF-8?q?=EA=B7=B8=20=EC=9D=B4=EB=8F=99=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../killingParts/components/WaveScrubber.tsx | 43 ++++++++++++++++--- 1 file changed, 36 insertions(+), 7 deletions(-) diff --git a/frontend/src/features/killingParts/components/WaveScrubber.tsx b/frontend/src/features/killingParts/components/WaveScrubber.tsx index 430b3841a..16609377c 100644 --- a/frontend/src/features/killingParts/components/WaveScrubber.tsx +++ b/frontend/src/features/killingParts/components/WaveScrubber.tsx @@ -1,4 +1,4 @@ -import { useRef } from 'react'; +import { useRef, useState } from 'react'; import styled, { keyframes } from 'styled-components'; import SoundWave from '@/features/killingParts/components/SoundWave'; import useCollectingPartContext from '@/features/killingParts/hooks/useCollectingPartContext'; @@ -8,7 +8,8 @@ import Flex from '@/shared/components/Flex/Flex'; const WaveScrubber = () => { const { interval, videoLength, setPartStartTime, isPlayingEntire } = useCollectingPartContext(); const video = useVideoPlayerContext(); - const ref = useRef(null); + const boxRef = useRef(null); + const [xPos, setXPos] = useState<{ initial: number; scroll: number } | null>(null); const maxPartStartTime = videoLength - interval; const progressWidth = 100 + (interval - 5) * 5; @@ -18,8 +19,8 @@ const WaveScrubber = () => { const changePartStartTime: React.UIEventHandler = (e) => { const { scrollWidth, scrollLeft } = e.currentTarget; - if (!ref.current) return; - const clientWidth = ref.current?.clientWidth; + if (!boxRef.current) return; + const clientWidth = boxRef.current?.clientWidth; const unit = (scrollWidth - clientWidth) / maxPartStartTime; const partStartTimeToChange = Math.floor(scrollLeft / unit); if (partStartTimeToChange >= 0 && partStartTimeToChange <= maxPartStartTime) { @@ -28,11 +29,12 @@ const WaveScrubber = () => { }; const wheelPartStartTime: React.WheelEventHandler = (e) => { - if (!ref.current) return; + console.log('[wheel]'); + if (!boxRef.current) return; if (Math.abs(e.deltaX) < Math.abs(e.deltaY)) { e.currentTarget?.scrollTo({ - left: e.deltaY / 1.2 + ref.current?.scrollLeft, + left: e.deltaY / 1.2 + boxRef.current?.scrollLeft, behavior: 'smooth', }); } @@ -51,9 +53,35 @@ const WaveScrubber = () => { onWheel={wheelPartStartTime} onTouchStart={playVideo} $progressWidth={progressWidth} - ref={ref} + ref={boxRef} $gap={8} $align="center" + onMouseDown={(e) => { + e.preventDefault(); + if (boxRef.current) { + setXPos({ + initial: e.screenX, + scroll: boxRef.current?.scrollLeft, + }); + } + }} + onMouseMove={({ screenX }) => { + if (!xPos) return; + console.log('[mouse move]'); + + boxRef.current?.scrollTo({ + left: xPos.scroll + (xPos.initial - screenX) / 0.5, + behavior: 'instant', + }); + }} + onMouseUp={() => { + console.log('[mouse up]'); + setXPos(null); + }} + onMouseLeave={() => { + console.log('[mouse leave]'); + setXPos(null); + }} > @@ -68,6 +96,7 @@ export default WaveScrubber; const WaveWrapper = styled(Flex)<{ $progressWidth: number }>` z-index: 3; + cursor: grab; overflow-x: scroll; From 33e8f6a520af8fd2475e41f43a32c55ee5d251a4 Mon Sep 17 00:00:00 2001 From: ukkodeveloper Date: Tue, 17 Oct 2023 21:46:09 +0900 Subject: [PATCH 64/95] =?UTF-8?q?design:=20=EC=A0=84=EB=B0=98=EC=A0=81?= =?UTF-8?q?=EC=9D=B8=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EB=82=B4=20=ED=8F=B0?= =?UTF-8?q?=ED=8A=B8=20=ED=81=AC=EA=B8=B0=20=EB=93=B1=20=EB=94=94=EC=9E=90?= =?UTF-8?q?=EC=9D=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../killingParts/components/CollectingInformation.tsx | 6 +++--- .../src/features/killingParts/components/VideoBadges.tsx | 4 ++-- .../features/killingParts/components/VideoController.tsx | 3 ++- frontend/src/pages/PartCollectingPage.tsx | 6 +++--- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/frontend/src/features/killingParts/components/CollectingInformation.tsx b/frontend/src/features/killingParts/components/CollectingInformation.tsx index a634f1791..f7542a368 100644 --- a/frontend/src/features/killingParts/components/CollectingInformation.tsx +++ b/frontend/src/features/killingParts/components/CollectingInformation.tsx @@ -15,11 +15,11 @@ const Container = styled.div``; const RegisterTitle = styled.h2` font-size: 18px; - font-weight: 700; + font-weight: 800; color: ${({ theme: { color } }) => color.white}; @media (min-width: ${({ theme }) => theme.breakPoints.md}) { - font-size: 22px; + font-size: 20px; } `; @@ -28,6 +28,6 @@ const Warning = styled.div` color: ${({ theme: { color } }) => color.subText}; @media (min-width: ${({ theme }) => theme.breakPoints.md}) { - font-size: 18px; + font-size: 15px; } `; diff --git a/frontend/src/features/killingParts/components/VideoBadges.tsx b/frontend/src/features/killingParts/components/VideoBadges.tsx index ec1fa9539..107212d49 100644 --- a/frontend/src/features/killingParts/components/VideoBadges.tsx +++ b/frontend/src/features/killingParts/components/VideoBadges.tsx @@ -47,7 +47,7 @@ const Badge = styled.span<{ $isActive?: boolean }>` min-width: 40px; padding: 0 10px; - font-size: 13px; + font-size: 14px; color: ${({ theme: { color }, $isActive }) => ($isActive ? color.black : color.white)}; text-align: center; @@ -58,6 +58,6 @@ const Badge = styled.span<{ $isActive?: boolean }>` transition: background-color 0.2s ease-in; @media (min-width: ${({ theme }) => theme.breakPoints.md}) { - font-size: 14px; + font-size: 16px; } `; diff --git a/frontend/src/features/killingParts/components/VideoController.tsx b/frontend/src/features/killingParts/components/VideoController.tsx index b6f0acd3f..4d2d95052 100644 --- a/frontend/src/features/killingParts/components/VideoController.tsx +++ b/frontend/src/features/killingParts/components/VideoController.tsx @@ -24,7 +24,8 @@ const SubHeading = styled.div` @media (min-width: ${({ theme }) => theme.breakPoints.md}) { display: unset; - font-size: 20px; + + font-size: 16px; margin-top: 8px; } `; diff --git a/frontend/src/pages/PartCollectingPage.tsx b/frontend/src/pages/PartCollectingPage.tsx index afde2b025..d9ee467fc 100644 --- a/frontend/src/pages/PartCollectingPage.tsx +++ b/frontend/src/pages/PartCollectingPage.tsx @@ -30,12 +30,12 @@ const PartCollectingPage = () => { - - + + - + From c6df2b8e511c88ae086030bf3f13d8fc335d1ed9 Mon Sep 17 00:00:00 2001 From: ukkodeveloper Date: Wed, 18 Oct 2023 00:40:55 +0900 Subject: [PATCH 65/95] =?UTF-8?q?feat:=20wave=20interaction=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80.=20=EC=82=AC=EC=9A=A9=EC=84=B1=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../killingParts/components/SoundWave.tsx | 42 +++++++++++++++---- .../killingParts/components/WaveScrubber.tsx | 6 ++- .../components/CollectingPartProvider.tsx | 3 +- 3 files changed, 39 insertions(+), 12 deletions(-) diff --git a/frontend/src/features/killingParts/components/SoundWave.tsx b/frontend/src/features/killingParts/components/SoundWave.tsx index a5b243491..f906fa98e 100644 --- a/frontend/src/features/killingParts/components/SoundWave.tsx +++ b/frontend/src/features/killingParts/components/SoundWave.tsx @@ -1,19 +1,39 @@ -import React from 'react'; +import React, { forwardRef } from 'react'; import styled from 'styled-components'; interface SoundWaveProps { length: number; + progressWidth: number; } -const SoundWave = ({ length }: SoundWaveProps) => { - return Array.from({ length }, (_, index) => { - return ( +// eslint-disable-next-line react/display-name +const SoundWave = forwardRef( + ({ length, progressWidth }, boxRef) => { + const refCallback = + (activeHeight: string, inactiveHeight: string) => (dom: HTMLDivElement | null) => { + if (!dom || !boxRef || typeof boxRef === 'function') return; + if (boxRef.current?.scrollLeft) { + const boxPos = + boxRef.current?.scrollLeft + boxRef.current?.clientWidth / 2 - progressWidth / 2; + + const containerRightEdge = boxPos + progressWidth - 1; + const itemRightEdge = dom.offsetLeft; + + if (itemRightEdge >= boxPos && itemRightEdge <= containerRightEdge) { + dom.style.height = activeHeight; + } else { + dom.style.height = inactiveHeight; + } + } + }; + + return Array.from({ length }, (_, index) => ( - - + + - ); - }); -}; + )); + } +); export default SoundWave; @@ -26,6 +46,8 @@ const LongBar = styled.div` background-color: ${({ theme: { color } }) => color.white}; border-radius: 5px; + + transition: height 0.2s ease; `; const ShortBar = styled.div` @@ -36,4 +58,6 @@ const ShortBar = styled.div` background-color: ${({ theme: { color } }) => color.white}; border-radius: 5px; + + transition: height 0.2s ease; `; diff --git a/frontend/src/features/killingParts/components/WaveScrubber.tsx b/frontend/src/features/killingParts/components/WaveScrubber.tsx index 16609377c..3912b4f11 100644 --- a/frontend/src/features/killingParts/components/WaveScrubber.tsx +++ b/frontend/src/features/killingParts/components/WaveScrubber.tsx @@ -8,9 +8,11 @@ import Flex from '@/shared/components/Flex/Flex'; const WaveScrubber = () => { const { interval, videoLength, setPartStartTime, isPlayingEntire } = useCollectingPartContext(); const video = useVideoPlayerContext(); - const boxRef = useRef(null); + const boxRef = useRef(null); const [xPos, setXPos] = useState<{ initial: number; scroll: number } | null>(null); + console.log('box scroll', boxRef.current?.scrollLeft); + const maxPartStartTime = videoLength - interval; const progressWidth = 100 + (interval - 5) * 5; const isInterval = video.playerState === YT.PlayerState.PLAYING && !isPlayingEntire; @@ -83,7 +85,7 @@ const WaveScrubber = () => { setXPos(null); }} > - + {isInterval && } diff --git a/frontend/src/features/songs/components/CollectingPartProvider.tsx b/frontend/src/features/songs/components/CollectingPartProvider.tsx index 6cc3ea19c..32b424090 100644 --- a/frontend/src/features/songs/components/CollectingPartProvider.tsx +++ b/frontend/src/features/songs/components/CollectingPartProvider.tsx @@ -51,7 +51,8 @@ export const CollectingPartProvider = ({ setInterval(interval - 1); }; - useDebounceEffect(() => seekTo(partStartTime), [partStartTime, interval], 300); + useDebounceEffect(() => seekTo(partStartTime), [partStartTime], 150); + useDebounceEffect(() => seekTo(partStartTime), [interval], 300); useEffect(() => { if (isPlayingEntire || playerState !== YT.PlayerState.PLAYING) return; From 92e7542619522c9560b1e504ab17eaf4566c1cfb Mon Sep 17 00:00:00 2001 From: ukkodeveloper Date: Wed, 18 Oct 2023 07:34:27 +0900 Subject: [PATCH 66/95] =?UTF-8?q?fix:=20waveScrubber=20interval=20?= =?UTF-8?q?=EC=A1=B0=EC=A0=88=20=EC=8B=9C=20=EC=8B=9C=EC=9E=91=EC=A0=90=20?= =?UTF-8?q?=EC=8A=A4=ED=81=AC=EB=A1=A4=20=EB=AC=B8=EC=A0=9C=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 --- .../killingParts/components/WaveScrubber.tsx | 7 ++++--- .../components/CollectingPartProvider.tsx | 21 +++++++++++++++++-- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/frontend/src/features/killingParts/components/WaveScrubber.tsx b/frontend/src/features/killingParts/components/WaveScrubber.tsx index 3912b4f11..b7f5f0f97 100644 --- a/frontend/src/features/killingParts/components/WaveScrubber.tsx +++ b/frontend/src/features/killingParts/components/WaveScrubber.tsx @@ -1,4 +1,4 @@ -import { useRef, useState } from 'react'; +import { useState } from 'react'; import styled, { keyframes } from 'styled-components'; import SoundWave from '@/features/killingParts/components/SoundWave'; import useCollectingPartContext from '@/features/killingParts/hooks/useCollectingPartContext'; @@ -6,9 +6,10 @@ import useVideoPlayerContext from '@/features/youtube/hooks/useVideoPlayerContex import Flex from '@/shared/components/Flex/Flex'; const WaveScrubber = () => { - const { interval, videoLength, setPartStartTime, isPlayingEntire } = useCollectingPartContext(); + const { boxRef, interval, videoLength, setPartStartTime, isPlayingEntire } = + useCollectingPartContext(); const video = useVideoPlayerContext(); - const boxRef = useRef(null); + const [xPos, setXPos] = useState<{ initial: number; scroll: number } | null>(null); console.log('box scroll', boxRef.current?.scrollLeft); diff --git a/frontend/src/features/songs/components/CollectingPartProvider.tsx b/frontend/src/features/songs/components/CollectingPartProvider.tsx index 32b424090..43aa8446d 100644 --- a/frontend/src/features/songs/components/CollectingPartProvider.tsx +++ b/frontend/src/features/songs/components/CollectingPartProvider.tsx @@ -1,4 +1,4 @@ -import { createContext, useEffect, useState } from 'react'; +import { createContext, useEffect, useRef, useState } from 'react'; import { MAX_PART_INTERVAL, MIN_PART_INTERVAL } from '@/features/songs/constants/partInterval'; import useVideoPlayerContext from '@/features/youtube/hooks/useVideoPlayerContext'; import useDebounceEffect from '@/shared/hooks/useDebounceEffect'; @@ -14,6 +14,7 @@ interface CollectingPartContextProps extends CollectingPartProviderProps { partStartTime: number; interval: number; isPlayingEntire: boolean; + boxRef: React.RefObject; setPartStartTime: React.Dispatch>; increasePartInterval: () => void; decreasePartInterval: () => void; @@ -31,6 +32,7 @@ export const CollectingPartProvider = ({ const [partStartTime, setPartStartTime] = useState(0); const [isPlayingEntire, setIsPlayingEntire] = useState(false); const { playerState, seekTo } = useVideoPlayerContext(); + const boxRef = useRef(null); const toggleEntirePlaying = () => { if (isPlayingEntire) { @@ -52,7 +54,21 @@ export const CollectingPartProvider = ({ }; useDebounceEffect(() => seekTo(partStartTime), [partStartTime], 150); - useDebounceEffect(() => seekTo(partStartTime), [interval], 300); + useDebounceEffect( + () => { + if (boxRef.current) { + const unit = + (boxRef.current.scrollWidth - boxRef.current.clientWidth) / (videoLength - interval); + boxRef.current.scrollTo({ + left: partStartTime * unit + 2.5, + behavior: 'instant', + }); + } + seekTo(partStartTime); + }, + [interval], + 300 + ); useEffect(() => { if (isPlayingEntire || playerState !== YT.PlayerState.PLAYING) return; @@ -75,6 +91,7 @@ export const CollectingPartProvider = ({ songId, songVideoId, isPlayingEntire, + boxRef, setPartStartTime, increasePartInterval, decreasePartInterval, From ef4142ecd49b98462535240826a38f16194bab17 Mon Sep 17 00:00:00 2001 From: ukkodeveloper Date: Wed, 18 Oct 2023 07:37:28 +0900 Subject: [PATCH 67/95] =?UTF-8?q?feat:=20=EB=B3=80=EA=B2=BD=EB=90=9C=20?= =?UTF-8?q?=EB=AA=85=EC=84=B8=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{songs/remotes => killingParts/hooks}/killingPart.ts | 2 +- .../src/features/killingParts/remotes/usePostKillingPart.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename frontend/src/features/{songs/remotes => killingParts/hooks}/killingPart.ts (76%) diff --git a/frontend/src/features/songs/remotes/killingPart.ts b/frontend/src/features/killingParts/hooks/killingPart.ts similarity index 76% rename from frontend/src/features/songs/remotes/killingPart.ts rename to frontend/src/features/killingParts/hooks/killingPart.ts index 1f78e8437..d711edb72 100644 --- a/frontend/src/features/songs/remotes/killingPart.ts +++ b/frontend/src/features/killingParts/hooks/killingPart.ts @@ -2,5 +2,5 @@ import fetcher from '@/shared/remotes'; import type { KillingPartPostRequest } from '@/shared/types/killingPart'; export const postKillingPart = async (songId: number, body: KillingPartPostRequest) => { - return await fetcher(`/voting-songs/${songId}/parts`, 'POST', body); + return await fetcher(`/songs/${songId}/member-parts`, 'POST', body); }; diff --git a/frontend/src/features/killingParts/remotes/usePostKillingPart.ts b/frontend/src/features/killingParts/remotes/usePostKillingPart.ts index 8bf631d37..c0620378e 100644 --- a/frontend/src/features/killingParts/remotes/usePostKillingPart.ts +++ b/frontend/src/features/killingParts/remotes/usePostKillingPart.ts @@ -1,4 +1,4 @@ -import { postKillingPart } from '@/features/songs/remotes/killingPart'; +import { postKillingPart } from '@/features/killingParts/hooks/killingPart'; import { useMutation } from '@/shared/hooks/useMutation'; export const usePostKillingPart = () => { From a4f193454a2c81c83941d7fdd84b1de1b6c3a113 Mon Sep 17 00:00:00 2001 From: ukkodeveloper Date: Wed, 18 Oct 2023 07:43:09 +0900 Subject: [PATCH 68/95] =?UTF-8?q?design:=20=EC=86=8C=EC=A0=9C=EB=AA=A9=20?= =?UTF-8?q?=ED=8F=B0=ED=8A=B8=20=EC=82=AC=EC=9D=B4=EC=A6=88=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/features/killingParts/components/VideoController.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/features/killingParts/components/VideoController.tsx b/frontend/src/features/killingParts/components/VideoController.tsx index 4d2d95052..24726625d 100644 --- a/frontend/src/features/killingParts/components/VideoController.tsx +++ b/frontend/src/features/killingParts/components/VideoController.tsx @@ -25,7 +25,7 @@ const SubHeading = styled.div` @media (min-width: ${({ theme }) => theme.breakPoints.md}) { display: unset; - font-size: 16px; + font-size: 18px; margin-top: 8px; } `; From dcd0b5e8bdfcdc7887473ace79913d2e088c9c8c Mon Sep 17 00:00:00 2001 From: ukkodeveloper Date: Wed, 18 Oct 2023 09:02:21 +0900 Subject: [PATCH 69/95] =?UTF-8?q?design:=20=EB=8D=B0=EC=8A=A4=ED=81=AC?= =?UTF-8?q?=ED=83=91=20=ED=99=98=EA=B2=BD=EC=97=90=EC=84=9C=20waveScrubber?= =?UTF-8?q?=20=EB=86=92=EC=9D=B4=20=EC=A6=9D=EA=B0=80=ED=95=98=EC=97=AC=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 --- .../src/features/killingParts/components/WaveScrubber.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/frontend/src/features/killingParts/components/WaveScrubber.tsx b/frontend/src/features/killingParts/components/WaveScrubber.tsx index b7f5f0f97..9a17839aa 100644 --- a/frontend/src/features/killingParts/components/WaveScrubber.tsx +++ b/frontend/src/features/killingParts/components/WaveScrubber.tsx @@ -108,6 +108,10 @@ const WaveWrapper = styled(Flex)<{ $progressWidth: number }>` padding: ${({ $progressWidth }) => `0 calc((100% - ${$progressWidth}px) / 2)`}; background-color: transparent; + + @media (min-width: ${({ theme }) => theme.breakPoints.md}) { + height: 90px; + } `; const Container = styled.div` From c1dfbb1b61e1da77eae870eb9ff45477e43b9f76 Mon Sep 17 00:00:00 2001 From: ukkodeveloper Date: Wed, 18 Oct 2023 12:33:11 +0900 Subject: [PATCH 70/95] =?UTF-8?q?refactor:=20useWave=20=ED=9B=85=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../killingParts/components/WaveScrubber.tsx | 96 ++++------------ .../features/killingParts/hooks/useWave.ts | 104 ++++++++++++++++++ .../components/CollectingPartProvider.tsx | 23 +--- 3 files changed, 127 insertions(+), 96 deletions(-) create mode 100644 frontend/src/features/killingParts/hooks/useWave.ts diff --git a/frontend/src/features/killingParts/components/WaveScrubber.tsx b/frontend/src/features/killingParts/components/WaveScrubber.tsx index 9a17839aa..b6fb933e5 100644 --- a/frontend/src/features/killingParts/components/WaveScrubber.tsx +++ b/frontend/src/features/killingParts/components/WaveScrubber.tsx @@ -1,96 +1,44 @@ -import { useState } from 'react'; import styled, { keyframes } from 'styled-components'; import SoundWave from '@/features/killingParts/components/SoundWave'; -import useCollectingPartContext from '@/features/killingParts/hooks/useCollectingPartContext'; -import useVideoPlayerContext from '@/features/youtube/hooks/useVideoPlayerContext'; +import useWave from '@/features/killingParts/hooks/useWave'; import Flex from '@/shared/components/Flex/Flex'; const WaveScrubber = () => { - const { boxRef, interval, videoLength, setPartStartTime, isPlayingEntire } = - useCollectingPartContext(); - const video = useVideoPlayerContext(); - - const [xPos, setXPos] = useState<{ initial: number; scroll: number } | null>(null); - - console.log('box scroll', boxRef.current?.scrollLeft); - - const maxPartStartTime = videoLength - interval; - const progressWidth = 100 + (interval - 5) * 5; - const isInterval = video.playerState === YT.PlayerState.PLAYING && !isPlayingEntire; - const isEntire = video.playerState === YT.PlayerState.PLAYING && isPlayingEntire; - - const changePartStartTime: React.UIEventHandler = (e) => { - const { scrollWidth, scrollLeft } = e.currentTarget; - - if (!boxRef.current) return; - const clientWidth = boxRef.current?.clientWidth; - const unit = (scrollWidth - clientWidth) / maxPartStartTime; - const partStartTimeToChange = Math.floor(scrollLeft / unit); - if (partStartTimeToChange >= 0 && partStartTimeToChange <= maxPartStartTime) { - setPartStartTime(partStartTimeToChange); - } - }; - - const wheelPartStartTime: React.WheelEventHandler = (e) => { - console.log('[wheel]'); - if (!boxRef.current) return; - - if (Math.abs(e.deltaX) < Math.abs(e.deltaY)) { - e.currentTarget?.scrollTo({ - left: e.deltaY / 1.2 + boxRef.current?.scrollLeft, - behavior: 'smooth', - }); - } - }; - - const playVideo = () => { - if (video.playerState !== YT.PlayerState.PLAYING) { - video.play(); - } - }; + const { + boxRef, + progressWidth, + isInterval, + isEntire, + scrollStartTime, + wheelStartTime, + maxPartStartTime, + interval, + playVideo, + dragStart, + dragMoving, + dragEnd, + } = useWave(); return ( { - e.preventDefault(); - if (boxRef.current) { - setXPos({ - initial: e.screenX, - scroll: boxRef.current?.scrollLeft, - }); - } - }} - onMouseMove={({ screenX }) => { - if (!xPos) return; - console.log('[mouse move]'); - - boxRef.current?.scrollTo({ - left: xPos.scroll + (xPos.initial - screenX) / 0.5, - behavior: 'instant', - }); - }} - onMouseUp={() => { - console.log('[mouse up]'); - setXPos(null); - }} - onMouseLeave={() => { - console.log('[mouse leave]'); - setXPos(null); - }} + onMouseDown={dragStart} + onMouseMove={dragMoving} + onMouseUp={dragEnd} + onMouseLeave={dragEnd} > {isInterval && } - {isPlayingEntire && } + {isEntire && } ); }; diff --git a/frontend/src/features/killingParts/hooks/useWave.ts b/frontend/src/features/killingParts/hooks/useWave.ts new file mode 100644 index 000000000..d92fdd09c --- /dev/null +++ b/frontend/src/features/killingParts/hooks/useWave.ts @@ -0,0 +1,104 @@ +import { useRef, useState } from 'react'; +import useCollectingPartContext from '@/features/killingParts/hooks/useCollectingPartContext'; +import useVideoPlayerContext from '@/features/youtube/hooks/useVideoPlayerContext'; +import useDebounceEffect from '@/shared/hooks/useDebounceEffect'; + +const useWave = () => { + const { interval, videoLength, partStartTime, setPartStartTime, isPlayingEntire } = + useCollectingPartContext(); + const video = useVideoPlayerContext(); + const boxRef = useRef(null); + const [xPos, setXPos] = useState<{ initial: number; scroll: number } | null>(null); + + const maxPartStartTime = videoLength - interval; + const progressWidth = 100 + (interval - 5) * 5; + const isInterval = video.playerState === YT.PlayerState.PLAYING && !isPlayingEntire; + const isEntire = video.playerState === YT.PlayerState.PLAYING && isPlayingEntire; + + const scrollStartTime: React.UIEventHandler = (e) => { + const { scrollWidth, scrollLeft } = e.currentTarget; + + if (!boxRef.current) return; + const clientWidth = boxRef.current?.clientWidth; + const unit = (scrollWidth - clientWidth) / maxPartStartTime; + const partStartTimeToChange = Math.floor(scrollLeft / unit); + if (partStartTimeToChange >= 0 && partStartTimeToChange <= maxPartStartTime) { + setPartStartTime(partStartTimeToChange); + } + }; + + const wheelStartTime: React.WheelEventHandler = (e) => { + console.log('[wheel]'); + if (!boxRef.current) return; + + if (Math.abs(e.deltaX) < Math.abs(e.deltaY)) { + e.currentTarget?.scrollTo({ + left: e.deltaY / 1.2 + boxRef.current?.scrollLeft, + behavior: 'smooth', + }); + } + }; + + const playVideo = () => { + if (video.playerState !== YT.PlayerState.PLAYING) { + video.play(); + } + }; + + const dragStart: React.MouseEventHandler = (e) => { + e.preventDefault(); + if (boxRef.current) { + setXPos({ + initial: e.screenX, + scroll: boxRef.current?.scrollLeft, + }); + } + }; + + const dragMoving: React.MouseEventHandler = ({ screenX }) => { + if (!xPos) return; + + boxRef.current?.scrollTo({ + left: xPos.scroll + (xPos.initial - screenX) / 0.3, + behavior: 'instant', + }); + }; + + const dragEnd = () => { + setXPos(null); + }; + + useDebounceEffect(() => video.seekTo(partStartTime), [partStartTime], 150); + useDebounceEffect( + () => { + if (boxRef.current) { + const unit = + (boxRef.current.scrollWidth - boxRef.current.clientWidth) / (videoLength - interval); + boxRef.current.scrollTo({ + left: partStartTime * unit + 2.5, + behavior: 'instant', + }); + } + video.seekTo(partStartTime); + }, + [interval], + 300 + ); + + return { + boxRef, + progressWidth, + interval, + maxPartStartTime, + isInterval, + isEntire, + scrollStartTime, + wheelStartTime, + playVideo, + dragStart, + dragMoving, + dragEnd, + }; +}; + +export default useWave; diff --git a/frontend/src/features/songs/components/CollectingPartProvider.tsx b/frontend/src/features/songs/components/CollectingPartProvider.tsx index 43aa8446d..e98eeca3d 100644 --- a/frontend/src/features/songs/components/CollectingPartProvider.tsx +++ b/frontend/src/features/songs/components/CollectingPartProvider.tsx @@ -1,7 +1,6 @@ -import { createContext, useEffect, useRef, useState } from 'react'; +import { createContext, useEffect, useState } from 'react'; import { MAX_PART_INTERVAL, MIN_PART_INTERVAL } from '@/features/songs/constants/partInterval'; import useVideoPlayerContext from '@/features/youtube/hooks/useVideoPlayerContext'; -import useDebounceEffect from '@/shared/hooks/useDebounceEffect'; import type { PropsWithChildren } from 'react'; interface CollectingPartProviderProps { @@ -14,7 +13,6 @@ interface CollectingPartContextProps extends CollectingPartProviderProps { partStartTime: number; interval: number; isPlayingEntire: boolean; - boxRef: React.RefObject; setPartStartTime: React.Dispatch>; increasePartInterval: () => void; decreasePartInterval: () => void; @@ -32,7 +30,6 @@ export const CollectingPartProvider = ({ const [partStartTime, setPartStartTime] = useState(0); const [isPlayingEntire, setIsPlayingEntire] = useState(false); const { playerState, seekTo } = useVideoPlayerContext(); - const boxRef = useRef(null); const toggleEntirePlaying = () => { if (isPlayingEntire) { @@ -53,23 +50,6 @@ export const CollectingPartProvider = ({ setInterval(interval - 1); }; - useDebounceEffect(() => seekTo(partStartTime), [partStartTime], 150); - useDebounceEffect( - () => { - if (boxRef.current) { - const unit = - (boxRef.current.scrollWidth - boxRef.current.clientWidth) / (videoLength - interval); - boxRef.current.scrollTo({ - left: partStartTime * unit + 2.5, - behavior: 'instant', - }); - } - seekTo(partStartTime); - }, - [interval], - 300 - ); - useEffect(() => { if (isPlayingEntire || playerState !== YT.PlayerState.PLAYING) return; @@ -91,7 +71,6 @@ export const CollectingPartProvider = ({ songId, songVideoId, isPlayingEntire, - boxRef, setPartStartTime, increasePartInterval, decreasePartInterval, From 6505614d3450cb25a7d053d2a734afc503b437a3 Mon Sep 17 00:00:00 2001 From: ukkodeveloper Date: Wed, 18 Oct 2023 12:57:17 +0900 Subject: [PATCH 71/95] =?UTF-8?q?design:=20stepper=20=EB=B2=84=ED=8A=BC=20?= =?UTF-8?q?=ED=81=AC=EA=B8=B0=20=EC=A1=B0=EC=A0=88=20=EB=B0=8F=20=20?= =?UTF-8?q?=EC=8A=A4=ED=81=AC=EB=A1=A4=20=EB=94=94=EC=9E=90=EC=9D=B8=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/features/killingParts/components/SoundWave.tsx | 2 +- .../killingParts/components/VideoIntervalStepper.tsx | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/frontend/src/features/killingParts/components/SoundWave.tsx b/frontend/src/features/killingParts/components/SoundWave.tsx index f906fa98e..b18614458 100644 --- a/frontend/src/features/killingParts/components/SoundWave.tsx +++ b/frontend/src/features/killingParts/components/SoundWave.tsx @@ -15,7 +15,7 @@ const SoundWave = forwardRef( const boxPos = boxRef.current?.scrollLeft + boxRef.current?.clientWidth / 2 - progressWidth / 2; - const containerRightEdge = boxPos + progressWidth - 1; + const containerRightEdge = boxPos + progressWidth; const itemRightEdge = dom.offsetLeft; if (itemRightEdge >= boxPos && itemRightEdge <= containerRightEdge) { diff --git a/frontend/src/features/killingParts/components/VideoIntervalStepper.tsx b/frontend/src/features/killingParts/components/VideoIntervalStepper.tsx index 354f13c31..923a3dcb3 100644 --- a/frontend/src/features/killingParts/components/VideoIntervalStepper.tsx +++ b/frontend/src/features/killingParts/components/VideoIntervalStepper.tsx @@ -24,7 +24,6 @@ const StepperElementStyle = css` padding: 4px 11px; font-weight: 700; - line-height: 1.8; text-align: center; border: none; @@ -36,6 +35,8 @@ const ControlButton = styled.button` color: ${({ theme: { color } }) => color.white}; background-color: ${({ theme: { color } }) => color.secondary}; + font-size: 24px; + &:active { background-color: ${({ theme: { color } }) => color.disabled}; transition: box-shadow 0.2s ease; @@ -46,6 +47,9 @@ const CountText = styled.p` ${StepperElementStyle}; color: ${({ theme: { color } }) => color.black}; background-color: ${({ theme: { color } }) => color.white}; + display: flex; + justify-content: center; + align-items: center; &:active { box-shadow: 0 0 0 1px inset ${({ theme: { color } }) => color.magenta300}; From f1a42ffc402b7a09e1ac43452f32512357fb05a9 Mon Sep 17 00:00:00 2001 From: ukkodeveloper Date: Wed, 18 Oct 2023 15:38:55 +0900 Subject: [PATCH 72/95] =?UTF-8?q?feat:=20song=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?=EB=AA=85=EC=84=B8=20=EB=B3=80=EA=B2=BD=EC=97=90=20=EB=94=B0?= =?UTF-8?q?=EB=A5=B8=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/pages/PartCollectingPage.tsx | 10 ++++------ frontend/src/shared/types/song.ts | 12 +++++------- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/frontend/src/pages/PartCollectingPage.tsx b/frontend/src/pages/PartCollectingPage.tsx index d9ee467fc..f79ccbfc5 100644 --- a/frontend/src/pages/PartCollectingPage.tsx +++ b/frontend/src/pages/PartCollectingPage.tsx @@ -12,17 +12,15 @@ import Spacing from '@/shared/components/Spacing'; import SRHeading from '@/shared/components/SRHeading'; import useFetch from '@/shared/hooks/useFetch'; import fetcher from '@/shared/remotes'; -import type { VotingSongList } from '@/shared/types/song'; +import type { SongInfo } from '@/shared/types/song'; const PartCollectingPage = () => { const { id: songId } = useParams(); // TODO: 조회 API 만들어야함. - const { data: votingSongs } = useFetch(() => - fetcher(`/voting-songs/${songId}`, 'GET') - ); + const { data: songInfo } = useFetch(() => fetcher(`/songs/${songId}`, 'GET')); - if (!votingSongs) return; - const { id, title, singer, videoLength, songVideoId, albumCoverUrl } = votingSongs.currentSong; + if (!songInfo) return; + const { id, title, singer, videoLength, songVideoId, albumCoverUrl } = songInfo; return ( <> diff --git a/frontend/src/shared/types/song.ts b/frontend/src/shared/types/song.ts index b5e080184..77a2ea658 100644 --- a/frontend/src/shared/types/song.ts +++ b/frontend/src/shared/types/song.ts @@ -1,3 +1,5 @@ +import type { Genre } from '@/features/songs/types/Song.type'; + export interface SongDetailEntries { prevSongs: SongDetail[]; currentSong: SongDetail; @@ -26,18 +28,14 @@ export interface KillingPart { likeStatus: boolean; } -export interface SongVoting { +// TODO: songDetail type과 다른 점...? +export interface SongInfo { id: number; title: string; singer: string; videoLength: number; songVideoId: string; albumCoverUrl: string; + genre: Genre; killingParts: KillingPart[]; } - -export interface VotingSongList { - prevSongs: SongVoting[]; - currentSong: SongVoting; - nextSongs: SongVoting[]; -} From 9a490d8f0915eb460434eec2fae27729cfebfc5a Mon Sep 17 00:00:00 2001 From: ukkodeveloper Date: Thu, 19 Oct 2023 04:11:10 +0900 Subject: [PATCH 73/95] =?UTF-8?q?chore:=20=ED=8E=98=EC=9D=B4=EC=A7=80=20?= =?UTF-8?q?=EB=82=B4=EC=97=90=EC=84=9C=20=EC=82=AC=EC=9A=A9=EB=90=A0=20svg?= =?UTF-8?q?=20=ED=8C=8C=EC=9D=BC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/assets/icon/pin.svg | 2 ++ frontend/src/assets/icon/play-stream.svg | 3 +++ frontend/src/assets/icon/remove.svg | 3 +++ 3 files changed, 8 insertions(+) create mode 100644 frontend/src/assets/icon/pin.svg create mode 100644 frontend/src/assets/icon/play-stream.svg create mode 100644 frontend/src/assets/icon/remove.svg diff --git a/frontend/src/assets/icon/pin.svg b/frontend/src/assets/icon/pin.svg new file mode 100644 index 000000000..60dfa4183 --- /dev/null +++ b/frontend/src/assets/icon/pin.svg @@ -0,0 +1,2 @@ + \ No newline at end of file diff --git a/frontend/src/assets/icon/play-stream.svg b/frontend/src/assets/icon/play-stream.svg new file mode 100644 index 000000000..090a75f92 --- /dev/null +++ b/frontend/src/assets/icon/play-stream.svg @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/frontend/src/assets/icon/remove.svg b/frontend/src/assets/icon/remove.svg new file mode 100644 index 000000000..802ca7781 --- /dev/null +++ b/frontend/src/assets/icon/remove.svg @@ -0,0 +1,3 @@ + \ No newline at end of file From dd52fc7506ef37d5451f71e732eef2b6d1b19b70 Mon Sep 17 00:00:00 2001 From: ukkodeveloper Date: Thu, 19 Oct 2023 04:11:41 +0900 Subject: [PATCH 74/95] =?UTF-8?q?fix:=20=EC=98=81=EC=83=81=20=EB=81=8A?= =?UTF-8?q?=EA=B9=80=20=ED=98=84=EC=83=81=EC=9C=BC=EB=A1=9C=20=EC=9D=B8?= =?UTF-8?q?=ED=95=B4=20debounce=20=EA=B8=B8=EC=9D=B4=20=EC=97=B0=EC=9E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/features/killingParts/hooks/useWave.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/features/killingParts/hooks/useWave.ts b/frontend/src/features/killingParts/hooks/useWave.ts index d92fdd09c..89a8fe0ec 100644 --- a/frontend/src/features/killingParts/hooks/useWave.ts +++ b/frontend/src/features/killingParts/hooks/useWave.ts @@ -68,7 +68,7 @@ const useWave = () => { setXPos(null); }; - useDebounceEffect(() => video.seekTo(partStartTime), [partStartTime], 150); + useDebounceEffect(() => video.seekTo(partStartTime), [partStartTime], 300); useDebounceEffect( () => { if (boxRef.current) { From 92afe469889ceead2f48bb04ba86f1a0ca866ff8 Mon Sep 17 00:00:00 2001 From: ukkodeveloper Date: Thu, 19 Oct 2023 04:14:46 +0900 Subject: [PATCH 75/95] =?UTF-8?q?feat:=20=EB=93=B1=EB=A1=9D=20=EC=8B=9C?= =?UTF-8?q?=EA=B0=84=20=EC=9E=84=EC=8B=9C=20=EC=A0=80=EC=9E=A5=ED=95=A0=20?= =?UTF-8?q?=EC=88=98=20=EC=9E=88=EB=8F=84=EB=A1=9D=20pin=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../killingParts/components/VideoBadges.tsx | 178 ++++++++++++++++-- .../components/VideoIntervalStepper.tsx | 5 +- .../components/CollectingPartProvider.tsx | 2 + 3 files changed, 173 insertions(+), 12 deletions(-) diff --git a/frontend/src/features/killingParts/components/VideoBadges.tsx b/frontend/src/features/killingParts/components/VideoBadges.tsx index 107212d49..5164b9cbb 100644 --- a/frontend/src/features/killingParts/components/VideoBadges.tsx +++ b/frontend/src/features/killingParts/components/VideoBadges.tsx @@ -1,18 +1,73 @@ -import styled from 'styled-components'; +import { useRef, useState } from 'react'; +import styled, { css, keyframes } from 'styled-components'; import playIcon from '@/assets/icon/fill-play.svg'; import pauseIcon from '@/assets/icon/pause.svg'; +import pinIcon from '@/assets/icon/pin.svg'; +import playStreamIcon from '@/assets/icon/play-stream.svg'; +import removeIcon from '@/assets/icon/remove.svg'; import useCollectingPartContext from '@/features/killingParts/hooks/useCollectingPartContext'; import useVideoPlayerContext from '@/features/youtube/hooks/useVideoPlayerContext'; import Flex from '@/shared/components/Flex/Flex'; +import Spacing from '@/shared/components/Spacing'; import { toMinSecText } from '@/shared/utils/convertTime'; const VideoBadges = () => { - const { partStartTime, isPlayingEntire, toggleEntirePlaying } = useCollectingPartContext(); + const { + partStartTime, + isPlayingEntire, + interval, + setPartStartTime, + setInterval, + toggleEntirePlaying, + } = useCollectingPartContext(); const video = useVideoPlayerContext(); const partStartTimeText = toMinSecText(partStartTime); + const ref = useRef(null); + + // state + const [timeListPinned, setTimeListPinned] = useState< + { partStartTime: number; interval: number; text: string }[] + >([]); + const [activePinnedIndex, setActivePinnedIndex] = useState(null); + + const animationKeyRef = useRef(1); + + const saveTime = () => { + const text = `${toMinSecText(partStartTime)} ~ ${toMinSecText(partStartTime + interval)}`; + if (timeListPinned.find((timePinned) => timePinned.text === text)) return; + + setTimeListPinned((prevTimeList) => [ + { + partStartTime, + interval, + text, + }, + ...prevTimeList, + ]); + + setActivePinnedIndex(0); + if (ref.current) { + ref.current.scrollTo({ + left: 0, + behavior: 'smooth', + }); + } + + animationKeyRef.current += 1; + }; const isPaused = video.playerState === YT.PlayerState.PAUSED; + const deletePin = () => { + if (activePinnedIndex) { + setTimeListPinned(timeListPinned.filter((_, index) => index !== activePinnedIndex)); + } else { + setTimeListPinned(timeListPinned.slice(1)); + } + + setActivePinnedIndex(null); + }; + const videoPlay = () => { if (isPlayingEntire) { video.play(); @@ -25,15 +80,48 @@ const VideoBadges = () => { }; return ( - - {partStartTimeText} - - {'재생 - - - 전체 듣기 - - + <> + + + + {partStartTimeText} + + + 나만의 파트 임시 저장 + + + {'재생 + + + 전체 듣기 + + + + + + {timeListPinned.length !== 0 && ( + + 나만의 파트 임시 저장 삭제하기 + + )} + {timeListPinned.map(({ partStartTime, interval, text }, index) => ( + { + setPartStartTime(partStartTime); + setInterval(interval); + + setActivePinnedIndex(index); + }} + $isActive={index === activePinnedIndex} + $isNew={index === 0 && index === activePinnedIndex} + > + {text} + + ))} + + ); }; export default VideoBadges; @@ -61,3 +149,71 @@ const Badge = styled.span<{ $isActive?: boolean }>` font-size: 16px; } `; + +const StartBadge = styled(Badge)` + margin-right: auto; + letter-spacing: 1px; +`; + +const PinFlex = styled(Flex)` + overflow-x: scroll; + position: relative; +`; + +const slideLeft = keyframes` + from { + opacity: 0; + transform: translateX(-30px); + + } + to { + opacity: 1; + transform: translateX(0); + } +`; + +const slideRight = keyframes` + from { + transform: translateX(-10px); + } + to { + transform: translateX(0); + } +`; + +const PinBadge = styled(Badge)<{ $isActive?: boolean; $isNew?: boolean }>` + background-color: ${({ theme: { color }, $isActive }) => + $isActive ? color.magenta700 : color.disabledBackground}; + + z-index: ${({ $isActive }) => ($isActive ? 1 : 0)}; + opacity: ${({ $isActive }) => ($isActive ? 1 : 0.5)} + + border: none; + width: 100px; + white-space: nowrap; + + color: ${({ theme: { color }, $isActive }) => ($isActive ? color.white : color.black)}; + font-size: 12px; + margin-right: 4px; + border-radius: 4px; + + transition: background-color 0.3s ease-in-out; + animation: ${({ $isNew }) => + $isNew + ? css` + ${slideLeft} 1s forwards + ` + : css` + ${slideRight} 0.5s forwards + `}; + + +`; + +const DeleteBadge = styled(Badge)` + border-radius: 50%; + height: 30px; + min-width: 30px; + padding: 0; + margin-right: 10px; +`; diff --git a/frontend/src/features/killingParts/components/VideoIntervalStepper.tsx b/frontend/src/features/killingParts/components/VideoIntervalStepper.tsx index 923a3dcb3..0e7c721c6 100644 --- a/frontend/src/features/killingParts/components/VideoIntervalStepper.tsx +++ b/frontend/src/features/killingParts/components/VideoIntervalStepper.tsx @@ -21,11 +21,11 @@ const StepperElementStyle = css` min-width: 50px; margin: 0; - padding: 4px 11px; font-weight: 700; text-align: center; + height: 30px; border: none; border-radius: 10px; `; @@ -36,6 +36,9 @@ const ControlButton = styled.button` background-color: ${({ theme: { color } }) => color.secondary}; font-size: 24px; + display: flex; + justify-content: center; + align-items: center; &:active { background-color: ${({ theme: { color } }) => color.disabled}; diff --git a/frontend/src/features/songs/components/CollectingPartProvider.tsx b/frontend/src/features/songs/components/CollectingPartProvider.tsx index e98eeca3d..0448bd43f 100644 --- a/frontend/src/features/songs/components/CollectingPartProvider.tsx +++ b/frontend/src/features/songs/components/CollectingPartProvider.tsx @@ -14,6 +14,7 @@ interface CollectingPartContextProps extends CollectingPartProviderProps { interval: number; isPlayingEntire: boolean; setPartStartTime: React.Dispatch>; + setInterval: React.Dispatch>; increasePartInterval: () => void; decreasePartInterval: () => void; toggleEntirePlaying: () => void; @@ -72,6 +73,7 @@ export const CollectingPartProvider = ({ songVideoId, isPlayingEntire, setPartStartTime, + setInterval, increasePartInterval, decreasePartInterval, toggleEntirePlaying, From c3711ba7cbbf1074c2950cfe450f564e5312210d Mon Sep 17 00:00:00 2001 From: ukkodeveloper Date: Thu, 19 Oct 2023 04:38:22 +0900 Subject: [PATCH 76/95] =?UTF-8?q?refactor:=20pin=20=EA=B4=80=EB=A0=A8=20?= =?UTF-8?q?=EC=83=81=ED=83=9C=20=EB=84=A4=EC=9D=B4=EB=B0=8D=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../killingParts/components/VideoBadges.tsx | 60 ++++++++++--------- 1 file changed, 31 insertions(+), 29 deletions(-) diff --git a/frontend/src/features/killingParts/components/VideoBadges.tsx b/frontend/src/features/killingParts/components/VideoBadges.tsx index 5164b9cbb..a27854e34 100644 --- a/frontend/src/features/killingParts/components/VideoBadges.tsx +++ b/frontend/src/features/killingParts/components/VideoBadges.tsx @@ -25,18 +25,21 @@ const VideoBadges = () => { const ref = useRef(null); // state - const [timeListPinned, setTimeListPinned] = useState< + const [pinList, setPinList] = useState< { partStartTime: number; interval: number; text: string }[] >([]); - const [activePinnedIndex, setActivePinnedIndex] = useState(null); + const [activePinIndex, setActivePinIndex] = useState(null); - const animationKeyRef = useRef(1); + const pinAnimationRef = useRef(1); + const refreshPinAnimation = () => { + pinAnimationRef.current += 1; + }; - const saveTime = () => { + const addPin = () => { const text = `${toMinSecText(partStartTime)} ~ ${toMinSecText(partStartTime + interval)}`; - if (timeListPinned.find((timePinned) => timePinned.text === text)) return; + if (pinList.find((pin) => pin.text === text)) return; - setTimeListPinned((prevTimeList) => [ + setPinList((prevTimeList) => [ { partStartTime, interval, @@ -45,7 +48,7 @@ const VideoBadges = () => { ...prevTimeList, ]); - setActivePinnedIndex(0); + setActivePinIndex(0); if (ref.current) { ref.current.scrollTo({ left: 0, @@ -53,21 +56,27 @@ const VideoBadges = () => { }); } - animationKeyRef.current += 1; + refreshPinAnimation(); }; - const isPaused = video.playerState === YT.PlayerState.PAUSED; - const deletePin = () => { - if (activePinnedIndex) { - setTimeListPinned(timeListPinned.filter((_, index) => index !== activePinnedIndex)); + if (activePinIndex) { + setPinList(pinList.filter((_, index) => index !== activePinIndex)); } else { - setTimeListPinned(timeListPinned.slice(1)); + setPinList(pinList.slice(1)); } - setActivePinnedIndex(null); + setActivePinIndex(null); }; + const playPin = (start: number, interval: number, index: number) => () => { + setPartStartTime(start); + setInterval(interval); + setActivePinIndex(index); + }; + + const isPaused = video.playerState === YT.PlayerState.PAUSED; + const videoPlay = () => { if (isPlayingEntire) { video.play(); @@ -86,7 +95,7 @@ const VideoBadges = () => { {partStartTimeText} - + 나만의 파트 임시 저장 @@ -99,25 +108,20 @@ const VideoBadges = () => { - {timeListPinned.length !== 0 && ( + {pinList.length !== 0 && ( 나만의 파트 임시 저장 삭제하기 )} - {timeListPinned.map(({ partStartTime, interval, text }, index) => ( + {pinList.map((pin, index) => ( { - setPartStartTime(partStartTime); - setInterval(interval); - - setActivePinnedIndex(index); - }} - $isActive={index === activePinnedIndex} - $isNew={index === 0 && index === activePinnedIndex} + onClick={playPin(pin.partStartTime, pin.interval, index)} + $isActive={index === activePinIndex} + $isNew={index === 0 && index === activePinIndex} > - {text} + {pin.text} ))} @@ -206,8 +210,6 @@ const PinBadge = styled(Badge)<{ $isActive?: boolean; $isNew?: boolean }>` : css` ${slideRight} 0.5s forwards `}; - - `; const DeleteBadge = styled(Badge)` From 89b2b9cc3be6f51af5bfbf12e93182242d00b518 Mon Sep 17 00:00:00 2001 From: ukkodeveloper Date: Thu, 19 Oct 2023 06:06:06 +0900 Subject: [PATCH 77/95] =?UTF-8?q?design:=20=EC=83=81=ED=95=98=20=EA=B0=84?= =?UTF-8?q?=EA=B2=A9=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/features/killingParts/components/VideoController.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/features/killingParts/components/VideoController.tsx b/frontend/src/features/killingParts/components/VideoController.tsx index 24726625d..eba8bd419 100644 --- a/frontend/src/features/killingParts/components/VideoController.tsx +++ b/frontend/src/features/killingParts/components/VideoController.tsx @@ -6,7 +6,7 @@ import Flex from '@/shared/components/Flex/Flex'; const VideoController = () => { return ( - + 길이 선택 From 69e4614dac99ae56ee03aa0d5a3ab148de42ece6 Mon Sep 17 00:00:00 2001 From: ukkodeveloper Date: Thu, 19 Oct 2023 06:09:59 +0900 Subject: [PATCH 78/95] =?UTF-8?q?fix:=20=ED=95=80=20active=20=EC=83=81?= =?UTF-8?q?=ED=83=9C=EB=A5=BC=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=A4=91?= =?UTF-8?q?=EC=8B=AC=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../killingParts/components/VideoBadges.tsx | 59 +++++++++++-------- 1 file changed, 33 insertions(+), 26 deletions(-) diff --git a/frontend/src/features/killingParts/components/VideoBadges.tsx b/frontend/src/features/killingParts/components/VideoBadges.tsx index a27854e34..5d590de8b 100644 --- a/frontend/src/features/killingParts/components/VideoBadges.tsx +++ b/frontend/src/features/killingParts/components/VideoBadges.tsx @@ -1,4 +1,4 @@ -import { useRef, useState } from 'react'; +import { useMemo, useRef, useState } from 'react'; import styled, { css, keyframes } from 'styled-components'; import playIcon from '@/assets/icon/fill-play.svg'; import pauseIcon from '@/assets/icon/pause.svg'; @@ -8,9 +8,13 @@ import removeIcon from '@/assets/icon/remove.svg'; import useCollectingPartContext from '@/features/killingParts/hooks/useCollectingPartContext'; import useVideoPlayerContext from '@/features/youtube/hooks/useVideoPlayerContext'; import Flex from '@/shared/components/Flex/Flex'; -import Spacing from '@/shared/components/Spacing'; import { toMinSecText } from '@/shared/utils/convertTime'; +interface Pin { + partStartTime: number; + interval: number; + text: string; +} const VideoBadges = () => { const { partStartTime, @@ -24,11 +28,15 @@ const VideoBadges = () => { const partStartTimeText = toMinSecText(partStartTime); const ref = useRef(null); - // state - const [pinList, setPinList] = useState< - { partStartTime: number; interval: number; text: string }[] - >([]); - const [activePinIndex, setActivePinIndex] = useState(null); + const [pinList, setPinList] = useState([]); + + const activePinIndex = useMemo( + () => + pinList.findIndex((pin) => pin.partStartTime === partStartTime && pin.interval === interval), + [pinList, partStartTime, interval] + ); + + const isPinListEmpty = pinList.length === 0; const pinAnimationRef = useRef(1); const refreshPinAnimation = () => { @@ -36,8 +44,7 @@ const VideoBadges = () => { }; const addPin = () => { - const text = `${toMinSecText(partStartTime)} ~ ${toMinSecText(partStartTime + interval)}`; - if (pinList.find((pin) => pin.text === text)) return; + const text = `${toMinSecText(partStartTime)}`; setPinList((prevTimeList) => [ { @@ -45,10 +52,9 @@ const VideoBadges = () => { interval, text, }, - ...prevTimeList, + ...prevTimeList.filter((pin) => pin.text !== text), ]); - setActivePinIndex(0); if (ref.current) { ref.current.scrollTo({ left: 0, @@ -60,19 +66,16 @@ const VideoBadges = () => { }; const deletePin = () => { - if (activePinIndex) { + if (activePinIndex >= 0) { setPinList(pinList.filter((_, index) => index !== activePinIndex)); } else { setPinList(pinList.slice(1)); } - - setActivePinIndex(null); }; - const playPin = (start: number, interval: number, index: number) => () => { + const playPin = (start: number, interval: number) => () => { setPartStartTime(start); setInterval(interval); - setActivePinIndex(index); }; const isPaused = video.playerState === YT.PlayerState.PAUSED; @@ -95,7 +98,7 @@ const VideoBadges = () => { {partStartTimeText} - + 나만의 파트 임시 저장 @@ -105,10 +108,8 @@ const VideoBadges = () => { 전체 듣기 - - - {pinList.length !== 0 && ( + {!isPinListEmpty && ( 나만의 파트 임시 저장 삭제하기 @@ -117,7 +118,7 @@ const VideoBadges = () => { @@ -140,14 +141,20 @@ const Badge = styled.span<{ $isActive?: boolean }>` padding: 0 10px; font-size: 14px; - color: ${({ theme: { color }, $isActive }) => ($isActive ? color.black : color.white)}; + color: ${({ theme: { color } }) => color.white}; text-align: center; background-color: ${({ theme: { color }, $isActive }) => - $isActive ? color.magenta200 : color.disabled}; + $isActive ? color.magenta700 : color.disabled}; border-radius: 40px; - transition: background-color 0.2s ease-in; + transition: + background-color 0.2s ease-in, + box-shadow 0.2s ease; + + &:active { + box-shadow: 0 0 0 1px inset white; + } @media (min-width: ${({ theme }) => theme.breakPoints.md}) { font-size: 16px; @@ -178,7 +185,7 @@ const slideLeft = keyframes` const slideRight = keyframes` from { - transform: translateX(-10px); + transform: translateX(-15px); } to { transform: translateX(0); @@ -193,7 +200,7 @@ const PinBadge = styled(Badge)<{ $isActive?: boolean; $isNew?: boolean }>` opacity: ${({ $isActive }) => ($isActive ? 1 : 0.5)} border: none; - width: 100px; + width: 50px; white-space: nowrap; color: ${({ theme: { color }, $isActive }) => ($isActive ? color.white : color.black)}; From ad9a4073270beca57fe27f3a4b2ea66f21a01d66 Mon Sep 17 00:00:00 2001 From: ukkodeveloper Date: Thu, 19 Oct 2023 06:21:53 +0900 Subject: [PATCH 79/95] =?UTF-8?q?refactor:=20usePin=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../killingParts/components/VideoBadges.tsx | 76 +++---------------- .../src/features/killingParts/hooks/usePin.ts | 76 +++++++++++++++++++ 2 files changed, 88 insertions(+), 64 deletions(-) create mode 100644 frontend/src/features/killingParts/hooks/usePin.ts diff --git a/frontend/src/features/killingParts/components/VideoBadges.tsx b/frontend/src/features/killingParts/components/VideoBadges.tsx index 5d590de8b..2b96cc587 100644 --- a/frontend/src/features/killingParts/components/VideoBadges.tsx +++ b/frontend/src/features/killingParts/components/VideoBadges.tsx @@ -1,4 +1,3 @@ -import { useMemo, useRef, useState } from 'react'; import styled, { css, keyframes } from 'styled-components'; import playIcon from '@/assets/icon/fill-play.svg'; import pauseIcon from '@/assets/icon/pause.svg'; @@ -6,77 +5,26 @@ import pinIcon from '@/assets/icon/pin.svg'; import playStreamIcon from '@/assets/icon/play-stream.svg'; import removeIcon from '@/assets/icon/remove.svg'; import useCollectingPartContext from '@/features/killingParts/hooks/useCollectingPartContext'; +import usePin from '@/features/killingParts/hooks/usePin'; import useVideoPlayerContext from '@/features/youtube/hooks/useVideoPlayerContext'; import Flex from '@/shared/components/Flex/Flex'; import { toMinSecText } from '@/shared/utils/convertTime'; -interface Pin { - partStartTime: number; - interval: number; - text: string; -} const VideoBadges = () => { + const { partStartTime, isPlayingEntire, toggleEntirePlaying } = useCollectingPartContext(); const { - partStartTime, - isPlayingEntire, - interval, - setPartStartTime, - setInterval, - toggleEntirePlaying, - } = useCollectingPartContext(); + pinList, + isPinListEmpty, + activePinIndex, + ref, + pinAnimationRef, + addPin, + deletePin, + playPin, + } = usePin(); + const video = useVideoPlayerContext(); const partStartTimeText = toMinSecText(partStartTime); - const ref = useRef(null); - - const [pinList, setPinList] = useState([]); - - const activePinIndex = useMemo( - () => - pinList.findIndex((pin) => pin.partStartTime === partStartTime && pin.interval === interval), - [pinList, partStartTime, interval] - ); - - const isPinListEmpty = pinList.length === 0; - - const pinAnimationRef = useRef(1); - const refreshPinAnimation = () => { - pinAnimationRef.current += 1; - }; - - const addPin = () => { - const text = `${toMinSecText(partStartTime)}`; - - setPinList((prevTimeList) => [ - { - partStartTime, - interval, - text, - }, - ...prevTimeList.filter((pin) => pin.text !== text), - ]); - - if (ref.current) { - ref.current.scrollTo({ - left: 0, - behavior: 'smooth', - }); - } - - refreshPinAnimation(); - }; - - const deletePin = () => { - if (activePinIndex >= 0) { - setPinList(pinList.filter((_, index) => index !== activePinIndex)); - } else { - setPinList(pinList.slice(1)); - } - }; - - const playPin = (start: number, interval: number) => () => { - setPartStartTime(start); - setInterval(interval); - }; const isPaused = video.playerState === YT.PlayerState.PAUSED; diff --git a/frontend/src/features/killingParts/hooks/usePin.ts b/frontend/src/features/killingParts/hooks/usePin.ts new file mode 100644 index 000000000..f20332692 --- /dev/null +++ b/frontend/src/features/killingParts/hooks/usePin.ts @@ -0,0 +1,76 @@ +import { useMemo, useRef, useState } from 'react'; +import useCollectingPartContext from '@/features/killingParts/hooks/useCollectingPartContext'; +import { toMinSecText } from '@/shared/utils/convertTime'; + +interface Pin { + partStartTime: number; + interval: number; + text: string; +} + +const usePin = () => { + const { partStartTime, interval, setPartStartTime, setInterval } = useCollectingPartContext(); + const [pinList, setPinList] = useState([]); + const ref = useRef(null); + + const activePinIndex = useMemo( + () => + pinList.findIndex((pin) => pin.partStartTime === partStartTime && pin.interval === interval), + [pinList, partStartTime, interval] + ); + + const isPinListEmpty = pinList.length === 0; + + const pinAnimationRef = useRef(1); + const refreshPinAnimation = () => { + pinAnimationRef.current += 1; + }; + + const addPin = () => { + const text = `${toMinSecText(partStartTime)}`; + + setPinList((prevTimeList) => [ + { + partStartTime, + interval, + text, + }, + ...prevTimeList.filter((pin) => pin.text !== text), + ]); + + if (ref.current) { + ref.current.scrollTo({ + left: 0, + behavior: 'smooth', + }); + } + + refreshPinAnimation(); + }; + + const deletePin = () => { + if (activePinIndex >= 0) { + setPinList(pinList.filter((_, index) => index !== activePinIndex)); + } else { + setPinList(pinList.slice(1)); + } + }; + + const playPin = (start: number, interval: number) => () => { + setPartStartTime(start); + setInterval(interval); + }; + + return { + pinList, + isPinListEmpty, + activePinIndex, + pinAnimationRef, + ref, + addPin, + deletePin, + playPin, + }; +}; + +export default usePin; From 44fd17a86750feeae751c9df6c986837693ea5b6 Mon Sep 17 00:00:00 2001 From: ukkodeveloper Date: Thu, 19 Oct 2023 06:44:31 +0900 Subject: [PATCH 80/95] =?UTF-8?q?feat:=20=EB=93=B1=EB=A1=9D=20=EC=8B=9C?= =?UTF-8?q?=EC=97=90=20=EC=9D=B4=EC=A0=84=20=ED=8E=98=EC=9D=B4=EC=A7=80=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99=20=EB=B0=8F=20=EB=AA=A8=EB=8B=AC=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../killingParts/components/RegisterPart.tsx | 44 ++++++++++--------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/frontend/src/features/killingParts/components/RegisterPart.tsx b/frontend/src/features/killingParts/components/RegisterPart.tsx index dbe469708..0d8359b1e 100644 --- a/frontend/src/features/killingParts/components/RegisterPart.tsx +++ b/frontend/src/features/killingParts/components/RegisterPart.tsx @@ -1,3 +1,4 @@ +import { useNavigate } from 'react-router-dom'; import styled from 'styled-components'; import { useAuthContext } from '@/features/auth/components/AuthProvider'; import useCollectingPartContext from '@/features/killingParts/hooks/useCollectingPartContext'; @@ -5,55 +6,50 @@ import { usePostKillingPart } from '@/features/killingParts/remotes/usePostKilli import useVideoPlayerContext from '@/features/youtube/hooks/useVideoPlayerContext'; import useModal from '@/shared/components/Modal/hooks/useModal'; import Modal from '@/shared/components/Modal/Modal'; -import useToastContext from '@/shared/components/Toast/hooks/useToastContext'; +import Spacing from '@/shared/components/Spacing'; import { toPlayingTimeText } from '@/shared/utils/convertTime'; -import copyClipboard from '@/shared/utils/copyClipBoard'; const RegisterPart = () => { const { isOpen, openModal, closeModal } = useModal(); - const { showToast } = useToastContext(); const { user } = useAuthContext(); - const { interval, partStartTime, songId, songVideoId } = useCollectingPartContext(); + const { interval, partStartTime, songId } = useCollectingPartContext(); const video = useVideoPlayerContext(); const { createKillingPart } = usePostKillingPart(); + const navigate = useNavigate(); // 현재 useMutation 훅이 response 객체를 리턴하지 않고 내부적으로 처리합니다. // 때문에 컴포넌트 단에서 createKillingPart 성공 여부에 따라 등록 완료 만료를 처리를 할 수 없어요! - // 현재 비로그인 시에 등록을 누르면 두 개의 모달이 뜹니다. + // 현재 비로그인 시에 등록을 누르면 두 개의 모달이 뜹니다.정 const submitKillingPart = async () => { video.pause(); await createKillingPart(songId, { startSecond: partStartTime, length: interval }); - openModal(); + navigate(-1); }; const voteTimeText = toPlayingTimeText(partStartTime, partStartTime + interval); - const copyPartVideoUrl = async () => { - await copyClipboard(`https://www.youtube.com/watch?v=${songVideoId}&t=${partStartTime}s`); - closeModal(); - showToast('클립보드에 영상링크가 복사되었습니다.'); - }; - return ( <> - + 등록 - {user?.nickname}님의 - 킬링파트 등록을 완료했습니다. + {user?.nickname}님의 파트 저장 - {voteTimeText} - 파트를 공유해 보세요😀 + + {voteTimeText} + + + 나만의 파트로 등록하시겠습니까? - 확인 + 취소 - - 공유하기 + + 등록 @@ -125,3 +121,11 @@ const ButtonContainer = styled.div` gap: 16px; width: 100%; `; + +const Part = styled.span` + background-color: ${({ theme: { color } }) => color.disabled}; + border-radius: 10px; + padding: 6px 11px; + letter-spacing: 1px; + color: white; +`; From 1f22ecaca04d73b45c4d830ead04e44d710522ba Mon Sep 17 00:00:00 2001 From: ukkodeveloper Date: Thu, 19 Oct 2023 07:02:23 +0900 Subject: [PATCH 81/95] =?UTF-8?q?design:=20pin=20=EB=94=94=EC=9E=90?= =?UTF-8?q?=EC=9D=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../killingParts/components/VideoBadges.tsx | 54 ++++++++++--------- 1 file changed, 30 insertions(+), 24 deletions(-) diff --git a/frontend/src/features/killingParts/components/VideoBadges.tsx b/frontend/src/features/killingParts/components/VideoBadges.tsx index 2b96cc587..3baba8cce 100644 --- a/frontend/src/features/killingParts/components/VideoBadges.tsx +++ b/frontend/src/features/killingParts/components/VideoBadges.tsx @@ -22,12 +22,10 @@ const VideoBadges = () => { deletePin, playPin, } = usePin(); - const video = useVideoPlayerContext(); const partStartTimeText = toMinSecText(partStartTime); const isPaused = video.playerState === YT.PlayerState.PAUSED; - const videoPlay = () => { if (isPlayingEntire) { video.play(); @@ -62,23 +60,36 @@ const VideoBadges = () => { 나만의 파트 임시 저장 삭제하기 )} - {pinList.map((pin, index) => ( - - {pin.text} - - ))} + + {pinList.map((pin, index) => ( + + {pin.text} + + ))} + ); }; export default VideoBadges; +const PinFlex = styled(Flex)` + //overflow-x: scroll; + //position: relative; + width: 100%; +`; + +const PinInner = styled(Flex)` + overflow-x: scroll; + width: calc(100% - 44px); +`; + const Badge = styled.span<{ $isActive?: boolean }>` display: flex; align-items: center; @@ -114,12 +125,7 @@ const StartBadge = styled(Badge)` letter-spacing: 1px; `; -const PinFlex = styled(Flex)` - overflow-x: scroll; - position: relative; -`; - -const slideLeft = keyframes` +const slideFirstItem = keyframes` from { opacity: 0; transform: translateX(-30px); @@ -131,7 +137,7 @@ const slideLeft = keyframes` } `; -const slideRight = keyframes` +const slideRestItems = keyframes` from { transform: translateX(-15px); } @@ -151,19 +157,19 @@ const PinBadge = styled(Badge)<{ $isActive?: boolean; $isNew?: boolean }>` width: 50px; white-space: nowrap; - color: ${({ theme: { color }, $isActive }) => ($isActive ? color.white : color.black)}; + color: black; font-size: 12px; margin-right: 4px; border-radius: 4px; - transition: background-color 0.3s ease-in-out; + transition: background-color 0.5s ease-in-out; animation: ${({ $isNew }) => $isNew ? css` - ${slideLeft} 1s forwards + ${slideFirstItem} 1s forwards ` : css` - ${slideRight} 0.5s forwards + ${slideRestItems} 0.5s forwards `}; `; From 72516489723390e44f2671ffa6af9ebb732e75eb Mon Sep 17 00:00:00 2001 From: ukkodeveloper Date: Thu, 19 Oct 2023 12:19:30 +0900 Subject: [PATCH 82/95] =?UTF-8?q?fix:=20pin=20=EB=B3=80=EA=B2=BD=20?= =?UTF-8?q?=EC=8B=9C=20=EC=8A=A4=ED=81=AC=EB=A1=A4=20=EC=97=B0=EB=8F=99?= =?UTF-8?q?=EB=90=98=EC=A7=80=20=EC=95=8A=EB=8A=94=20=ED=98=84=EC=83=81=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../killingParts/components/WaveScrubber.tsx | 6 +- .../src/features/killingParts/hooks/usePin.ts | 26 ++++---- .../features/killingParts/hooks/useWave.ts | 60 ++++++++++--------- .../components/CollectingPartProvider.tsx | 20 ++++++- 4 files changed, 66 insertions(+), 46 deletions(-) diff --git a/frontend/src/features/killingParts/components/WaveScrubber.tsx b/frontend/src/features/killingParts/components/WaveScrubber.tsx index b6fb933e5..f545fc764 100644 --- a/frontend/src/features/killingParts/components/WaveScrubber.tsx +++ b/frontend/src/features/killingParts/components/WaveScrubber.tsx @@ -5,7 +5,7 @@ import Flex from '@/shared/components/Flex/Flex'; const WaveScrubber = () => { const { - boxRef, + waveScrubberRef, progressWidth, isInterval, isEntire, @@ -26,7 +26,7 @@ const WaveScrubber = () => { onWheel={wheelStartTime} onTouchStart={playVideo} $progressWidth={progressWidth} - ref={boxRef} + ref={waveScrubberRef} $gap={8} $align="center" onMouseDown={dragStart} @@ -34,7 +34,7 @@ const WaveScrubber = () => { onMouseUp={dragEnd} onMouseLeave={dragEnd} > - + {isInterval && } diff --git a/frontend/src/features/killingParts/hooks/usePin.ts b/frontend/src/features/killingParts/hooks/usePin.ts index f20332692..08ea68779 100644 --- a/frontend/src/features/killingParts/hooks/usePin.ts +++ b/frontend/src/features/killingParts/hooks/usePin.ts @@ -1,23 +1,19 @@ -import { useMemo, useRef, useState } from 'react'; +import { useRef } from 'react'; import useCollectingPartContext from '@/features/killingParts/hooks/useCollectingPartContext'; import { toMinSecText } from '@/shared/utils/convertTime'; -interface Pin { - partStartTime: number; - interval: number; - text: string; -} - const usePin = () => { - const { partStartTime, interval, setPartStartTime, setInterval } = useCollectingPartContext(); - const [pinList, setPinList] = useState([]); - const ref = useRef(null); + const { + partStartTime, + interval, + setPartStartTime, + setInterval, + pinList, + setPinList, + activePinIndex, + } = useCollectingPartContext(); - const activePinIndex = useMemo( - () => - pinList.findIndex((pin) => pin.partStartTime === partStartTime && pin.interval === interval), - [pinList, partStartTime, interval] - ); + const ref = useRef(null); const isPinListEmpty = pinList.length === 0; diff --git a/frontend/src/features/killingParts/hooks/useWave.ts b/frontend/src/features/killingParts/hooks/useWave.ts index 89a8fe0ec..728a0d223 100644 --- a/frontend/src/features/killingParts/hooks/useWave.ts +++ b/frontend/src/features/killingParts/hooks/useWave.ts @@ -4,10 +4,29 @@ import useVideoPlayerContext from '@/features/youtube/hooks/useVideoPlayerContex import useDebounceEffect from '@/shared/hooks/useDebounceEffect'; const useWave = () => { - const { interval, videoLength, partStartTime, setPartStartTime, isPlayingEntire } = - useCollectingPartContext(); + const { + interval, + videoLength, + partStartTime, + setPartStartTime, + isPlayingEntire, + activePinIndex, + } = useCollectingPartContext(); const video = useVideoPlayerContext(); - const boxRef = useRef(null); + const waveScrubberRef = useRef(null); + const scrollWaveToStartTime = () => { + if (waveScrubberRef.current) { + const unit = + (waveScrubberRef.current.scrollWidth - waveScrubberRef.current.clientWidth) / + (videoLength - interval); + waveScrubberRef.current.scrollTo({ + left: partStartTime * unit + 2.5, + behavior: 'instant', + }); + } + video.seekTo(partStartTime); + }; + const [xPos, setXPos] = useState<{ initial: number; scroll: number } | null>(null); const maxPartStartTime = videoLength - interval; @@ -18,22 +37,23 @@ const useWave = () => { const scrollStartTime: React.UIEventHandler = (e) => { const { scrollWidth, scrollLeft } = e.currentTarget; - if (!boxRef.current) return; - const clientWidth = boxRef.current?.clientWidth; + if (!waveScrubberRef.current) return; + + const clientWidth = waveScrubberRef.current?.clientWidth; const unit = (scrollWidth - clientWidth) / maxPartStartTime; const partStartTimeToChange = Math.floor(scrollLeft / unit); + if (partStartTimeToChange >= 0 && partStartTimeToChange <= maxPartStartTime) { setPartStartTime(partStartTimeToChange); } }; const wheelStartTime: React.WheelEventHandler = (e) => { - console.log('[wheel]'); - if (!boxRef.current) return; + if (!waveScrubberRef.current) return; if (Math.abs(e.deltaX) < Math.abs(e.deltaY)) { e.currentTarget?.scrollTo({ - left: e.deltaY / 1.2 + boxRef.current?.scrollLeft, + left: e.deltaY / 1.2 + waveScrubberRef.current?.scrollLeft, behavior: 'smooth', }); } @@ -47,10 +67,10 @@ const useWave = () => { const dragStart: React.MouseEventHandler = (e) => { e.preventDefault(); - if (boxRef.current) { + if (waveScrubberRef.current) { setXPos({ initial: e.screenX, - scroll: boxRef.current?.scrollLeft, + scroll: waveScrubberRef.current?.scrollLeft, }); } }; @@ -58,7 +78,7 @@ const useWave = () => { const dragMoving: React.MouseEventHandler = ({ screenX }) => { if (!xPos) return; - boxRef.current?.scrollTo({ + waveScrubberRef.current?.scrollTo({ left: xPos.scroll + (xPos.initial - screenX) / 0.3, behavior: 'instant', }); @@ -69,24 +89,10 @@ const useWave = () => { }; useDebounceEffect(() => video.seekTo(partStartTime), [partStartTime], 300); - useDebounceEffect( - () => { - if (boxRef.current) { - const unit = - (boxRef.current.scrollWidth - boxRef.current.clientWidth) / (videoLength - interval); - boxRef.current.scrollTo({ - left: partStartTime * unit + 2.5, - behavior: 'instant', - }); - } - video.seekTo(partStartTime); - }, - [interval], - 300 - ); + useDebounceEffect(scrollWaveToStartTime, [interval, activePinIndex], 300); return { - boxRef, + waveScrubberRef, progressWidth, interval, maxPartStartTime, diff --git a/frontend/src/features/songs/components/CollectingPartProvider.tsx b/frontend/src/features/songs/components/CollectingPartProvider.tsx index 0448bd43f..88ff45013 100644 --- a/frontend/src/features/songs/components/CollectingPartProvider.tsx +++ b/frontend/src/features/songs/components/CollectingPartProvider.tsx @@ -1,4 +1,4 @@ -import { createContext, useEffect, useState } from 'react'; +import { createContext, useEffect, useMemo, useState } from 'react'; import { MAX_PART_INTERVAL, MIN_PART_INTERVAL } from '@/features/songs/constants/partInterval'; import useVideoPlayerContext from '@/features/youtube/hooks/useVideoPlayerContext'; import type { PropsWithChildren } from 'react'; @@ -14,12 +14,21 @@ interface CollectingPartContextProps extends CollectingPartProviderProps { interval: number; isPlayingEntire: boolean; setPartStartTime: React.Dispatch>; + pinList: Pin[]; + activePinIndex: number; + setPinList: React.Dispatch>; setInterval: React.Dispatch>; increasePartInterval: () => void; decreasePartInterval: () => void; toggleEntirePlaying: () => void; } +interface Pin { + partStartTime: number; + interval: number; + text: string; +} + export const CollectingPartContext = createContext(null); export const CollectingPartProvider = ({ children, @@ -31,6 +40,12 @@ export const CollectingPartProvider = ({ const [partStartTime, setPartStartTime] = useState(0); const [isPlayingEntire, setIsPlayingEntire] = useState(false); const { playerState, seekTo } = useVideoPlayerContext(); + const [pinList, setPinList] = useState([]); + const activePinIndex = useMemo( + () => + pinList.findIndex((pin) => pin.partStartTime === partStartTime && pin.interval === interval), + [pinList, partStartTime, interval] + ); const toggleEntirePlaying = () => { if (isPlayingEntire) { @@ -72,6 +87,9 @@ export const CollectingPartProvider = ({ songId, songVideoId, isPlayingEntire, + pinList, + activePinIndex, + setPinList, setPartStartTime, setInterval, increasePartInterval, From d119c11e8d832caa9f93768398f9707a392f16ca Mon Sep 17 00:00:00 2001 From: ukkodeveloper Date: Thu, 19 Oct 2023 12:24:45 +0900 Subject: [PATCH 83/95] =?UTF-8?q?design:=20=EC=95=88=EB=82=B4=20=EB=A9=94?= =?UTF-8?q?=EC=84=B8=EC=A7=80=20=EC=82=AD=EC=A0=9C=EB=A1=9C=20=EC=9D=B8?= =?UTF-8?q?=ED=95=9C=20=EC=A0=84=EB=B0=98=EC=A0=81=EC=9D=B8=20=EB=86=92?= =?UTF-8?q?=EC=9D=B4=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/CollectingInformation.tsx | 33 ------------------- .../components/VideoController.tsx | 2 +- .../components/VideoIntervalStepper.tsx | 2 +- frontend/src/pages/PartCollectingPage.tsx | 15 +++++++-- 4 files changed, 14 insertions(+), 38 deletions(-) delete mode 100644 frontend/src/features/killingParts/components/CollectingInformation.tsx diff --git a/frontend/src/features/killingParts/components/CollectingInformation.tsx b/frontend/src/features/killingParts/components/CollectingInformation.tsx deleted file mode 100644 index f7542a368..000000000 --- a/frontend/src/features/killingParts/components/CollectingInformation.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import { styled } from 'styled-components'; - -const CollectingInformation = () => { - return ( - - 나만의 파트 저장하기 - 같은 파트에 대한 중복 등록은 한 번의 등록으로 처리됩니다. - - ); -}; - -export default CollectingInformation; - -const Container = styled.div``; - -const RegisterTitle = styled.h2` - font-size: 18px; - font-weight: 800; - color: ${({ theme: { color } }) => color.white}; - - @media (min-width: ${({ theme }) => theme.breakPoints.md}) { - font-size: 20px; - } -`; - -const Warning = styled.div` - font-size: 14px; - color: ${({ theme: { color } }) => color.subText}; - - @media (min-width: ${({ theme }) => theme.breakPoints.md}) { - font-size: 15px; - } -`; diff --git a/frontend/src/features/killingParts/components/VideoController.tsx b/frontend/src/features/killingParts/components/VideoController.tsx index eba8bd419..72a49743b 100644 --- a/frontend/src/features/killingParts/components/VideoController.tsx +++ b/frontend/src/features/killingParts/components/VideoController.tsx @@ -6,7 +6,7 @@ import Flex from '@/shared/components/Flex/Flex'; const VideoController = () => { return ( - + 길이 선택 diff --git a/frontend/src/features/killingParts/components/VideoIntervalStepper.tsx b/frontend/src/features/killingParts/components/VideoIntervalStepper.tsx index 0e7c721c6..adf9ef299 100644 --- a/frontend/src/features/killingParts/components/VideoIntervalStepper.tsx +++ b/frontend/src/features/killingParts/components/VideoIntervalStepper.tsx @@ -25,7 +25,7 @@ const StepperElementStyle = css` font-weight: 700; text-align: center; - height: 30px; + height: 36px; border: none; border-radius: 10px; `; diff --git a/frontend/src/pages/PartCollectingPage.tsx b/frontend/src/pages/PartCollectingPage.tsx index f79ccbfc5..ae0afae56 100644 --- a/frontend/src/pages/PartCollectingPage.tsx +++ b/frontend/src/pages/PartCollectingPage.tsx @@ -1,6 +1,5 @@ import { useParams } from 'react-router-dom'; import { styled } from 'styled-components'; -import CollectingInformation from '@/features/killingParts/components/CollectingInformation'; import RegisterPart from '@/features/killingParts/components/RegisterPart'; import VideoController from '@/features/killingParts/components/VideoController'; import { CollectingPartProvider } from '@/features/songs/components/CollectingPartProvider'; @@ -28,13 +27,13 @@ const PartCollectingPage = () => { - + - + 나만의 파트 저장하기 @@ -80,3 +79,13 @@ const SongPlayerFlex = styled(Flex)` max-width: calc(100% - 320px); } `; + +const RegisterTitle = styled.h2` + font-size: 20px; + font-weight: 800; + color: ${({ theme: { color } }) => color.white}; + + @media (min-width: ${({ theme }) => theme.breakPoints.md}) { + font-size: 24px; + } +`; From e77d50a047a242f0dc2e11d2b3df5c491a91b2d0 Mon Sep 17 00:00:00 2001 From: ukkodeveloper Date: Thu, 19 Oct 2023 12:30:35 +0900 Subject: [PATCH 84/95] =?UTF-8?q?refactor:=20soundwave=20active=20?= =?UTF-8?q?=EB=86=92=EC=9D=B4=EB=B3=80=EA=B2=BD=20=ED=95=A8=EC=88=98?= =?UTF-8?q?=EC=9D=98=20=EC=9D=B4=EB=A6=84=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/features/killingParts/components/SoundWave.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/src/features/killingParts/components/SoundWave.tsx b/frontend/src/features/killingParts/components/SoundWave.tsx index b18614458..ca549190d 100644 --- a/frontend/src/features/killingParts/components/SoundWave.tsx +++ b/frontend/src/features/killingParts/components/SoundWave.tsx @@ -8,7 +8,7 @@ interface SoundWaveProps { // eslint-disable-next-line react/display-name const SoundWave = forwardRef( ({ length, progressWidth }, boxRef) => { - const refCallback = + const stretchWaveHeight = (activeHeight: string, inactiveHeight: string) => (dom: HTMLDivElement | null) => { if (!dom || !boxRef || typeof boxRef === 'function') return; if (boxRef.current?.scrollLeft) { @@ -28,8 +28,8 @@ const SoundWave = forwardRef( return Array.from({ length }, (_, index) => ( - - + + )); } From 7065662d9e3d6d16c922f76cf106c4769525379f Mon Sep 17 00:00:00 2001 From: ukkodeveloper Date: Thu, 19 Oct 2023 12:42:22 +0900 Subject: [PATCH 85/95] =?UTF-8?q?style:=20styled=20lint=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../killingParts/components/RegisterPart.tsx | 8 +++-- .../killingParts/components/VideoBadges.tsx | 30 +++++++++---------- .../components/VideoController.tsx | 3 +- .../components/VideoIntervalStepper.tsx | 27 ++++++++++------- .../killingParts/components/WaveScrubber.tsx | 9 +++--- frontend/src/pages/PartCollectingPage.tsx | 2 +- frontend/src/shared/styles/GlobalStyles.ts | 2 +- 7 files changed, 44 insertions(+), 37 deletions(-) diff --git a/frontend/src/features/killingParts/components/RegisterPart.tsx b/frontend/src/features/killingParts/components/RegisterPart.tsx index 0d8359b1e..f99a298c6 100644 --- a/frontend/src/features/killingParts/components/RegisterPart.tsx +++ b/frontend/src/features/killingParts/components/RegisterPart.tsx @@ -123,9 +123,11 @@ const ButtonContainer = styled.div` `; const Part = styled.span` - background-color: ${({ theme: { color } }) => color.disabled}; - border-radius: 10px; padding: 6px 11px; - letter-spacing: 1px; + color: white; + letter-spacing: 1px; + + background-color: ${({ theme: { color } }) => color.disabled}; + border-radius: 10px; `; diff --git a/frontend/src/features/killingParts/components/VideoBadges.tsx b/frontend/src/features/killingParts/components/VideoBadges.tsx index 3baba8cce..f12250601 100644 --- a/frontend/src/features/killingParts/components/VideoBadges.tsx +++ b/frontend/src/features/killingParts/components/VideoBadges.tsx @@ -23,8 +23,8 @@ const VideoBadges = () => { playPin, } = usePin(); const video = useVideoPlayerContext(); - const partStartTimeText = toMinSecText(partStartTime); + const partStartTimeText = toMinSecText(partStartTime); const isPaused = video.playerState === YT.PlayerState.PAUSED; const videoPlay = () => { if (isPlayingEntire) { @@ -33,6 +33,7 @@ const VideoBadges = () => { video.seekTo(partStartTime); } }; + const videoPause = () => { video.pause(); }; @@ -80,8 +81,6 @@ const VideoBadges = () => { export default VideoBadges; const PinFlex = styled(Flex)` - //overflow-x: scroll; - //position: relative; width: 100%; `; @@ -95,8 +94,8 @@ const Badge = styled.span<{ $isActive?: boolean }>` align-items: center; justify-content: center; - height: 30px; min-width: 40px; + height: 30px; padding: 0 10px; font-size: 14px; @@ -147,19 +146,19 @@ const slideRestItems = keyframes` `; const PinBadge = styled(Badge)<{ $isActive?: boolean; $isNew?: boolean }>` - background-color: ${({ theme: { color }, $isActive }) => - $isActive ? color.magenta700 : color.disabledBackground}; - z-index: ${({ $isActive }) => ($isActive ? 1 : 0)}; - opacity: ${({ $isActive }) => ($isActive ? 1 : 0.5)} - border: none; width: 50px; - white-space: nowrap; + margin-right: 4px; - color: black; font-size: 12px; - margin-right: 4px; + color: black; + white-space: nowrap; + + opacity: ${({ $isActive }) => ($isActive ? 1 : 0.5)}; + background-color: ${({ theme: { color }, $isActive }) => + $isActive ? color.magenta700 : color.disabledBackground}; + border: none; border-radius: 4px; transition: background-color 0.5s ease-in-out; @@ -174,9 +173,10 @@ const PinBadge = styled(Badge)<{ $isActive?: boolean; $isNew?: boolean }>` `; const DeleteBadge = styled(Badge)` - border-radius: 50%; - height: 30px; min-width: 30px; - padding: 0; + height: 30px; margin-right: 10px; + padding: 0; + + border-radius: 50%; `; diff --git a/frontend/src/features/killingParts/components/VideoController.tsx b/frontend/src/features/killingParts/components/VideoController.tsx index 72a49743b..faa0f01ce 100644 --- a/frontend/src/features/killingParts/components/VideoController.tsx +++ b/frontend/src/features/killingParts/components/VideoController.tsx @@ -24,8 +24,7 @@ const SubHeading = styled.div` @media (min-width: ${({ theme }) => theme.breakPoints.md}) { display: unset; - - font-size: 18px; margin-top: 8px; + font-size: 18px; } `; diff --git a/frontend/src/features/killingParts/components/VideoIntervalStepper.tsx b/frontend/src/features/killingParts/components/VideoIntervalStepper.tsx index adf9ef299..ec2b9c653 100644 --- a/frontend/src/features/killingParts/components/VideoIntervalStepper.tsx +++ b/frontend/src/features/killingParts/components/VideoIntervalStepper.tsx @@ -20,42 +20,47 @@ const StepperElementStyle = css` flex: 1; min-width: 50px; + height: 36px; margin: 0; font-weight: 700; text-align: center; - height: 36px; border: none; border-radius: 10px; `; const ControlButton = styled.button` - ${StepperElementStyle}; - color: ${({ theme: { color } }) => color.white}; - background-color: ${({ theme: { color } }) => color.secondary}; - - font-size: 24px; display: flex; - justify-content: center; align-items: center; + justify-content: center; + + font-size: 24px; + color: ${({ theme: { color } }) => color.white}; + + background-color: ${({ theme: { color } }) => color.secondary}; &:active { background-color: ${({ theme: { color } }) => color.disabled}; transition: box-shadow 0.2s ease; } + + ${StepperElementStyle} `; const CountText = styled.p` - ${StepperElementStyle}; - color: ${({ theme: { color } }) => color.black}; - background-color: ${({ theme: { color } }) => color.white}; display: flex; - justify-content: center; align-items: center; + justify-content: center; + + color: ${({ theme: { color } }) => color.black}; + + background-color: ${({ theme: { color } }) => color.white}; &:active { box-shadow: 0 0 0 1px inset ${({ theme: { color } }) => color.magenta300}; transition: box-shadow 0.1s ease; } + + ${StepperElementStyle} `; diff --git a/frontend/src/features/killingParts/components/WaveScrubber.tsx b/frontend/src/features/killingParts/components/WaveScrubber.tsx index f545fc764..2001c8d30 100644 --- a/frontend/src/features/killingParts/components/WaveScrubber.tsx +++ b/frontend/src/features/killingParts/components/WaveScrubber.tsx @@ -46,9 +46,10 @@ const WaveScrubber = () => { export default WaveScrubber; const WaveWrapper = styled(Flex)<{ $progressWidth: number }>` - z-index: 3; cursor: grab; + z-index: 3; + overflow-x: scroll; width: 100%; @@ -88,8 +89,8 @@ const ProgressFrame = styled.div<{ $progressWidth: number }>` left: 50%; transform: translateX(-50%); - height: 50px; width: ${({ $progressWidth }) => $progressWidth}px; + height: 50px; border: transparent; border-radius: 4px; @@ -113,8 +114,8 @@ const ProgressFill = styled.div<{ $progressWidth: number; $interval: number }>` left: 50%; transform: translateX(-50%); - height: 50px; width: ${({ $progressWidth }) => $progressWidth}px; + height: 50px; background: ${({ theme: { color } }) => `linear-gradient(to left, transparent 50%, ${color.magenta300} 50%)`}; @@ -162,8 +163,8 @@ const WaveFill = styled.div<{ $progressWidth: number; $isRunning: boolean }>` left: 50%; transform: translateX(-50%); - height: 50px; width: ${({ $progressWidth }) => $progressWidth}px; + height: 50px; background: ${({ theme: { color } }) => `linear-gradient(to left, ${color.magenta100}, ${color.magenta400})`}; diff --git a/frontend/src/pages/PartCollectingPage.tsx b/frontend/src/pages/PartCollectingPage.tsx index ae0afae56..d9c3b3514 100644 --- a/frontend/src/pages/PartCollectingPage.tsx +++ b/frontend/src/pages/PartCollectingPage.tsx @@ -55,7 +55,7 @@ const HeaderSpacing = styled(Spacing)` const PageFlex = styled(Flex)` width: 100%; margin: auto; - padding: 10px; + padding: 8px; background-color: ${({ theme: { color } }) => color.black300}; border-radius: 8px; diff --git a/frontend/src/shared/styles/GlobalStyles.ts b/frontend/src/shared/styles/GlobalStyles.ts index 573db0bdf..50122fffc 100644 --- a/frontend/src/shared/styles/GlobalStyles.ts +++ b/frontend/src/shared/styles/GlobalStyles.ts @@ -53,8 +53,8 @@ const GlobalStyles = createGlobalStyle` border: 0; } a { - text-decoration: none; cursor: pointer; + text-decoration: none; } table { border-spacing: 0; From 615cb57bd5aad6aebe932607bf5241e71fb92b61 Mon Sep 17 00:00:00 2001 From: ukkodeveloper Date: Thu, 19 Oct 2023 12:48:36 +0900 Subject: [PATCH 86/95] =?UTF-8?q?style:=20=EC=82=AC=EC=9A=A9=ED=95=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EB=8A=94=20css=20=EC=86=8D=EC=84=B1=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/features/killingParts/components/RegisterPart.tsx | 5 ----- 1 file changed, 5 deletions(-) diff --git a/frontend/src/features/killingParts/components/RegisterPart.tsx b/frontend/src/features/killingParts/components/RegisterPart.tsx index f99a298c6..8beae0948 100644 --- a/frontend/src/features/killingParts/components/RegisterPart.tsx +++ b/frontend/src/features/killingParts/components/RegisterPart.tsx @@ -72,11 +72,6 @@ const RegisterButton = styled.button` border-radius: 6px; @media (min-width: ${({ theme }) => theme.breakPoints.md}) { - position: static; - left: unset; - transform: unset; - - width: 100%; padding: 11px 15px; font-size: 18px; From f21a5170b96aa7b2810336e2d13fb1825a403024 Mon Sep 17 00:00:00 2001 From: ukkodeveloper Date: Thu, 19 Oct 2023 13:07:14 +0900 Subject: [PATCH 87/95] =?UTF-8?q?refactor:=20ref=20=EC=9D=B4=EB=A6=84=20?= =?UTF-8?q?=EB=AA=85=ED=99=95=ED=95=98=EA=B2=8C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/features/killingParts/components/VideoBadges.tsx | 6 +++--- frontend/src/features/killingParts/hooks/usePin.ts | 9 +++++---- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/frontend/src/features/killingParts/components/VideoBadges.tsx b/frontend/src/features/killingParts/components/VideoBadges.tsx index f12250601..cfe8853ad 100644 --- a/frontend/src/features/killingParts/components/VideoBadges.tsx +++ b/frontend/src/features/killingParts/components/VideoBadges.tsx @@ -16,7 +16,7 @@ const VideoBadges = () => { pinList, isPinListEmpty, activePinIndex, - ref, + pinContainerRef, pinAnimationRef, addPin, deletePin, @@ -55,13 +55,13 @@ const VideoBadges = () => { 전체 듣기 - + {!isPinListEmpty && ( 나만의 파트 임시 저장 삭제하기 )} - + {pinList.map((pin, index) => ( { activePinIndex, } = useCollectingPartContext(); - const ref = useRef(null); + const pinContainerRef = useRef(null); const isPinListEmpty = pinList.length === 0; @@ -34,8 +34,8 @@ const usePin = () => { ...prevTimeList.filter((pin) => pin.text !== text), ]); - if (ref.current) { - ref.current.scrollTo({ + if (pinContainerRef.current) { + pinContainerRef.current.scrollTo({ left: 0, behavior: 'smooth', }); @@ -62,7 +62,8 @@ const usePin = () => { isPinListEmpty, activePinIndex, pinAnimationRef, - ref, + pinContainerRef, + partStartTime, addPin, deletePin, playPin, From 572d1b8f07aea7ae027f90f67dabc4a7326cdb70 Mon Sep 17 00:00:00 2001 From: ukkodeveloper Date: Thu, 19 Oct 2023 13:30:42 +0900 Subject: [PATCH 88/95] =?UTF-8?q?fix:=20useDebounceEffect=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=EC=82=AC=ED=95=AD=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/features/search/hooks/useSearchBar.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/features/search/hooks/useSearchBar.ts b/frontend/src/features/search/hooks/useSearchBar.ts index 28062f14e..9f1527cc6 100644 --- a/frontend/src/features/search/hooks/useSearchBar.ts +++ b/frontend/src/features/search/hooks/useSearchBar.ts @@ -16,7 +16,7 @@ const useSearchBar = () => { false ); - useDebounceEffect(fetchSingerSearchPreview, searchQuery, 300); + useDebounceEffect(fetchSingerSearchPreview, [searchQuery], 300); const search: React.FormEventHandler = useCallback( (e) => { From 07bb3a1dbb5d518491ff5ebec3d6f07aebc24bd8 Mon Sep 17 00:00:00 2001 From: ukkodeveloper Date: Thu, 19 Oct 2023 16:27:43 +0900 Subject: [PATCH 89/95] =?UTF-8?q?refactor:=20=EC=A1=B0=EA=B8=B0=20?= =?UTF-8?q?=EB=A6=AC=ED=84=B4=EC=9C=BC=EB=A1=9C=20=EB=A1=9C=EC=A7=81=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 --- .../killingParts/components/SoundWave.tsx | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/frontend/src/features/killingParts/components/SoundWave.tsx b/frontend/src/features/killingParts/components/SoundWave.tsx index ca549190d..784cdd64d 100644 --- a/frontend/src/features/killingParts/components/SoundWave.tsx +++ b/frontend/src/features/killingParts/components/SoundWave.tsx @@ -10,19 +10,18 @@ const SoundWave = forwardRef( ({ length, progressWidth }, boxRef) => { const stretchWaveHeight = (activeHeight: string, inactiveHeight: string) => (dom: HTMLDivElement | null) => { - if (!dom || !boxRef || typeof boxRef === 'function') return; - if (boxRef.current?.scrollLeft) { - const boxPos = - boxRef.current?.scrollLeft + boxRef.current?.clientWidth / 2 - progressWidth / 2; - - const containerRightEdge = boxPos + progressWidth; - const itemRightEdge = dom.offsetLeft; - - if (itemRightEdge >= boxPos && itemRightEdge <= containerRightEdge) { - dom.style.height = activeHeight; - } else { - dom.style.height = inactiveHeight; - } + if (!dom || !boxRef || typeof boxRef === 'function' || !boxRef.current?.scrollLeft) return; + + const boxPos = + boxRef.current?.scrollLeft + boxRef.current?.clientWidth / 2 - progressWidth / 2; + + const containerRightEdge = boxPos + progressWidth; + const itemRightEdge = dom.offsetLeft; + + if (itemRightEdge >= boxPos && itemRightEdge <= containerRightEdge) { + dom.style.height = activeHeight; + } else { + dom.style.height = inactiveHeight; } }; From 6825ec0814aa417fa31f1c0e4f21f2e1b32b0668 Mon Sep 17 00:00:00 2001 From: ukkodeveloper Date: Thu, 19 Oct 2023 16:32:16 +0900 Subject: [PATCH 90/95] =?UTF-8?q?fix:=20=EB=82=B4=20=ED=8C=8C=ED=8A=B8=20?= =?UTF-8?q?=EB=93=B1=EB=A1=9D=20=EC=8B=9C=EC=97=90=20=EB=B9=84=EB=94=94?= =?UTF-8?q?=EC=98=A4=20=EC=A0=95=EC=A7=80=EB=90=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../killingParts/components/RegisterPart.tsx | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/frontend/src/features/killingParts/components/RegisterPart.tsx b/frontend/src/features/killingParts/components/RegisterPart.tsx index 8beae0948..8e72cc36a 100644 --- a/frontend/src/features/killingParts/components/RegisterPart.tsx +++ b/frontend/src/features/killingParts/components/RegisterPart.tsx @@ -21,18 +21,20 @@ const RegisterPart = () => { // 때문에 컴포넌트 단에서 createKillingPart 성공 여부에 따라 등록 완료 만료를 처리를 할 수 없어요! // 현재 비로그인 시에 등록을 누르면 두 개의 모달이 뜹니다.정 const submitKillingPart = async () => { - video.pause(); await createKillingPart(songId, { startSecond: partStartTime, length: interval }); navigate(-1); }; + const openRegisterModal = () => { + video.pause(); + openModal(); + }; + const voteTimeText = toPlayingTimeText(partStartTime, partStartTime + interval); return ( <> - - 등록 - + 등록 {user?.nickname}님의 파트 저장 @@ -45,12 +47,12 @@ const RegisterPart = () => { 나만의 파트로 등록하시겠습니까? - + 취소 - - + + 등록 - + @@ -101,12 +103,12 @@ const Button = styled.button` border-radius: 10px; `; -const Confirm = styled(Button)` +const Cancel = styled(Button)` flex: 1; background-color: ${({ theme: { color } }) => color.secondary}; `; -const Share = styled(Button)` +const Confirm = styled(Button)` flex: 1; background-color: ${({ theme: { color } }) => color.primary}; `; From 63a8bccfd54c9f43ce0df8428fc84abe526e7ac6 Mon Sep 17 00:00:00 2001 From: ukkodeveloper Date: Thu, 19 Oct 2023 16:35:25 +0900 Subject: [PATCH 91/95] =?UTF-8?q?style:=20WaveWrapper=20props=20=EC=88=9C?= =?UTF-8?q?=EC=84=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/features/killingParts/components/WaveScrubber.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/frontend/src/features/killingParts/components/WaveScrubber.tsx b/frontend/src/features/killingParts/components/WaveScrubber.tsx index 2001c8d30..b20aab78e 100644 --- a/frontend/src/features/killingParts/components/WaveScrubber.tsx +++ b/frontend/src/features/killingParts/components/WaveScrubber.tsx @@ -22,13 +22,13 @@ const WaveScrubber = () => { return ( Date: Thu, 19 Oct 2023 16:42:00 +0900 Subject: [PATCH 92/95] =?UTF-8?q?style:=20SoundWave=20ref=20lint=20?= =?UTF-8?q?=EC=97=90=EB=9F=AC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/features/killingParts/components/SoundWave.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/src/features/killingParts/components/SoundWave.tsx b/frontend/src/features/killingParts/components/SoundWave.tsx index 784cdd64d..fcf89fbe7 100644 --- a/frontend/src/features/killingParts/components/SoundWave.tsx +++ b/frontend/src/features/killingParts/components/SoundWave.tsx @@ -5,7 +5,7 @@ interface SoundWaveProps { length: number; progressWidth: number; } -// eslint-disable-next-line react/display-name + const SoundWave = forwardRef( ({ length, progressWidth }, boxRef) => { const stretchWaveHeight = @@ -34,6 +34,7 @@ const SoundWave = forwardRef( } ); +SoundWave.displayName = 'SoundWave'; export default SoundWave; const LongBar = styled.div` From 955b6d34e6846a409e4c3cf79a27b05db44c1758 Mon Sep 17 00:00:00 2001 From: ukkodeveloper Date: Thu, 19 Oct 2023 18:10:04 +0900 Subject: [PATCH 93/95] =?UTF-8?q?feat:=20pin=20=EB=B0=8F=20SoundWave=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9=EC=84=B1=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/features/killingParts/hooks/usePin.ts | 2 ++ .../features/killingParts/hooks/useWave.ts | 23 +++--------- .../components/CollectingPartProvider.tsx | 35 ++++++++++++++++--- 3 files changed, 37 insertions(+), 23 deletions(-) diff --git a/frontend/src/features/killingParts/hooks/usePin.ts b/frontend/src/features/killingParts/hooks/usePin.ts index adb97460a..0fc8336a6 100644 --- a/frontend/src/features/killingParts/hooks/usePin.ts +++ b/frontend/src/features/killingParts/hooks/usePin.ts @@ -11,6 +11,7 @@ const usePin = () => { pinList, setPinList, activePinIndex, + triggerScrollKey, } = useCollectingPartContext(); const pinContainerRef = useRef(null); @@ -55,6 +56,7 @@ const usePin = () => { const playPin = (start: number, interval: number) => () => { setPartStartTime(start); setInterval(interval); + triggerScrollKey(); }; return { diff --git a/frontend/src/features/killingParts/hooks/useWave.ts b/frontend/src/features/killingParts/hooks/useWave.ts index 728a0d223..4166ec8c6 100644 --- a/frontend/src/features/killingParts/hooks/useWave.ts +++ b/frontend/src/features/killingParts/hooks/useWave.ts @@ -1,32 +1,18 @@ -import { useRef, useState } from 'react'; +import { useState } from 'react'; import useCollectingPartContext from '@/features/killingParts/hooks/useCollectingPartContext'; import useVideoPlayerContext from '@/features/youtube/hooks/useVideoPlayerContext'; import useDebounceEffect from '@/shared/hooks/useDebounceEffect'; const useWave = () => { const { + partStartTime, interval, videoLength, - partStartTime, setPartStartTime, isPlayingEntire, - activePinIndex, + waveScrubberRef, } = useCollectingPartContext(); const video = useVideoPlayerContext(); - const waveScrubberRef = useRef(null); - const scrollWaveToStartTime = () => { - if (waveScrubberRef.current) { - const unit = - (waveScrubberRef.current.scrollWidth - waveScrubberRef.current.clientWidth) / - (videoLength - interval); - waveScrubberRef.current.scrollTo({ - left: partStartTime * unit + 2.5, - behavior: 'instant', - }); - } - video.seekTo(partStartTime); - }; - const [xPos, setXPos] = useState<{ initial: number; scroll: number } | null>(null); const maxPartStartTime = videoLength - interval; @@ -88,8 +74,7 @@ const useWave = () => { setXPos(null); }; - useDebounceEffect(() => video.seekTo(partStartTime), [partStartTime], 300); - useDebounceEffect(scrollWaveToStartTime, [interval, activePinIndex], 300); + useDebounceEffect(() => video.seekTo(partStartTime), [interval, partStartTime], 300); return { waveScrubberRef, diff --git a/frontend/src/features/songs/components/CollectingPartProvider.tsx b/frontend/src/features/songs/components/CollectingPartProvider.tsx index 88ff45013..457820ae7 100644 --- a/frontend/src/features/songs/components/CollectingPartProvider.tsx +++ b/frontend/src/features/songs/components/CollectingPartProvider.tsx @@ -1,4 +1,4 @@ -import { createContext, useEffect, useMemo, useState } from 'react'; +import { createContext, useEffect, useMemo, useRef, useState } from 'react'; import { MAX_PART_INTERVAL, MIN_PART_INTERVAL } from '@/features/songs/constants/partInterval'; import useVideoPlayerContext from '@/features/youtube/hooks/useVideoPlayerContext'; import type { PropsWithChildren } from 'react'; @@ -16,6 +16,9 @@ interface CollectingPartContextProps extends CollectingPartProviderProps { setPartStartTime: React.Dispatch>; pinList: Pin[]; activePinIndex: number; + waveScrubberRef: React.RefObject; + forceScrollWave: () => void; + triggerScrollKey: () => void; setPinList: React.Dispatch>; setInterval: React.Dispatch>; increasePartInterval: () => void; @@ -41,12 +44,17 @@ export const CollectingPartProvider = ({ const [isPlayingEntire, setIsPlayingEntire] = useState(false); const { playerState, seekTo } = useVideoPlayerContext(); const [pinList, setPinList] = useState([]); + const [scrollKey, setScrollKey] = useState(0); + const waveScrubberRef = useRef(null); const activePinIndex = useMemo( - () => - pinList.findIndex((pin) => pin.partStartTime === partStartTime && pin.interval === interval), - [pinList, partStartTime, interval] + () => pinList.findIndex((pin) => pin.partStartTime === partStartTime), + [pinList, partStartTime] ); + const triggerScrollKey = () => { + setScrollKey((prevKey) => prevKey + 1); + }; + const toggleEntirePlaying = () => { if (isPlayingEntire) { seekTo(partStartTime); @@ -66,6 +74,18 @@ export const CollectingPartProvider = ({ setInterval(interval - 1); }; + const forceScrollWave = () => { + if (waveScrubberRef.current) { + const unit = + (waveScrubberRef.current.scrollWidth - waveScrubberRef.current.clientWidth) / + (videoLength - interval); + waveScrubberRef.current.scrollTo({ + left: partStartTime * unit + 2.5, + behavior: 'instant', + }); + } + }; + useEffect(() => { if (isPlayingEntire || playerState !== YT.PlayerState.PLAYING) return; @@ -78,6 +98,10 @@ export const CollectingPartProvider = ({ }; }, [playerState, partStartTime, interval, isPlayingEntire]); + useEffect(() => { + forceScrollWave(); + }, [scrollKey]); + return ( Date: Thu, 19 Oct 2023 20:16:49 +0900 Subject: [PATCH 94/95] =?UTF-8?q?feat:=20SoundWave=20=EC=9D=B4=EB=8F=99=20?= =?UTF-8?q?=EC=8B=9C=EC=97=90=20pin=20=ED=81=B4=EB=A6=AD=20=EC=9D=B4?= =?UTF-8?q?=EB=B2=A4=ED=8A=B8=20=EB=B0=A9=EC=A7=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/features/killingParts/hooks/usePin.ts | 13 ++++++++++--- frontend/src/features/killingParts/hooks/useWave.ts | 7 ++++++- .../songs/components/CollectingPartProvider.tsx | 6 +++++- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/frontend/src/features/killingParts/hooks/usePin.ts b/frontend/src/features/killingParts/hooks/usePin.ts index 0fc8336a6..1c85451ed 100644 --- a/frontend/src/features/killingParts/hooks/usePin.ts +++ b/frontend/src/features/killingParts/hooks/usePin.ts @@ -1,4 +1,5 @@ import { useRef } from 'react'; +import { flushSync } from 'react-dom'; import useCollectingPartContext from '@/features/killingParts/hooks/useCollectingPartContext'; import { toMinSecText } from '@/shared/utils/convertTime'; @@ -11,6 +12,7 @@ const usePin = () => { pinList, setPinList, activePinIndex, + scrollingRef, triggerScrollKey, } = useCollectingPartContext(); @@ -54,9 +56,14 @@ const usePin = () => { }; const playPin = (start: number, interval: number) => () => { - setPartStartTime(start); - setInterval(interval); - triggerScrollKey(); + if (!scrollingRef.current) { + flushSync(() => { + setPartStartTime(start); + setInterval(interval); + }); + + triggerScrollKey(); + } }; return { diff --git a/frontend/src/features/killingParts/hooks/useWave.ts b/frontend/src/features/killingParts/hooks/useWave.ts index 4166ec8c6..4665a4c4c 100644 --- a/frontend/src/features/killingParts/hooks/useWave.ts +++ b/frontend/src/features/killingParts/hooks/useWave.ts @@ -11,6 +11,7 @@ const useWave = () => { setPartStartTime, isPlayingEntire, waveScrubberRef, + scrollingRef, } = useCollectingPartContext(); const video = useVideoPlayerContext(); const [xPos, setXPos] = useState<{ initial: number; scroll: number } | null>(null); @@ -32,6 +33,10 @@ const useWave = () => { if (partStartTimeToChange >= 0 && partStartTimeToChange <= maxPartStartTime) { setPartStartTime(partStartTimeToChange); } + + scrollingRef.current = window.setTimeout(() => { + scrollingRef.current = null; + }, 300); }; const wheelStartTime: React.WheelEventHandler = (e) => { @@ -74,7 +79,7 @@ const useWave = () => { setXPos(null); }; - useDebounceEffect(() => video.seekTo(partStartTime), [interval, partStartTime], 300); + useDebounceEffect(() => video.seekTo(partStartTime), [interval, partStartTime], 200); return { waveScrubberRef, diff --git a/frontend/src/features/songs/components/CollectingPartProvider.tsx b/frontend/src/features/songs/components/CollectingPartProvider.tsx index 457820ae7..824d63a00 100644 --- a/frontend/src/features/songs/components/CollectingPartProvider.tsx +++ b/frontend/src/features/songs/components/CollectingPartProvider.tsx @@ -17,6 +17,7 @@ interface CollectingPartContextProps extends CollectingPartProviderProps { pinList: Pin[]; activePinIndex: number; waveScrubberRef: React.RefObject; + scrollingRef: React.MutableRefObject; forceScrollWave: () => void; triggerScrollKey: () => void; setPinList: React.Dispatch>; @@ -44,13 +45,15 @@ export const CollectingPartProvider = ({ const [isPlayingEntire, setIsPlayingEntire] = useState(false); const { playerState, seekTo } = useVideoPlayerContext(); const [pinList, setPinList] = useState([]); - const [scrollKey, setScrollKey] = useState(0); + const [scrollKey, setScrollKey] = useState(0); const waveScrubberRef = useRef(null); const activePinIndex = useMemo( () => pinList.findIndex((pin) => pin.partStartTime === partStartTime), [pinList, partStartTime] ); + const scrollingRef = useRef(null); + const triggerScrollKey = () => { setScrollKey((prevKey) => prevKey + 1); }; @@ -114,6 +117,7 @@ export const CollectingPartProvider = ({ pinList, activePinIndex, waveScrubberRef, + scrollingRef, triggerScrollKey, forceScrollWave, setPinList, From 2634432a806a9d17485d67f0d77e4bce4dc4e742 Mon Sep 17 00:00:00 2001 From: ukkodeveloper Date: Thu, 19 Oct 2023 20:31:41 +0900 Subject: [PATCH 95/95] =?UTF-8?q?feat:=20=EC=8A=A4=ED=81=AC=EB=A1=A4=20?= =?UTF-8?q?=EC=A4=91=20=EC=9C=A0=ED=8A=9C=EB=B8=8C=20=EC=9E=AC=EC=83=9D=20?= =?UTF-8?q?=EC=A0=95=EC=A7=80=20=EB=B0=A9=EC=A7=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../killingParts/components/VideoBadges.tsx | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/frontend/src/features/killingParts/components/VideoBadges.tsx b/frontend/src/features/killingParts/components/VideoBadges.tsx index cfe8853ad..66fc19fe2 100644 --- a/frontend/src/features/killingParts/components/VideoBadges.tsx +++ b/frontend/src/features/killingParts/components/VideoBadges.tsx @@ -11,7 +11,8 @@ import Flex from '@/shared/components/Flex/Flex'; import { toMinSecText } from '@/shared/utils/convertTime'; const VideoBadges = () => { - const { partStartTime, isPlayingEntire, toggleEntirePlaying } = useCollectingPartContext(); + const { partStartTime, isPlayingEntire, scrollingRef, toggleEntirePlaying } = + useCollectingPartContext(); const { pinList, isPinListEmpty, @@ -35,7 +36,9 @@ const VideoBadges = () => { }; const videoPause = () => { - video.pause(); + if (scrollingRef.current === null) { + video.pause(); + } }; return ( @@ -68,7 +71,7 @@ const VideoBadges = () => { as="button" onClick={playPin(pin.partStartTime, pin.interval)} $isActive={index === activePinIndex} - $isNew={index === 0 && index === activePinIndex} + $isNew={index === 0} > {pin.text} @@ -161,14 +164,14 @@ const PinBadge = styled(Badge)<{ $isActive?: boolean; $isNew?: boolean }>` border: none; border-radius: 4px; - transition: background-color 0.5s ease-in-out; + transition: background-color 0.3s ease-in-out; animation: ${({ $isNew }) => $isNew ? css` - ${slideFirstItem} 1s forwards + ${slideFirstItem} 0.6s forwards ` : css` - ${slideRestItems} 0.5s forwards + ${slideRestItems} 0.3s forwards `}; `;