Fix: Improve UI (#103)

* use sonner

* fix ui

* fix post audio player
This commit is contained in:
an-lee
2024-01-13 22:59:57 +08:00
committed by GitHub
parent d6a4b24a1e
commit fe0542e8c6
26 changed files with 210 additions and 204 deletions

View File

@@ -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"));
});
}
};

View File

@@ -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>
</>
);
};

View File

@@ -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>

View File

@@ -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);