@@ -1,4 +1,4 @@
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
import { useContext, useState } from "react";
|
||||
import { AppSettingsProviderContext } from "@renderer/context";
|
||||
import { ConversationsShortcut } from "@renderer/components";
|
||||
import {
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
ScrollArea,
|
||||
useToast,
|
||||
toast,
|
||||
} from "@renderer/components/ui";
|
||||
import { t } from "i18next";
|
||||
import Markdown from "react-markdown";
|
||||
@@ -37,7 +37,6 @@ export const PostActions = (props: { post: PostType }) => {
|
||||
const [_, copyToClipboard] = useCopyToClipboard();
|
||||
const [copied, setCopied] = useState<boolean>(false);
|
||||
const { EnjoyApp } = useContext(AppSettingsProviderContext);
|
||||
const { toast } = useToast();
|
||||
const [asking, setAsking] = useState<boolean>(false);
|
||||
const [aiReplies, setAiReplies] = useState<MessageType[]>([]);
|
||||
|
||||
@@ -50,9 +49,7 @@ export const PostActions = (props: { post: PostType }) => {
|
||||
try {
|
||||
const video = await EnjoyApp.videos.findOne({ md5: medium.md5 });
|
||||
if (video) {
|
||||
toast({
|
||||
description: t("videoAlreadyAddedToLibrary"),
|
||||
});
|
||||
toast.info(t("videoAlreadyAddedToLibrary"));
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -65,21 +62,17 @@ export const PostActions = (props: { post: PostType }) => {
|
||||
md5: medium.md5,
|
||||
})
|
||||
.then(() => {
|
||||
toast({
|
||||
description: t("videoSuccessfullyAddedToLibrary"),
|
||||
});
|
||||
toast.success(t("videoSuccessfullyAddedToLibrary"));
|
||||
});
|
||||
} else if (medium.mediumType === "Audio") {
|
||||
try {
|
||||
const audio = await EnjoyApp.audios.findOne({ md5: medium.md5 });
|
||||
if (audio) {
|
||||
toast({
|
||||
description: t("audioAlreadyAddedToLibrary"),
|
||||
});
|
||||
toast.info(t("audioAlreadyAddedToLibrary"));
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
toast.error(error.message);
|
||||
}
|
||||
|
||||
EnjoyApp.audios
|
||||
@@ -88,9 +81,7 @@ export const PostActions = (props: { post: PostType }) => {
|
||||
md5: medium.md5,
|
||||
})
|
||||
.then(() => {
|
||||
toast({
|
||||
description: t("audioSuccessfullyAddedToLibrary"),
|
||||
});
|
||||
toast.success(t("audioSuccessfullyAddedToLibrary"));
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -6,12 +6,83 @@ import { Button, Skeleton } from "@renderer/components/ui";
|
||||
import { PlayIcon, PauseIcon } from "lucide-react";
|
||||
import { useIntersectionObserver } from "@uidotdev/usehooks";
|
||||
import { secondsToTimestamp } from "@renderer/lib/utils";
|
||||
import { MediaPlayer, MediaProvider } from "@vidstack/react";
|
||||
import {
|
||||
DefaultAudioLayout,
|
||||
defaultLayoutIcons,
|
||||
} from "@vidstack/react/player/layouts/default";
|
||||
export const STORAGE_WORKER_ENDPOINT = "https://enjoy-storage.baizhiheizi.com";
|
||||
|
||||
export const PostAudio = (props: {
|
||||
audio: Partial<MediumType>;
|
||||
height?: number;
|
||||
}) => {
|
||||
const { audio, height = 80 } = props;
|
||||
const [currentTime, setCurrentTime] = useState<number>(0);
|
||||
const { webApi } = useContext(AppSettingsProviderContext);
|
||||
const [transcription, setTranscription] = useState<TranscriptionType>();
|
||||
|
||||
const currentTranscription = (transcription?.result || []).find(
|
||||
(s) =>
|
||||
currentTime >= s.offsets.from / 1000.0 &&
|
||||
currentTime <= s.offsets.to / 1000.0
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
webApi
|
||||
.transcriptions({
|
||||
targetMd5: audio.md5,
|
||||
})
|
||||
.then((response) => {
|
||||
setTranscription(response?.transcriptions?.[0]);
|
||||
});
|
||||
}, [audio.md5]);
|
||||
|
||||
return (
|
||||
<div className="w-full">
|
||||
{audio.sourceUrl.startsWith(STORAGE_WORKER_ENDPOINT) ? (
|
||||
<WavesurferPlayer
|
||||
currentTime={currentTime}
|
||||
setCurrentTime={setCurrentTime}
|
||||
audio={audio}
|
||||
height={height}
|
||||
/>
|
||||
) : (
|
||||
<MediaPlayer
|
||||
onTimeUpdate={({ currentTime: _currentTime }) => {
|
||||
setCurrentTime(_currentTime);
|
||||
}}
|
||||
src={audio.sourceUrl}
|
||||
>
|
||||
<MediaProvider />
|
||||
<DefaultAudioLayout icons={defaultLayoutIcons} />
|
||||
</MediaPlayer>
|
||||
)}
|
||||
|
||||
{currentTranscription && (
|
||||
<div className="mt-2 bg-muted px-4 py-2 rounded">
|
||||
<div className="text-muted-foreground text-center font-serif">
|
||||
{currentTranscription.text}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{audio.coverUrl && (
|
||||
<div className="mt-2">
|
||||
<img src={audio.coverUrl} className="w-full rounded" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const WavesurferPlayer = (props: {
|
||||
audio: Partial<MediumType>;
|
||||
height?: number;
|
||||
currentTime: number;
|
||||
setCurrentTime: (currentTime: number) => void;
|
||||
}) => {
|
||||
const { audio, height = 80, currentTime, setCurrentTime } = props;
|
||||
const [initialized, setInitialized] = useState(false);
|
||||
const [isPlaying, setIsPlaying] = useState(false);
|
||||
const [wavesurfer, setWavesurfer] = useState(null);
|
||||
@@ -20,15 +91,6 @@ export const PostAudio = (props: {
|
||||
threshold: 1,
|
||||
});
|
||||
const [duration, setDuration] = useState<number>(0);
|
||||
const { webApi } = useContext(AppSettingsProviderContext);
|
||||
const [currentTime, setCurrentTime] = useState<number>(0);
|
||||
const [transcription, setTranscription] = useState<TranscriptionType>();
|
||||
|
||||
const currentTranscription = (transcription?.result || []).find(
|
||||
(s) =>
|
||||
currentTime >= s.offsets.from / 1000.0 &&
|
||||
currentTime <= s.offsets.to / 1000.0
|
||||
);
|
||||
|
||||
const onPlayClick = useCallback(() => {
|
||||
wavesurfer.isPlaying() ? wavesurfer.pause() : wavesurfer.play();
|
||||
@@ -93,18 +155,8 @@ export const PostAudio = (props: {
|
||||
};
|
||||
}, [wavesurfer]);
|
||||
|
||||
useEffect(() => {
|
||||
webApi
|
||||
.transcriptions({
|
||||
targetMd5: audio.md5,
|
||||
})
|
||||
.then((response) => {
|
||||
setTranscription(response?.transcriptions?.[0]);
|
||||
});
|
||||
}, [audio.md5]);
|
||||
|
||||
return (
|
||||
<div className="w-full">
|
||||
<>
|
||||
<div className="flex justify-end">
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{secondsToTimestamp(duration)}
|
||||
@@ -141,20 +193,6 @@ export const PostAudio = (props: {
|
||||
ref={containerRef}
|
||||
></div>
|
||||
</div>
|
||||
|
||||
{currentTranscription && (
|
||||
<div className="mt-2 bg-muted px-4 py-2 rounded">
|
||||
<div className="text-muted-foreground text-center font-serif">
|
||||
{currentTranscription.text}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{audio.coverUrl && (
|
||||
<div className="">
|
||||
<img src={audio.coverUrl} className="w-full rounded" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -37,7 +37,7 @@ export const PostMedium = (props: { medium: MediumType }) => {
|
||||
<div className="text-xs text-muted-foreground">
|
||||
{t("sharedAudio")}
|
||||
</div>
|
||||
<PostAudio audio={medium as Partial<AudioType>} />
|
||||
<PostAudio audio={medium} />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
import { AppSettingsProviderContext } from "@renderer/context";
|
||||
import { PostCard, LoaderSpin } from "@renderer/components";
|
||||
import { useToast, Button } from "@renderer/components//ui";
|
||||
import { toast, Button } from "@renderer/components//ui";
|
||||
import { t } from "i18next";
|
||||
|
||||
export const Posts = () => {
|
||||
@@ -9,23 +9,16 @@ export const Posts = () => {
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
const [posts, setPosts] = useState<PostType[]>([]);
|
||||
const [nextPage, setNextPage] = useState(1);
|
||||
const { toast } = useToast();
|
||||
|
||||
const handleDelete = (id: string) => {
|
||||
webApi
|
||||
.deletePost(id)
|
||||
.then(() => {
|
||||
toast({
|
||||
description: t("removeSharingSuccessfully"),
|
||||
});
|
||||
toast.success(t("removeSharingSuccessfully"));
|
||||
setPosts(posts.filter((post) => post.id !== id));
|
||||
})
|
||||
.catch((error) => {
|
||||
toast({
|
||||
title: t("removeSharingFailed"),
|
||||
description: error.message,
|
||||
variant: "destructive",
|
||||
});
|
||||
toast.error(t("removeSharingFailed"), { description: error.message });
|
||||
});
|
||||
};
|
||||
|
||||
@@ -42,10 +35,7 @@ export const Posts = () => {
|
||||
setNextPage(res.next);
|
||||
})
|
||||
.catch((err) => {
|
||||
toast({
|
||||
description: err.message,
|
||||
variant: "destructive",
|
||||
});
|
||||
toast.error(err.message);
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
|
||||
Reference in New Issue
Block a user