[#17] Now have a download button to download a podcast episode.
This commit is contained in:
parent
34539a16df
commit
5dfc18b22e
1
public/icons/download.svg
Normal file
1
public/icons/download.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?><svg width="24px" height="24px" stroke-width="1.5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" color="#000000"><path d="M6 20L18 20" stroke="#000000" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><path d="M12 4V16M12 16L15.5 12.5M12 16L8.5 12.5" stroke="#000000" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path></svg>
|
||||||
|
After Width: | Height: | Size: 430 B |
@ -1,8 +1,9 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import { PodcastDto } from '../../../common/dtos/podcastDto';
|
import { PodcastDto } from '@/common/dtos/podcastDto';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
import DownloadButton from '@/app/common/components/buttons/DownloadButton';
|
||||||
|
|
||||||
export default function PodcastCard({
|
export default function PodcastCard({
|
||||||
imageUrl = "https://placehold.co/400",
|
imageUrl = "https://placehold.co/400",
|
||||||
@ -40,9 +41,22 @@ export default function PodcastCard({
|
|||||||
<p>By: {author}</p>
|
<p>By: {author}</p>
|
||||||
<p>Uploaded: {uploadDate}</p>
|
<p>Uploaded: {uploadDate}</p>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="flex">
|
||||||
<a href={url} target="_blank" rel="noopener noreferrer" className="mt-4 text-blue-500 hover:underline text-sm">
|
<a href={url} target="_blank" rel="noopener noreferrer" className="mt-4 text-blue-500 hover:underline text-sm">
|
||||||
Listen Now
|
Listen Now
|
||||||
</a>
|
</a>
|
||||||
|
{url &&
|
||||||
|
<DownloadButton
|
||||||
|
href={url}
|
||||||
|
iconSrc={`${process.env.NEXT_PUBLIC_API_BASE_URL}/icons/download.svg`}
|
||||||
|
iconAlt="Download"
|
||||||
|
text="Download"
|
||||||
|
download={`${title}-${url.split('/').pop()}`}
|
||||||
|
className="mt-4 ml-2"
|
||||||
|
debounceMs={2000}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -54,7 +54,6 @@ export default function StreamPublishForm({ stream }: { stream?: string | string
|
|||||||
};
|
};
|
||||||
|
|
||||||
xhr.send(formData);
|
xhr.send(formData);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
50
src/app/common/components/buttons/DownloadButton.tsx
Normal file
50
src/app/common/components/buttons/DownloadButton.tsx
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import IconExpandTextButton from "./IconExpandTextButton";
|
||||||
|
|
||||||
|
type DownloadButtonProps = {
|
||||||
|
href: string;
|
||||||
|
iconSrc: string;
|
||||||
|
iconAlt?: string;
|
||||||
|
text: string;
|
||||||
|
download?: boolean | string;
|
||||||
|
className?: string;
|
||||||
|
debounceMs?: number;
|
||||||
|
disabled?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function DownloadButton({
|
||||||
|
href,
|
||||||
|
iconSrc,
|
||||||
|
iconAlt = "icon",
|
||||||
|
text,
|
||||||
|
download = false,
|
||||||
|
className = "",
|
||||||
|
debounceMs,
|
||||||
|
disabled = false
|
||||||
|
}: DownloadButtonProps) {
|
||||||
|
const onClick = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (disabled) return;
|
||||||
|
|
||||||
|
const link = document.createElement("a");
|
||||||
|
link.href = href;
|
||||||
|
if (download) {
|
||||||
|
link.setAttribute('download', typeof download === 'string' ? download : '');
|
||||||
|
}
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
document.body.removeChild(link);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<IconExpandTextButton
|
||||||
|
iconSrc={iconSrc}
|
||||||
|
iconAlt={iconAlt}
|
||||||
|
text={text}
|
||||||
|
className={className}
|
||||||
|
debounceMs={debounceMs}
|
||||||
|
disabled={disabled}
|
||||||
|
onClick={onClick}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
53
src/app/common/components/buttons/IconExpandTextButton.tsx
Normal file
53
src/app/common/components/buttons/IconExpandTextButton.tsx
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useState } from "react";
|
||||||
|
import Image from "next/image";
|
||||||
|
|
||||||
|
type IconExpandTextButtonProps = {
|
||||||
|
iconSrc: string;
|
||||||
|
iconAlt?: string;
|
||||||
|
text: string;
|
||||||
|
download?: boolean | string;
|
||||||
|
className?: string;
|
||||||
|
debounceMs?: number;
|
||||||
|
onClick?: (e: React.MouseEvent<HTMLAnchorElement>) => void;
|
||||||
|
disabled?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function IconExpandTextButton({
|
||||||
|
iconSrc,
|
||||||
|
iconAlt = "icon",
|
||||||
|
text,
|
||||||
|
download = false,
|
||||||
|
className = "",
|
||||||
|
debounceMs,
|
||||||
|
onClick,
|
||||||
|
disabled = false
|
||||||
|
}: IconExpandTextButtonProps) {
|
||||||
|
const [debounced, setDebounced] = useState(false),
|
||||||
|
isDisabled = disabled || debounced,
|
||||||
|
handleClick = (e: React.MouseEvent<HTMLAnchorElement>) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (disabled || debounced) return;
|
||||||
|
if (debounceMs != null) {
|
||||||
|
setDebounced(true);
|
||||||
|
setTimeout(() => setDebounced(false), debounceMs);
|
||||||
|
}
|
||||||
|
onClick?.(e);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<a
|
||||||
|
download={download}
|
||||||
|
className={`flex items-center group transition-all duration-200 bg-transparent rounded-sm hover:bg-purple-400 hover:shadow pl-1 pr-2 py-1 text-sm select-none ${className}`}
|
||||||
|
onClick={handleClick}
|
||||||
|
tabIndex={isDisabled ? -1 : 0}
|
||||||
|
aria-disabled={isDisabled}
|
||||||
|
>
|
||||||
|
<Image src={iconSrc} alt={iconAlt} width={20} height={20} />
|
||||||
|
<span className="overflow-hidden max-w-0 group-hover:max-w-[80px] transition-all duration-200 whitespace-nowrap ml-1">
|
||||||
|
{text}
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user