mirror of
https://github.com/khairul169/home-lab.git
synced 2025-04-28 16:49:36 +07:00
feat: update file manager
This commit is contained in:
parent
1160b30c3d
commit
2ba5da4b73
22
backend/routes/files/delete.ts
Normal file
22
backend/routes/files/delete.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import type { Context } from "hono";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { getFilePath } from "./utils";
|
||||||
|
import fs from "fs";
|
||||||
|
import { HTTPException } from "hono/http-exception";
|
||||||
|
|
||||||
|
const schema = z.object({
|
||||||
|
path: z.string().min(1),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const deleteFile = async (c: Context) => {
|
||||||
|
const data = schema.parse(await c.req.json());
|
||||||
|
const { path } = getFilePath(data.path);
|
||||||
|
|
||||||
|
if (!fs.existsSync(path)) {
|
||||||
|
throw new HTTPException(404, { message: "File not found!" });
|
||||||
|
}
|
||||||
|
|
||||||
|
await fs.promises.unlink(path);
|
||||||
|
|
||||||
|
return c.json({ result: true });
|
||||||
|
};
|
@ -6,6 +6,7 @@ import { download } from "./download";
|
|||||||
import { getYtdl, ytdl } from "./ytdl";
|
import { getYtdl, ytdl } from "./ytdl";
|
||||||
import cache from "../../middlewares/cache";
|
import cache from "../../middlewares/cache";
|
||||||
import { getId3Tags, getId3Image } from "./id3Tags";
|
import { getId3Tags, getId3Image } from "./id3Tags";
|
||||||
|
import { deleteFile } from "./delete";
|
||||||
|
|
||||||
const cacheFile = cache({ ttl: 86400 });
|
const cacheFile = cache({ ttl: 86400 });
|
||||||
|
|
||||||
@ -16,6 +17,7 @@ const route = new Hono()
|
|||||||
.get("/ytdl/:id", getYtdl)
|
.get("/ytdl/:id", getYtdl)
|
||||||
.get("/download/*", cacheFile, download)
|
.get("/download/*", cacheFile, download)
|
||||||
.get("/id3-tags/*", cacheFile, getId3Tags)
|
.get("/id3-tags/*", cacheFile, getId3Tags)
|
||||||
.get("/id3-img/*", cacheFile, getId3Image);
|
.get("/id3-img/*", cacheFile, getId3Image)
|
||||||
|
.delete("/delete", deleteFile);
|
||||||
|
|
||||||
export default route;
|
export default route;
|
||||||
|
@ -5,7 +5,7 @@ import { useAuth } from "@/stores/authStore";
|
|||||||
import BackButton from "@ui/BackButton";
|
import BackButton from "@ui/BackButton";
|
||||||
import Input from "@ui/Input";
|
import Input from "@ui/Input";
|
||||||
import { Stack } from "expo-router";
|
import { Stack } from "expo-router";
|
||||||
import React, { useState } from "react";
|
import React, { useMemo, useState } from "react";
|
||||||
import { useMutation, useQuery } from "react-query";
|
import { useMutation, useQuery } from "react-query";
|
||||||
import FileDrop from "@/components/pages/files/FileDrop";
|
import FileDrop from "@/components/pages/files/FileDrop";
|
||||||
import { showToast } from "@/stores/toastStore";
|
import { showToast } from "@/stores/toastStore";
|
||||||
@ -28,6 +28,9 @@ const FilesPage = () => {
|
|||||||
path: "",
|
path: "",
|
||||||
});
|
});
|
||||||
const [viewFile, setViewFile] = useState<FileItem | null>(null);
|
const [viewFile, setViewFile] = useState<FileItem | null>(null);
|
||||||
|
const [isSearching, setSearching] = useState(false);
|
||||||
|
const [search, setSearch] = useState("");
|
||||||
|
|
||||||
const parentPath =
|
const parentPath =
|
||||||
params.path.length > 0
|
params.path.length > 0
|
||||||
? params.path.split("/").slice(0, -1).join("/")
|
? params.path.split("/").slice(0, -1).join("/")
|
||||||
@ -65,6 +68,18 @@ const FilesPage = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const files = useMemo(() => {
|
||||||
|
let items = [...(data || [])].map((item, idx) => ({ ...item, idx }));
|
||||||
|
|
||||||
|
if (search) {
|
||||||
|
items = items.filter((item) =>
|
||||||
|
item.name.toLowerCase().includes(search.toLowerCase())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return items;
|
||||||
|
}, [data, search]);
|
||||||
|
|
||||||
if (!isLoggedIn) {
|
if (!isLoggedIn) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -81,7 +96,34 @@ const FilesPage = () => {
|
|||||||
>
|
>
|
||||||
<Head title="Files" />
|
<Head title="Files" />
|
||||||
<Stack.Screen
|
<Stack.Screen
|
||||||
options={{ headerLeft: () => <BackButton />, title: "Files" }}
|
options={{
|
||||||
|
headerLeft: () => <BackButton />,
|
||||||
|
title: "Files",
|
||||||
|
headerRight: () => (
|
||||||
|
<Button
|
||||||
|
variant="icon"
|
||||||
|
size="lg"
|
||||||
|
icon={<Ionicons name={!isSearching ? "search" : "close"} />}
|
||||||
|
onPress={() => {
|
||||||
|
setSearching(!isSearching);
|
||||||
|
setSearch("");
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
headerTitleAlign: isSearching ? "center" : undefined,
|
||||||
|
headerTitle: isSearching
|
||||||
|
? () => (
|
||||||
|
<Input
|
||||||
|
placeholder="Search..."
|
||||||
|
value={search}
|
||||||
|
onChangeText={setSearch}
|
||||||
|
autoFocus
|
||||||
|
className="lg:w-screen lg:max-w-3xl"
|
||||||
|
leftElement={<Ionicons name="search" size={18} />}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
: undefined,
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Container className="flex-1">
|
<Container className="flex-1">
|
||||||
@ -106,8 +148,8 @@ const FilesPage = () => {
|
|||||||
|
|
||||||
<FileDrop onFileDrop={onFileDrop} isDisabled={upload.isLoading}>
|
<FileDrop onFileDrop={onFileDrop} isDisabled={upload.isLoading}>
|
||||||
<FileList
|
<FileList
|
||||||
files={data}
|
files={files}
|
||||||
onSelect={(file, idx) => {
|
onSelect={(file: FileItem & { idx: number }) => {
|
||||||
if (file.isDirectory) {
|
if (file.isDirectory) {
|
||||||
return setParams({ path: file.path });
|
return setParams({ path: file.path });
|
||||||
}
|
}
|
||||||
@ -115,7 +157,7 @@ const FilesPage = () => {
|
|||||||
const fileType = getFileType(file.path);
|
const fileType = getFileType(file.path);
|
||||||
if (fileType === "audio") {
|
if (fileType === "audio") {
|
||||||
audioPlayer.expand();
|
audioPlayer.expand();
|
||||||
return audioPlayer.play(data, idx);
|
return audioPlayer.play(data, file.idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
setViewFile(file);
|
setViewFile(file);
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { getFileUrl } from "@/app/apps/lib";
|
import { getFileUrl } from "@/app/apps/lib";
|
||||||
import { fetchAPI } from "@/lib/api";
|
import { fetchAPI } from "@/lib/api";
|
||||||
import { API_BASEURL } from "@/lib/constants";
|
import { API_BASEURL } from "@/lib/constants";
|
||||||
import { base64encode } from "@/lib/utils";
|
|
||||||
import { audioPlayer, audioPlayerStore } from "@/stores/audioPlayerStore";
|
import { audioPlayer, audioPlayerStore } from "@/stores/audioPlayerStore";
|
||||||
import authStore from "@/stores/authStore";
|
import authStore from "@/stores/authStore";
|
||||||
import { AVPlaybackStatusSuccess, Audio } from "expo-av";
|
import { AVPlaybackStatusSuccess, Audio } from "expo-av";
|
||||||
@ -9,9 +8,10 @@ import { useEffect, useRef } from "react";
|
|||||||
import { useStore } from "zustand";
|
import { useStore } from "zustand";
|
||||||
|
|
||||||
const AudioPlayerProvider = () => {
|
const AudioPlayerProvider = () => {
|
||||||
const { currentIdx, playlist, repeat, status } = useStore(audioPlayerStore);
|
const { currentIdx, playlist, repeat, status, shouldPlay } =
|
||||||
|
useStore(audioPlayerStore);
|
||||||
const soundRef = useRef<Audio.Sound | null>(null);
|
const soundRef = useRef<Audio.Sound | null>(null);
|
||||||
|
const lastStatusRef = useRef<Date>(new Date());
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!playlist?.length || currentIdx < 0) {
|
if (!playlist?.length || currentIdx < 0) {
|
||||||
@ -27,8 +27,16 @@ const AudioPlayerProvider = () => {
|
|||||||
soundRef.current = sound;
|
soundRef.current = sound;
|
||||||
audioPlayerStore.setState({ sound });
|
audioPlayerStore.setState({ sound });
|
||||||
|
|
||||||
|
sound.setProgressUpdateIntervalAsync(1000);
|
||||||
sound.setIsLoopingAsync(repeat);
|
sound.setIsLoopingAsync(repeat);
|
||||||
sound.setOnPlaybackStatusUpdate((st: AVPlaybackStatusSuccess) => {
|
sound.setOnPlaybackStatusUpdate((st: AVPlaybackStatusSuccess) => {
|
||||||
|
const curDate = new Date();
|
||||||
|
const diff = curDate.getTime() - lastStatusRef.current.getTime();
|
||||||
|
if (diff < 1000) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
lastStatusRef.current = curDate;
|
||||||
audioPlayerStore.setState({ status: st as any });
|
audioPlayerStore.setState({ status: st as any });
|
||||||
|
|
||||||
if (st.didJustFinish) {
|
if (st.didJustFinish) {
|
||||||
@ -65,17 +73,24 @@ const AudioPlayerProvider = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
loadMediaTags();
|
loadMediaTags();
|
||||||
|
|
||||||
|
if (shouldPlay) {
|
||||||
play();
|
play();
|
||||||
|
}
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
soundRef.current?.unloadAsync();
|
const sound = soundRef.current;
|
||||||
|
if (sound) {
|
||||||
|
sound.unloadAsync();
|
||||||
|
sound.setOnPlaybackStatusUpdate(null);
|
||||||
soundRef.current = null;
|
soundRef.current = null;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}, [currentIdx, playlist]);
|
}, [currentIdx, playlist, shouldPlay]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (status?.isLoaded) {
|
if (status?.isLoaded && soundRef.current) {
|
||||||
soundRef.current?.setIsLoopingAsync(repeat);
|
soundRef.current.setIsLoopingAsync(repeat);
|
||||||
}
|
}
|
||||||
}, [repeat, status?.isLoaded]);
|
}, [repeat, status?.isLoaded]);
|
||||||
|
|
||||||
|
@ -8,6 +8,11 @@ import ActionSheet from "@ui/ActionSheet";
|
|||||||
import { HStack } from "@ui/Stack";
|
import { HStack } from "@ui/Stack";
|
||||||
import Button from "@ui/Button";
|
import Button from "@ui/Button";
|
||||||
import { openFile } from "@/app/apps/lib";
|
import { openFile } from "@/app/apps/lib";
|
||||||
|
import { showDialog } from "@/stores/dialogStore";
|
||||||
|
import api from "@/lib/api";
|
||||||
|
import { useMutation } from "react-query";
|
||||||
|
import { useFilesContext } from "./FilesContext";
|
||||||
|
import { showToast } from "@/stores/toastStore";
|
||||||
|
|
||||||
type Store = {
|
type Store = {
|
||||||
isVisible: boolean;
|
isVisible: boolean;
|
||||||
@ -26,12 +31,30 @@ export const openFileMenu = (file: FileItem) => {
|
|||||||
const FileMenu = () => {
|
const FileMenu = () => {
|
||||||
const { isVisible, file } = useStore(store);
|
const { isVisible, file } = useStore(store);
|
||||||
const onClose = () => store.setState({ isVisible: false });
|
const onClose = () => store.setState({ isVisible: false });
|
||||||
|
const { refresh } = useFilesContext();
|
||||||
|
|
||||||
|
const deleteMutation = useMutation({
|
||||||
|
mutationFn: (json: any) => api.files.delete.$delete({ json }),
|
||||||
|
onSuccess: () => {
|
||||||
|
refresh();
|
||||||
|
showToast("File deleted!");
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const onDownload = () => {
|
const onDownload = () => {
|
||||||
openFile(file, true);
|
openFile(file, true);
|
||||||
onClose();
|
onClose();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onDelete = () => {
|
||||||
|
showDialog(
|
||||||
|
"Delete file",
|
||||||
|
"Are you sure you want to delete this file?",
|
||||||
|
() => deleteMutation.mutate({ path: file?.path })
|
||||||
|
);
|
||||||
|
onClose();
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ActionSheet isVisible={isVisible} onClose={onClose}>
|
<ActionSheet isVisible={isVisible} onClose={onClose}>
|
||||||
<Text className="text-lg md:text-xl" numberOfLines={1}>
|
<Text className="text-lg md:text-xl" numberOfLines={1}>
|
||||||
@ -45,7 +68,9 @@ const FileMenu = () => {
|
|||||||
<List.Item icon={<Ionicons name="download" />} onPress={onDownload}>
|
<List.Item icon={<Ionicons name="download" />} onPress={onDownload}>
|
||||||
Download
|
Download
|
||||||
</List.Item>
|
</List.Item>
|
||||||
<List.Item icon={<Ionicons name="trash" />}>Delete</List.Item>
|
<List.Item icon={<Ionicons name="trash" />} onPress={onDelete}>
|
||||||
|
Delete
|
||||||
|
</List.Item>
|
||||||
</List>
|
</List>
|
||||||
|
|
||||||
<HStack className="justify-end mt-6 hidden md:flex">
|
<HStack className="justify-end mt-6 hidden md:flex">
|
||||||
|
@ -13,7 +13,6 @@ import Input from "@ui/Input";
|
|||||||
import { useStore } from "zustand";
|
import { useStore } from "zustand";
|
||||||
import { audioPlayer, audioPlayerStore } from "@/stores/audioPlayerStore";
|
import { audioPlayer, audioPlayerStore } from "@/stores/audioPlayerStore";
|
||||||
import Modal from "react-native-modal";
|
import Modal from "react-native-modal";
|
||||||
import { getFileUrl } from "@/app/apps/lib";
|
|
||||||
import { useQuery } from "react-query";
|
import { useQuery } from "react-query";
|
||||||
import { API_BASEURL } from "@/lib/constants";
|
import { API_BASEURL } from "@/lib/constants";
|
||||||
import authStore from "@/stores/authStore";
|
import authStore from "@/stores/authStore";
|
||||||
|
@ -6,31 +6,39 @@ import Button from "@ui/Button";
|
|||||||
import { Ionicons } from "@ui/Icons";
|
import { Ionicons } from "@ui/Icons";
|
||||||
import { HStack } from "@ui/Stack";
|
import { HStack } from "@ui/Stack";
|
||||||
import Text from "@ui/Text";
|
import Text from "@ui/Text";
|
||||||
import React from "react";
|
import React, { useState } from "react";
|
||||||
import { Pressable } from "react-native";
|
import { Pressable } from "react-native";
|
||||||
import { useStore } from "zustand";
|
import { useStore } from "zustand";
|
||||||
|
|
||||||
const MiniPlayer = () => {
|
const MiniPlayer = () => {
|
||||||
const { status, playlist, currentIdx, mediaTags } =
|
const { status, playlist, currentIdx, mediaTags } =
|
||||||
useStore(audioPlayerStore);
|
useStore(audioPlayerStore);
|
||||||
|
const [minimize, setMinimize] = useState(true);
|
||||||
const current = playlist[currentIdx];
|
const current = playlist[currentIdx];
|
||||||
const filename = getFilename(current?.path);
|
const filename = getFilename(current?.path);
|
||||||
|
|
||||||
if (!status?.isLoaded) {
|
const onExpand = () => audioPlayerStore.setState({ expanded: true });
|
||||||
return null;
|
|
||||||
|
if (minimize) {
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
icon={<Ionicons name="musical-notes" />}
|
||||||
|
className="absolute bottom-4 right-4 rounded-full"
|
||||||
|
size="icon-lg"
|
||||||
|
onPress={() => {
|
||||||
|
setMinimize(false);
|
||||||
|
onExpand();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Box className="w-full h-20 flex md:hidden" />
|
<Box className="w-full h-20 flex md:hidden" />
|
||||||
<HStack className="absolute bottom-0 right-0 md:bottom-4 md:right-4 bg-white md:rounded-lg shadow-lg w-full max-w-sm">
|
<HStack className="absolute bottom-0 right-0 md:bottom-4 md:right-4 bg-white md:rounded-lg shadow-lg w-full max-w-sm">
|
||||||
<Pressable
|
<Pressable style={cn("flex-1 p-4")} onPress={onExpand}>
|
||||||
style={cn("flex-1 p-4")}
|
<Text numberOfLines={1}>{mediaTags?.title || filename || "..."}</Text>
|
||||||
onPress={() => audioPlayerStore.setState({ expanded: true })}
|
|
||||||
>
|
|
||||||
<Text numberOfLines={1}>
|
|
||||||
{mediaTags?.tags?.title || filename || "..."}
|
|
||||||
</Text>
|
|
||||||
<Slider
|
<Slider
|
||||||
minimumValue={0}
|
minimumValue={0}
|
||||||
maximumValue={100}
|
maximumValue={100}
|
||||||
@ -64,6 +72,13 @@ const MiniPlayer = () => {
|
|||||||
icon={<Ionicons name="play-forward" />}
|
icon={<Ionicons name="play-forward" />}
|
||||||
onPress={audioPlayer.next}
|
onPress={audioPlayer.next}
|
||||||
/>
|
/>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
icon={<Ionicons name="chevron-down" />}
|
||||||
|
iconClassName="text-xl"
|
||||||
|
onPress={() => setMinimize(true)}
|
||||||
|
/>
|
||||||
</HStack>
|
</HStack>
|
||||||
</HStack>
|
</HStack>
|
||||||
</>
|
</>
|
||||||
|
@ -15,12 +15,14 @@ const buttonVariants = cva(
|
|||||||
ghost: "",
|
ghost: "",
|
||||||
link: "text-primary underline-offset-4",
|
link: "text-primary underline-offset-4",
|
||||||
outline: "border border-primary",
|
outline: "border border-primary",
|
||||||
|
icon: "",
|
||||||
},
|
},
|
||||||
size: {
|
size: {
|
||||||
default: "h-10 px-4",
|
default: "h-10 px-4",
|
||||||
sm: "h-8 px-2",
|
sm: "h-8 px-2",
|
||||||
lg: "h-12 px-8",
|
lg: "h-12 px-8",
|
||||||
icon: "h-10 w-10 px-0",
|
icon: "h-10 w-10 px-0",
|
||||||
|
"icon-lg": "h-14 w-14 px-0",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
@ -39,12 +41,14 @@ const buttonTextVariants = cva("text-center font-medium", {
|
|||||||
ghost: "text-primary",
|
ghost: "text-primary",
|
||||||
link: "text-primary-foreground underline",
|
link: "text-primary-foreground underline",
|
||||||
outline: "text-primary",
|
outline: "text-primary",
|
||||||
|
icon: "text-secondary-foreground",
|
||||||
},
|
},
|
||||||
size: {
|
size: {
|
||||||
default: "text-base",
|
default: "text-base",
|
||||||
sm: "text-sm",
|
sm: "text-sm",
|
||||||
lg: "text-xl",
|
lg: "text-xl",
|
||||||
icon: "text-base",
|
icon: "text-base",
|
||||||
|
"icon-lg": "text-lg",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
|
@ -10,6 +10,7 @@ type BaseInputProps = ComponentPropsWithClassName<typeof TextInput> & {
|
|||||||
label?: string;
|
label?: string;
|
||||||
inputClassName?: string;
|
inputClassName?: string;
|
||||||
error?: string;
|
error?: string;
|
||||||
|
leftElement?: React.ReactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
type InputProps<T extends FieldValues> = BaseInputProps & {
|
type InputProps<T extends FieldValues> = BaseInputProps & {
|
||||||
@ -22,20 +23,30 @@ const BaseInput = ({
|
|||||||
inputClassName,
|
inputClassName,
|
||||||
label,
|
label,
|
||||||
error,
|
error,
|
||||||
|
leftElement,
|
||||||
...props
|
...props
|
||||||
}: BaseInputProps) => {
|
}: BaseInputProps) => {
|
||||||
return (
|
return (
|
||||||
<Box className={className}>
|
<Box className={className}>
|
||||||
{label ? <Text className="text-sm mb-1">{label}</Text> : null}
|
{label ? <Text className="text-sm mb-1">{label}</Text> : null}
|
||||||
|
|
||||||
|
<Box className="relative w-full">
|
||||||
|
{leftElement ? (
|
||||||
|
<Box className="absolute left-0 top-0 h-full aspect-square flex items-center justify-center">
|
||||||
|
{leftElement}
|
||||||
|
</Box>
|
||||||
|
) : null}
|
||||||
|
|
||||||
<TextInput
|
<TextInput
|
||||||
style={cn(
|
style={cn(
|
||||||
"border border-gray-300 rounded-lg px-3 h-10 w-full",
|
"border border-gray-300 rounded-lg px-3 h-10 w-full",
|
||||||
|
leftElement ? "pl-10" : "",
|
||||||
inputClassName
|
inputClassName
|
||||||
)}
|
)}
|
||||||
placeholderTextColor="#787878"
|
placeholderTextColor="#787878"
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
{error ? (
|
{error ? (
|
||||||
<Text className="text-red-500 text-sm mt-1">{error}</Text>
|
<Text className="text-red-500 text-sm mt-1">{error}</Text>
|
||||||
|
17
src/hooks/useDisclosure.ts
Normal file
17
src/hooks/useDisclosure.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
|
export const useDisclosure = () => {
|
||||||
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
const [data, setData] = useState<any>();
|
||||||
|
|
||||||
|
const open = (data?: any) => {
|
||||||
|
setIsOpen(true);
|
||||||
|
setData(data);
|
||||||
|
};
|
||||||
|
|
||||||
|
const close = () => {
|
||||||
|
setIsOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return { isOpen, open, close, data };
|
||||||
|
};
|
@ -19,6 +19,7 @@ type AudioPlayerStore = {
|
|||||||
status: AVPlaybackStatusSuccess | null;
|
status: AVPlaybackStatusSuccess | null;
|
||||||
mediaTags: MediaTags | null;
|
mediaTags: MediaTags | null;
|
||||||
expanded: boolean;
|
expanded: boolean;
|
||||||
|
shouldPlay: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const audioPlayerStore = createStore(
|
export const audioPlayerStore = createStore(
|
||||||
@ -32,6 +33,7 @@ export const audioPlayerStore = createStore(
|
|||||||
status: null,
|
status: null,
|
||||||
mediaTags: null,
|
mediaTags: null,
|
||||||
expanded: false,
|
expanded: false,
|
||||||
|
shouldPlay: false,
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
name: "audioPlayer",
|
name: "audioPlayer",
|
||||||
@ -42,6 +44,7 @@ export const audioPlayerStore = createStore(
|
|||||||
status: null,
|
status: null,
|
||||||
mediaTags: null,
|
mediaTags: null,
|
||||||
expanded: false,
|
expanded: false,
|
||||||
|
shouldPlay: false,
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -58,6 +61,7 @@ const play = (files: FileItem[], idx: number) => {
|
|||||||
currentIdx,
|
currentIdx,
|
||||||
status: null,
|
status: null,
|
||||||
mediaTags: null,
|
mediaTags: null,
|
||||||
|
shouldPlay: true,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -72,7 +76,14 @@ const advanceBy = (increment: number) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const togglePlay = async () => {
|
const togglePlay = async () => {
|
||||||
const { sound, status } = audioPlayerStore.getState();
|
const { sound, status, shouldPlay } = audioPlayerStore.getState();
|
||||||
|
|
||||||
|
if (!shouldPlay || !sound || !status?.isPlaying) {
|
||||||
|
console.log("shoud play toggle");
|
||||||
|
audioPlayerStore.setState({ shouldPlay: true });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!sound) {
|
if (!sound) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user