From 009e0360d89fe6abc3823a21e90ea5e6403d2320 Mon Sep 17 00:00:00 2001 From: jinhojang6 Date: Wed, 2 Aug 2023 00:13:02 +0900 Subject: [PATCH 1/6] feat: enable Youtube embed link --- src/components/Article/Article.Block.tsx | 36 ++++++++++++++++++++++++ src/utils/ui.utils.ts | 1 - 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/src/components/Article/Article.Block.tsx b/src/components/Article/Article.Block.tsx index 7307629..ab72bf7 100644 --- a/src/components/Article/Article.Block.tsx +++ b/src/components/Article/Article.Block.tsx @@ -49,6 +49,24 @@ export const RenderArticleBlock = ({ /> ) } + case 'p': { + const isIframeRegex = /]*>(?:<\/iframe>|[^]*?<\/iframe>)/ + + const isIframe = isIframeRegex.test(block.text) + + return isIframe ? ( + + ) : ( + + ) + } default: return ( iframe, + & > object, + & > embed { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + } +` diff --git a/src/utils/ui.utils.ts b/src/utils/ui.utils.ts index 6525d9d..2b45f1c 100644 --- a/src/utils/ui.utils.ts +++ b/src/utils/ui.utils.ts @@ -83,7 +83,6 @@ export function useIntersectionObserver( headings.forEach((heading) => { if (heading.isIntersecting && heading.target instanceof HTMLElement) { const targetId = heading.target.getAttribute('id') - console.log(targetId) if (targetId) setActiveId(targetId) } }) From da16c1e5821f29e7fbb2cbfdbfd51f8e2c661c28 Mon Sep 17 00:00:00 2001 From: jinhojang6 Date: Wed, 2 Aug 2023 23:18:50 +0900 Subject: [PATCH 2/6] implement react-player --- package.json | 1 + src/components/Article/Article.Block.tsx | 24 ++++++++++---- .../GlobalAudioPlayer/GlobalAudioPlayer.tsx | 32 ++++++++++++++++++ src/components/GlobalAudioPlayer/index.ts | 1 + src/pages/_app.tsx | 2 ++ yarn.lock | 33 ++++++++++++++++++- 6 files changed, 86 insertions(+), 7 deletions(-) create mode 100644 src/components/GlobalAudioPlayer/GlobalAudioPlayer.tsx create mode 100644 src/components/GlobalAudioPlayer/index.ts diff --git a/package.json b/package.json index a470567..b06220b 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "react-blurhash": "^0.3.0", "react-dom": "18.2.0", "react-imgix": "^9.7.0", + "react-player": "^2.12.0", "typescript": "5.0.4" }, "devDependencies": { diff --git a/src/components/Article/Article.Block.tsx b/src/components/Article/Article.Block.tsx index ab72bf7..2df4646 100644 --- a/src/components/Article/Article.Block.tsx +++ b/src/components/Article/Article.Block.tsx @@ -1,8 +1,4 @@ -import { - TextBlockEnhanced, - UnbodyImageBlock, - UnbodyTextBlock, -} from '@/lib/unbody/unbody.types' +import { UnbodyImageBlock, UnbodyTextBlock } from '@/lib/unbody/unbody.types' import { UnbodyGraphQl } from '@/lib/unbody/unbody-content.types' import { ArticleImageBlockWrapper } from './Article.ImageBlockWrapper' import { PostImageRatio } from '../Post/Post' @@ -16,6 +12,8 @@ import { import { HeadingElementsRef } from '@/utils/ui.utils' import UnbodyDocumentTypeNames = UnbodyGraphQl.UnbodyDocumentTypeNames import { ArticleHeading } from '@/components/Article/Article.Heading' +import ReactPlayer from 'react-player' +import { GlobalAudioPlayer } from '../GlobalAudioPlayer' export const RenderArticleBlock = ({ block, @@ -51,11 +49,25 @@ export const RenderArticleBlock = ({ } case 'p': { const isIframeRegex = /]*>(?:<\/iframe>|[^]*?<\/iframe>)/ - const isIframe = isIframeRegex.test(block.text) + const isYoutubeRegex = + /https?:\/\/(?:www\.)?youtu\.be\/([a-zA-Z0-9_-]{11})/ + const isYoutube = isYoutubeRegex.test(block.text) + + const isSimplecastRegex = /https?:\/\/.*cdn\.simplecast\.com\/.*/ + const isSimplecast = isSimplecastRegex.test(block.text) + return isIframe ? ( + ) : isYoutube ? ( + + ) : isSimplecast ? ( + ) : ( + + + ) +} + +const Container = styled.div` + width: 100vw; + height: 80px; + background: rgb(var(--lsd-surface-primary)); + position: fixed; + display: flex; + align-items: center; + justify-content: center; + bottom: 0; + left: 0; + border-top: 1px solid rgb(var(--lsd-border-primary)); +` diff --git a/src/components/GlobalAudioPlayer/index.ts b/src/components/GlobalAudioPlayer/index.ts new file mode 100644 index 0000000..97edc94 --- /dev/null +++ b/src/components/GlobalAudioPlayer/index.ts @@ -0,0 +1 @@ +export { default as GlobalAudioPlayer } from './GlobalAudioPlayer' diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index 4285c00..90339ef 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -8,6 +8,7 @@ import type { AppProps } from 'next/app' import Head from 'next/head' import { ReactNode } from 'react' import { LSDThemeProvider } from '../containers/LSDThemeProvider' +import { GlobalAudioPlayer } from '@/components/GlobalAudioPlayer' type NextLayoutComponentType

= NextComponentType< NextPageContext, @@ -94,6 +95,7 @@ export default function App({ Component, pageProps }: AppLayoutProps) { {getLayout()} + ) } diff --git a/yarn.lock b/yarn.lock index 5e039f6..03ae763 100644 --- a/yarn.lock +++ b/yarn.lock @@ -977,6 +977,11 @@ deep-is@^0.1.3: resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== +deepmerge@^4.0.0: + version "4.3.1" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" + integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== + define-lazy-prop@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f" @@ -2111,6 +2116,11 @@ listr2@^5.0.7: through "^2.3.8" wrap-ansi "^7.0.0" +load-script@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/load-script/-/load-script-1.0.0.tgz#0491939e0bee5643ee494a7e3da3d2bac70c6ca4" + integrity sha512-kPEjMFtZvwL9TaZo0uZ2ml+Ye9HUMmPwbYRJ324qF9tqMejwykJ5ggTyvzmrbBeapCAbk98BSbTeovHEEP1uCA== + locate-path@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" @@ -2166,6 +2176,11 @@ mdn-data@2.0.14: resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50" integrity sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow== +memoize-one@^5.1.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.2.1.tgz#8337aa3c4335581839ec01c3d594090cebe8f00e" + integrity sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q== + merge-stream@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" @@ -2537,7 +2552,7 @@ prettier@^2.8.7: resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.7.tgz#bb79fc8729308549d28fe3a98fce73d2c0656450" integrity sha512-yPngTo3aXUUmyuTjeTUT75txrf+aMh9FiD7q9ZE/i6r0bPb22g4FsE6Y338PQX1bmfy08i9QQCB7/rcUAVntfw== -prop-types@^15.6.2, prop-types@^15.8.1: +prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1: version "15.8.1" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== @@ -2574,6 +2589,11 @@ react-dom@18.2.0: loose-envify "^1.1.0" scheduler "^0.23.0" +react-fast-compare@^3.0.1: + version "3.2.2" + resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.2.tgz#929a97a532304ce9fee4bcae44234f1ce2c21d49" + integrity sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ== + react-imgix@^9.7.0: version "9.7.0" resolved "https://registry.yarnpkg.com/react-imgix/-/react-imgix-9.7.0.tgz#944f63693daf6524d07898aaf7d1cbbe59e5edca" @@ -2600,6 +2620,17 @@ react-measure@^2.3.0: prop-types "^15.6.2" resize-observer-polyfill "^1.5.0" +react-player@^2.12.0: + version "2.12.0" + resolved "https://registry.yarnpkg.com/react-player/-/react-player-2.12.0.tgz#2fc05dbfec234c829292fbca563b544064bd14f0" + integrity sha512-rymLRz/2GJJD+Wc01S7S+i9pGMFYnNmQibR2gVE3KmHJCBNN8BhPAlOPTGZtn1uKpJ6p4RPLlzPQ1OLreXd8gw== + dependencies: + deepmerge "^4.0.0" + load-script "^1.0.0" + memoize-one "^5.1.1" + prop-types "^15.7.2" + react-fast-compare "^3.0.1" + react-universal-interface@^0.6.2: version "0.6.2" resolved "https://registry.yarnpkg.com/react-universal-interface/-/react-universal-interface-0.6.2.tgz#5e8d438a01729a4dbbcbeeceb0b86be146fe2b3b" From 2bb67cd3375f3cb889e4013b19132c51390344ab Mon Sep 17 00:00:00 2001 From: jinhojang6 Date: Mon, 7 Aug 2023 21:45:17 +0900 Subject: [PATCH 3/6] chore: support dynamic link --- src/components/Article/Article.Block.tsx | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/components/Article/Article.Block.tsx b/src/components/Article/Article.Block.tsx index 2df4646..209d42d 100644 --- a/src/components/Article/Article.Block.tsx +++ b/src/components/Article/Article.Block.tsx @@ -54,20 +54,18 @@ export const RenderArticleBlock = ({ const isYoutubeRegex = /https?:\/\/(?:www\.)?youtu\.be\/([a-zA-Z0-9_-]{11})/ const isYoutube = isYoutubeRegex.test(block.text) + const youtubeLink = block.text.match(isYoutubeRegex) ?? [] const isSimplecastRegex = /https?:\/\/.*cdn\.simplecast\.com\/.*/ const isSimplecast = isSimplecastRegex.test(block.text) + const simplecastLink = block.text.match(isSimplecastRegex) ?? [] return isIframe ? ( ) : isYoutube ? ( - + ) : isSimplecast ? ( - + ) : ( Date: Tue, 8 Aug 2023 23:14:28 +0900 Subject: [PATCH 4/6] feat: implement audio player --- .../GlobalAudioPlayer/GlobalAudioPlayer.tsx | 219 +++++++++++++++++- src/components/Icons/PauseIcon/PauseIcon.tsx | 22 ++ src/components/Icons/PauseIcon/index.ts | 1 + src/components/Icons/PlayIcon/PlayIcon.tsx | 20 ++ src/components/Icons/PlayIcon/index.ts | 1 + .../Icons/VolumeIcon/VolumeIcon.tsx | 27 +++ src/components/Icons/VolumeIcon/index.ts | 1 + src/styles/globals.css | 1 + 8 files changed, 285 insertions(+), 7 deletions(-) create mode 100644 src/components/Icons/PauseIcon/PauseIcon.tsx create mode 100644 src/components/Icons/PauseIcon/index.ts create mode 100644 src/components/Icons/PlayIcon/PlayIcon.tsx create mode 100644 src/components/Icons/PlayIcon/index.ts create mode 100644 src/components/Icons/VolumeIcon/VolumeIcon.tsx create mode 100644 src/components/Icons/VolumeIcon/index.ts diff --git a/src/components/GlobalAudioPlayer/GlobalAudioPlayer.tsx b/src/components/GlobalAudioPlayer/GlobalAudioPlayer.tsx index f49005a..7e1a299 100644 --- a/src/components/GlobalAudioPlayer/GlobalAudioPlayer.tsx +++ b/src/components/GlobalAudioPlayer/GlobalAudioPlayer.tsx @@ -1,18 +1,183 @@ import ReactPlayer from 'react-player' import styled from '@emotion/styled' +import { useRef, useState } from 'react' +import { PlayIcon } from '../Icons/PlayIcon' +import { PauseIcon } from '../Icons/PauseIcon' +import { VolumeIcon } from '../Icons/VolumeIcon' + +type StateProps = { + url: string | null + pip: boolean + playing: boolean + controls: boolean + light: boolean + volume: number + muted: boolean + played: number + loaded: number + duration: number + playbackRate: number + loop: boolean + seeking: boolean +} + +const TEMP_URL = + 'https://cdn.simplecast.com/audio/b54c0885-7c72-415d-b032-7d294b78d785/episodes/30d4e2f5-4434-419c-8fc1-a76e4b367e20/audio/3c8eb229-3f34-45a4-84f1-ce1d6bd65922/default_tc.mp3' export default function GlobalAudioPlayer() { - const audioSrc = - 'https://pdcn.co/e/cdn.simplecast.com/audio/b623b331-ffef-40c4-918d-b35a07ee8729/episodes/72d2eac9-2d2a-4a8c-943d-c2ffa1e071c0/audio/98a3ad48-86ec-45e3-be20-bdb0beea23c1/default_tc.mp3?aid=embed' + const ref = useRef(null) + const [state, setState] = useState({ + url: TEMP_URL, + pip: false, + playing: false, + controls: false, + light: false, + volume: 0.8, + muted: false, + played: 0, + loaded: 0, + duration: 0, + playbackRate: 1.0, + loop: false, + seeking: false, + }) + const [showVolume, setShowVolume] = useState(false) + + // const handleLoad = (url: string) => { + // setState((prev) => ({ ...prev, url, played: 0, loaded: 0, pip: false })) + // } + + const handlePlay = () => { + setState((prev) => ({ ...prev, playing: true })) + } + + const handlePlayPause = () => { + setState((prev) => ({ ...prev, playing: !state.playing })) + } + + // const handleStop = () => { + // setState((prev) => ({ ...prev, url: null, playing: false })) + // } + + const handleEnded = () => { + setState((prev) => ({ ...prev, playing: prev.loop })) + } + + const handleVolumeChange = (e: React.ChangeEvent) => { + setState((prev) => ({ ...prev, volume: parseFloat(e.target.value) })) + } + + // const handleToggleMuted = (e: React.ChangeEvent) => { + // setState((prev) => ({ ...prev, muted: !state.muted })) + // } + + // const handleSetPlaybackRate = (e: React.ChangeEvent) => { + // setState((prev) => ({ ...prev, playbackRate: parseFloat(e.target.value) })) + // } + + const handlePause = () => { + setState((prev) => ({ ...prev, playing: false })) + } + + const handleSeekMouseDown = ( + e: React.MouseEvent, + ) => { + setState((prev) => ({ ...prev, seeking: true })) + } + + const handleSeekChange = (e: React.ChangeEvent) => { + setState((prev) => ({ ...prev, played: parseFloat(e.target.value) })) + } + + const handleSeekMouseUp = ( + e: React.MouseEvent, + ) => { + setState((prev) => ({ ...prev, seeking: false })) + const target = e.target as HTMLInputElement + ref.current?.seekTo(parseFloat(target?.value)) + } + + const handleDuration = (duration: number) => { + setState((prev) => ({ ...prev, duration })) + } + + const handleOnPlaybackRateChange = ( + e: React.ChangeEvent, + ) => { + setState((prev) => ({ ...prev, playbackRate: parseFloat(e.target.value) })) + } + + // const handleProgress = (newState: StateProps) => { + // if (!state.seeking) { + // setState((prev) => ({ ...prev, ...newState })) + // } + // } return ( + + + + {state.playing ? : } + + setShowVolume((prev) => !prev)}> + {showVolume && ( + + + + )} + + + + + {/* */} + + + + + + console.log('onReady')} + onStart={() => console.log('onStart')} + onPlay={handlePlay} + onPause={handlePause} + onBuffer={() => console.log('onBuffer')} + onPlaybackRateChange={handleOnPlaybackRateChange} + onSeek={(e) => console.log('onSeek', e)} + onEnded={handleEnded} + onError={(e) => console.log('onError', e)} + onDuration={handleDuration} + // onProgress={handleProgress} /> ) @@ -21,6 +186,7 @@ export default function GlobalAudioPlayer() { const Container = styled.div` width: 100vw; height: 80px; + padding: 22px 16px; background: rgb(var(--lsd-surface-primary)); position: fixed; display: flex; @@ -30,3 +196,42 @@ const Container = styled.div` left: 0; border-top: 1px solid rgb(var(--lsd-border-primary)); ` + +const Buttons = styled.div` + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; +` + +const VolumeContainer = styled.div` + display: flex; + justify-content: center; + position: relative; + align-items: center; +` + +const Seek = styled.div` + display: flex; + width: 100%; +` + +const VolumeGauge = styled.div` + position: absolute; + top: -30px; +` + +const AudioPlayer = styled.div` + display: flex; + flex-direction: column; + gap: 16px; + width: 100%; +` + +const PlayPause = styled.button` + display: flex; + align-items: center; + justify-content: center; + border: none; + background: none; +` diff --git a/src/components/Icons/PauseIcon/PauseIcon.tsx b/src/components/Icons/PauseIcon/PauseIcon.tsx new file mode 100644 index 0000000..fd853c9 --- /dev/null +++ b/src/components/Icons/PauseIcon/PauseIcon.tsx @@ -0,0 +1,22 @@ +import { LsdIcon } from '@acid-info/lsd-react' + +export const PauseIcon = LsdIcon( + (props) => ( + + + + + + + ), + { filled: true }, +) diff --git a/src/components/Icons/PauseIcon/index.ts b/src/components/Icons/PauseIcon/index.ts new file mode 100644 index 0000000..6802941 --- /dev/null +++ b/src/components/Icons/PauseIcon/index.ts @@ -0,0 +1 @@ +export * from './PauseIcon' diff --git a/src/components/Icons/PlayIcon/PlayIcon.tsx b/src/components/Icons/PlayIcon/PlayIcon.tsx new file mode 100644 index 0000000..5ba8b5c --- /dev/null +++ b/src/components/Icons/PlayIcon/PlayIcon.tsx @@ -0,0 +1,20 @@ +import { LsdIcon } from '@acid-info/lsd-react' + +export const PlayIcon = LsdIcon( + (props) => ( + + + + ), + { filled: true }, +) diff --git a/src/components/Icons/PlayIcon/index.ts b/src/components/Icons/PlayIcon/index.ts new file mode 100644 index 0000000..63c61c0 --- /dev/null +++ b/src/components/Icons/PlayIcon/index.ts @@ -0,0 +1 @@ +export * from './PlayIcon' diff --git a/src/components/Icons/VolumeIcon/VolumeIcon.tsx b/src/components/Icons/VolumeIcon/VolumeIcon.tsx new file mode 100644 index 0000000..ff375a5 --- /dev/null +++ b/src/components/Icons/VolumeIcon/VolumeIcon.tsx @@ -0,0 +1,27 @@ +import { LsdIcon } from '@acid-info/lsd-react' + +export const VolumeIcon = LsdIcon( + (props) => ( + + + + + + + + + + + ), + { filled: true }, +) diff --git a/src/components/Icons/VolumeIcon/index.ts b/src/components/Icons/VolumeIcon/index.ts new file mode 100644 index 0000000..78b4732 --- /dev/null +++ b/src/components/Icons/VolumeIcon/index.ts @@ -0,0 +1 @@ +export * from './VolumeIcon' diff --git a/src/styles/globals.css b/src/styles/globals.css index e69de29..8b13789 100644 --- a/src/styles/globals.css +++ b/src/styles/globals.css @@ -0,0 +1 @@ + From c29087be8f56a55dcdf427222554a6144e359725 Mon Sep 17 00:00:00 2001 From: jinhojang6 Date: Wed, 9 Aug 2023 00:43:45 +0900 Subject: [PATCH 5/6] style: implement basic media player styles --- src/components/Article/Article.Block.tsx | 2 +- .../GlobalAudioPlayer.module.css | 20 ++++++ .../GlobalAudioPlayer/GlobalAudioPlayer.tsx | 62 +++++++++++++++---- src/utils/string.utils.ts | 12 ++++ 4 files changed, 82 insertions(+), 14 deletions(-) create mode 100644 src/components/GlobalAudioPlayer/GlobalAudioPlayer.module.css diff --git a/src/components/Article/Article.Block.tsx b/src/components/Article/Article.Block.tsx index 209d42d..453fd98 100644 --- a/src/components/Article/Article.Block.tsx +++ b/src/components/Article/Article.Block.tsx @@ -13,7 +13,6 @@ import { HeadingElementsRef } from '@/utils/ui.utils' import UnbodyDocumentTypeNames = UnbodyGraphQl.UnbodyDocumentTypeNames import { ArticleHeading } from '@/components/Article/Article.Heading' import ReactPlayer from 'react-player' -import { GlobalAudioPlayer } from '../GlobalAudioPlayer' export const RenderArticleBlock = ({ block, @@ -53,6 +52,7 @@ export const RenderArticleBlock = ({ const isYoutubeRegex = /https?:\/\/(?:www\.)?youtu\.be\/([a-zA-Z0-9_-]{11})/ + const isYoutube = isYoutubeRegex.test(block.text) const youtubeLink = block.text.match(isYoutubeRegex) ?? [] diff --git a/src/components/GlobalAudioPlayer/GlobalAudioPlayer.module.css b/src/components/GlobalAudioPlayer/GlobalAudioPlayer.module.css new file mode 100644 index 0000000..fb6fdbb --- /dev/null +++ b/src/components/GlobalAudioPlayer/GlobalAudioPlayer.module.css @@ -0,0 +1,20 @@ +.audioPlayer > input[type='range'] { + width: 100%; + accent-color: black; +} + +.audioPlayer > input[type='range']:focus { + outline: none; +} + +.audioPlayer > input[type='range']::-webkit-slider-runnable-track { + width: 100%; + height: 8px; + cursor: grabbing; +} + +.audioPlayer > input[type='range']::-webkit-slider-thumb { + -webkit-appearance: none; + appearance: none; + margin-top: -4px; +} \ No newline at end of file diff --git a/src/components/GlobalAudioPlayer/GlobalAudioPlayer.tsx b/src/components/GlobalAudioPlayer/GlobalAudioPlayer.tsx index 7e1a299..b878777 100644 --- a/src/components/GlobalAudioPlayer/GlobalAudioPlayer.tsx +++ b/src/components/GlobalAudioPlayer/GlobalAudioPlayer.tsx @@ -4,11 +4,15 @@ import { useRef, useState } from 'react' import { PlayIcon } from '../Icons/PlayIcon' import { PauseIcon } from '../Icons/PauseIcon' import { VolumeIcon } from '../Icons/VolumeIcon' +import styles from './GlobalAudioPlayer.module.css' +import { convertSecToMinAndSec } from '@/utils/string.utils' +import { Typography } from '@acid-info/lsd-react' type StateProps = { url: string | null pip: boolean playing: boolean + playedSeconds: number controls: boolean light: boolean volume: number @@ -30,6 +34,7 @@ export default function GlobalAudioPlayer() { url: TEMP_URL, pip: false, playing: false, + playedSeconds: 0, controls: false, light: false, volume: 0.8, @@ -107,19 +112,29 @@ export default function GlobalAudioPlayer() { setState((prev) => ({ ...prev, playbackRate: parseFloat(e.target.value) })) } - // const handleProgress = (newState: StateProps) => { - // if (!state.seeking) { - // setState((prev) => ({ ...prev, ...newState })) - // } - // } + const handleProgress = (newState: { playedSeconds: number }) => { + setState((prev) => ({ ...prev, playedSeconds: newState.playedSeconds })) + } return ( - - {state.playing ? : } - + + + {state.playing ? : } + + + + / + + + + setShowVolume((prev) => !prev)}> {showVolume && ( @@ -139,9 +154,8 @@ export default function GlobalAudioPlayer() { {/* */} - + console.log('onError', e)} onDuration={handleDuration} - // onProgress={handleProgress} + onProgress={handleProgress} /> + ) } @@ -195,6 +211,7 @@ const Container = styled.div` bottom: 0; left: 0; border-top: 1px solid rgb(var(--lsd-border-primary)); + box-sizing: border-box; ` const Buttons = styled.div` @@ -224,8 +241,12 @@ const VolumeGauge = styled.div` const AudioPlayer = styled.div` display: flex; flex-direction: column; - gap: 16px; - width: 100%; + gap: 8px; + width: 60%; +` + +const RightMenu = styled.div` + width: 40%; ` const PlayPause = styled.button` @@ -234,4 +255,19 @@ const PlayPause = styled.button` justify-content: center; border: none; background: none; + margin-right: 8px; +` + +const Row = styled.div` + display: flex; + align-items: center; + white-space: pre-wrap; +` + +const TimeContainer = styled(Row)` + gap: 8px; +` + +const Time = styled(Typography)` + width: 32px; ` diff --git a/src/utils/string.utils.ts b/src/utils/string.utils.ts index 44e963b..f44a8a3 100644 --- a/src/utils/string.utils.ts +++ b/src/utils/string.utils.ts @@ -41,3 +41,15 @@ export const calcReadingTime = (text: string): number => { const numberOfWords = text.split(/\s/g).length return Math.ceil(numberOfWords / wordsPerMinute) } + +export function convertSecToMinAndSec(totalSeconds: number) { + // Convert seconds to minutes and seconds + const minutes = Math.floor(totalSeconds / 60) + const seconds = Math.floor(totalSeconds % 60) + + // Ensure two digit format + const formattedMinutes = String(minutes).padStart(2, '0') + const formattedSeconds = String(seconds).padStart(2, '0') + + return `${formattedMinutes}:${formattedSeconds}` +} From 68b197fd1b13a9f99a74d07a5ac425171ce50367 Mon Sep 17 00:00:00 2001 From: jinhojang6 Date: Wed, 9 Aug 2023 00:58:03 +0900 Subject: [PATCH 6/6] refactor: update Youtube and Simplecast regex --- src/components/Article/Article.Block.tsx | 26 +++++++++++++++++++++--- src/utils/data.utils.ts | 24 +++++++++++++++++++++- src/utils/string.utils.ts | 12 +++++++++++ 3 files changed, 58 insertions(+), 4 deletions(-) diff --git a/src/components/Article/Article.Block.tsx b/src/components/Article/Article.Block.tsx index 453fd98..433257c 100644 --- a/src/components/Article/Article.Block.tsx +++ b/src/components/Article/Article.Block.tsx @@ -13,6 +13,8 @@ import { HeadingElementsRef } from '@/utils/ui.utils' import UnbodyDocumentTypeNames = UnbodyGraphQl.UnbodyDocumentTypeNames import { ArticleHeading } from '@/components/Article/Article.Heading' import ReactPlayer from 'react-player' +import { getAudioSourceFromSimplecastPlayer } from '@/utils/data.utils' +import { convertToIframe, extractUUIDFromEpisode } from '@/utils/string.utils' export const RenderArticleBlock = ({ block, @@ -51,21 +53,39 @@ export const RenderArticleBlock = ({ const isIframe = isIframeRegex.test(block.text) const isYoutubeRegex = - /https?:\/\/(?:www\.)?youtu\.be\/([a-zA-Z0-9_-]{11})/ + /^(https?\:\/\/)?((www\.)?youtube\.com|youtu\.?be)\/.+$/ const isYoutube = isYoutubeRegex.test(block.text) const youtubeLink = block.text.match(isYoutubeRegex) ?? [] - const isSimplecastRegex = /https?:\/\/.*cdn\.simplecast\.com\/.*/ + const isSimplecastRegex = + /^https?:\/\/([a-zA-Z0-9-]+\.)*simplecast\.com\/[^?\s]+(\?[\s\S]*)?$/ + const isSimplecast = isSimplecastRegex.test(block.text) const simplecastLink = block.text.match(isSimplecastRegex) ?? [] + // const episodeId = extractUUIDFromEpisode(simplecastLink[0] ?? '') + + // let audioSrc = '' + + // if (isSimplecast) { + // fetch( + // `https://api.simplecast.com/episodes/audio/bc313c16-82e9-439a-8e0c-af59833d22d7`, + // ) + // .then((response) => response.json()) + // .then((data) => console.log(data)) + // } + return isIframe ? ( ) : isYoutube ? ( ) : isSimplecast ? ( - + ) : ( { return array } + +export const getAudioSourceFromSimplecastPlayer = async (url: string) => { + const myHeaders = new Headers() + myHeaders.append( + 'Authorization', + 'Bearer eyJhcGlfa2V5IjoiMzg3OTdhY2Y5N2NmZjgzZjQxNGI5ODNiN2E2MjY3NmQifQ==', + ) + + const requestOptions = { + method: 'GET', + headers: myHeaders, + } + + const result = await fetch( + `https://api.simplecast.com/episodes/${url}`, + requestOptions, + ) + + const data = await result.json() + console.log(data) + return data +} diff --git a/src/utils/string.utils.ts b/src/utils/string.utils.ts index f44a8a3..fcd3f28 100644 --- a/src/utils/string.utils.ts +++ b/src/utils/string.utils.ts @@ -53,3 +53,15 @@ export function convertSecToMinAndSec(totalSeconds: number) { return `${formattedMinutes}:${formattedSeconds}` } + +export function extractUUIDFromEpisode(url: string) { + const regex = /([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/ + const match = url.match(regex) + return match ? match[1] : null +} + +export function convertToIframe(url: string) { + if (!url) return '' + + return `` +}