Skip to content

Commit

Permalink
improvement: refactor:
Browse files Browse the repository at this point in the history
  • Loading branch information
zhanglun committed Nov 15, 2024
1 parent f46bfbb commit 7f9fec2
Show file tree
Hide file tree
Showing 11 changed files with 490 additions and 24 deletions.
3 changes: 2 additions & 1 deletion src/components/AddFeed/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { Icon } from "../Icon";
import { ArrowLeft, Loader2, Plus } from "lucide-react";
import { toast } from "sonner";
import { useBearStore } from "@/stores";
import { wraperWithRadix } from "../ArticleView/ContentRender";

export const AddFeedChannel = (props: any) => {
const store = useBearStore((state) => ({
Expand Down Expand Up @@ -162,7 +163,7 @@ export const AddFeedChannel = (props: any) => {
</div>
</div>
<div>
<div className="text-md">{feed.description}</div>
<div className="text-md">{wraperWithRadix(feed.description || "No description")}</div>
</div>
</div>
<div className="flex justify-end gap-3 mt-4">
Expand Down
6 changes: 5 additions & 1 deletion src/components/ArticleView/ContentRender.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Text, Link, Heading, Blockquote } from "@radix-ui/themes";
import { Text, Link, Heading, Blockquote, Quote } from "@radix-ui/themes";
import HTMLReactParser, { domToReact, HTMLReactParserOptions, DOMNode, attributesToProps } from "html-react-parser";

// 自定义转换函数,用于替换标签
Expand Down Expand Up @@ -27,6 +27,10 @@ const options: HTMLReactParserOptions = {
);
}

if (node.name === "quote") {
return <Quote {...attributesToProps(node.attribs)}>{domToReact(node.children as DOMNode[], options)}</Quote>;
}

if (node.name === "h1") {
return (
<Heading {...attributesToProps(node.attribs)} size="8" mb="6">
Expand Down
55 changes: 55 additions & 0 deletions src/components/LPodcast/MiniPlayer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import React from 'react';
import { Box, Flex, IconButton, Text } from '@radix-ui/themes';
import { PlayIcon, PauseIcon, ArrowUpIcon } from '@radix-ui/react-icons';
import { AudioTrack } from './index';

interface MiniPlayerProps {
currentTrack: AudioTrack | null;
isPlaying: boolean;
togglePlay: () => void;
onExpand: () => void;
}

export const MiniPlayer: React.FC<MiniPlayerProps> = ({
currentTrack,
isPlaying,
togglePlay,
onExpand,
}) => {
return (
<Box
className="mini-player"
style={{
position: 'fixed',
bottom: '20px',
right: '20px',
background: 'var(--color-panel-solid)',
padding: '8px',
width: '240px',
borderRadius: '8px',
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)',
zIndex: 1000,
}}
>
<Flex align="center" gap="2">
<IconButton
size="1"
variant="soft"
onClick={togglePlay}
>
{isPlaying ? <PauseIcon /> : <PlayIcon />}
</IconButton>
<Text size="1" style={{ flex: 1, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
{currentTrack?.title || 'No track selected'}
</Text>
<IconButton
size="1"
variant="ghost"
onClick={onExpand}
>
<ArrowUpIcon />
</IconButton>
</Flex>
</Box>
);
};
53 changes: 53 additions & 0 deletions src/components/LPodcast/PlayList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import React from 'react';
import { Box, Flex, Text } from '@radix-ui/themes';
import { AudioTrack } from './index';
import { formatTime } from './utils';

interface PlayListProps {
tracks: AudioTrack[];
currentTrack: AudioTrack | null;
onTrackSelect: (track: AudioTrack) => void;
}

export const PlayList: React.FC<PlayListProps> = ({
tracks,
currentTrack,
onTrackSelect,
}) => {
return (
<Box style={{ maxHeight: '200px', overflowY: 'auto' }}>
{tracks.map((track) => (
<Flex
key={track.id}
align="center"
gap="2"
style={{
padding: '8px',
cursor: 'pointer',
borderRadius: '4px',
background: track.id === currentTrack?.id ? 'var(--color-surface-accent)' : 'transparent',
}}
onClick={() => onTrackSelect(track)}
>
<Text
size="1"
weight={track.id === currentTrack?.id ? 'medium' : 'regular'}
style={{
flex: 1,
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
}}
>
{track.title}
</Text>
{track.duration && (
<Text size="1" color="gray">
{formatTime(track.duration)}
</Text>
)}
</Flex>
))}
</Box>
);
};
150 changes: 150 additions & 0 deletions src/components/LPodcast/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import React, { useEffect, useRef, useState } from 'react';
import { Box, Flex, Slider, IconButton, Text } from '@radix-ui/themes';
import { PlayIcon, PauseIcon, SpeakerLoudIcon, SpeakerOffIcon, ChevronUpIcon, ChevronDownIcon } from '@radix-ui/react-icons';
import { useAudioPlayer } from './useAudioPlayer';
import { MiniPlayer } from './MiniPlayer';
import { PlayList } from './PlayList';
import { formatTime } from './utils';
import clsx from 'clsx';

export interface AudioTrack {
id: string;
title: string;
url: string;
duration?: number;
}

interface PodcastPlayerProps {
tracks: AudioTrack[];
initialTrackId?: string;
mini?: boolean;
}

export const LPodcast: React.FC<PodcastPlayerProps> = ({
tracks,
initialTrackId,
mini = false
}) => {
const [isMini, setIsMini] = useState(mini);
const [showPlaylist, setShowPlaylist] = useState(false);
const {
currentTrack,
isPlaying,
volume,
progress,
duration,
togglePlay,
setVolume,
seek,
playTrack,
setProgress,
} = useAudioPlayer(tracks, initialTrackId);

if (isMini) {
return (
<MiniPlayer
currentTrack={currentTrack}
isPlaying={isPlaying}
togglePlay={togglePlay}
onExpand={() => setIsMini(false)}
/>
);
}

return (
<Box
className="podcast-player"
style={{
position: 'fixed',
bottom: 0,
left: 0,
right: 0,
background: 'var(--color-panel-solid)',
borderTop: '1px solid var(--gray-5)',
zIndex: 1000,
}}
>
{showPlaylist && (
<Box
style={{
position: 'absolute',
bottom: '100%',
left: 0,
right: 0,
background: 'var(--color-panel-solid)',
borderTop: '1px solid var(--gray-5)',
maxHeight: '300px',
overflow: 'auto',
}}
>
<PlayList
tracks={tracks}
currentTrack={currentTrack}
onTrackSelect={playTrack}
/>
</Box>
)}

<Flex direction="column" gap="2" p="3">
<Flex justify="between" align="center">
<Flex align="center" gap="4" style={{ flex: 1 }}>
<Flex align="center" gap="2">
<IconButton
size="2"
variant="soft"
onClick={togglePlay}
>
{isPlaying ? <PauseIcon /> : <PlayIcon />}
</IconButton>
<Text size="2" weight="medium" style={{ maxWidth: '300px', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
{currentTrack?.title || 'No track selected'}
</Text>
</Flex>

<Flex align="center" gap="2" style={{ flex: 1 }}>
<Text size="1" color="gray">
{formatTime(progress)}
</Text>
<Slider
size="1"
variant="soft"
value={[progress]}
max={duration || 100}
style={{ flex: 1 }}
onChange={(value) => seek(value[0])}
/>
<Text size="1" color="gray">
{formatTime(duration || 0)}
</Text>
</Flex>
</Flex>

<Flex align="center" gap="2">
<IconButton
size="1"
variant="ghost"
onClick={() => setShowPlaylist(!showPlaylist)}
>
{showPlaylist ? <ChevronDownIcon /> : <ChevronUpIcon />}
</IconButton>
<IconButton
size="1"
variant="ghost"
onClick={() => setVolume(volume > 0 ? 0 : 1)}
>
{volume > 0 ? <SpeakerLoudIcon /> : <SpeakerOffIcon />}
</IconButton>
<Slider
size="1"
variant="soft"
value={[volume * 100]}
max={100}
style={{ width: '80px' }}
onChange={(value) => setVolume(value[0] / 100)}
/>
</Flex>
</Flex>
</Flex>
</Box>
);
};
Loading

0 comments on commit 7f9fec2

Please sign in to comment.