mirror of
https://github.com/sussy-code/smov.git
synced 2026-05-20 00:21:49 +00:00
124 lines
3.9 KiB
TypeScript
124 lines
3.9 KiB
TypeScript
import { useAutoAnimate } from "@formkit/auto-animate/react";
|
|
import { useEffect, useMemo, useRef, useState } from "react";
|
|
import { useTranslation } from "react-i18next";
|
|
|
|
import { EditButton } from "@/components/buttons/EditButton";
|
|
import { Icons } from "@/components/Icon";
|
|
import { SectionHeading } from "@/components/layout/SectionHeading";
|
|
import { MediaGrid } from "@/components/media/MediaGrid";
|
|
import { WatchedMediaCard } from "@/components/media/WatchedMediaCard";
|
|
import { useBookmarkStore } from "@/stores/bookmarks";
|
|
import { useProgressStore } from "@/stores/progress";
|
|
import { MediaItem } from "@/utils/mediaTypes";
|
|
|
|
const LONG_PRESS_DURATION = 500; // 0.5 seconds
|
|
|
|
export function BookmarksPart({
|
|
onItemsChange,
|
|
}: {
|
|
onItemsChange: (hasItems: boolean) => void;
|
|
}) {
|
|
const { t } = useTranslation();
|
|
const progressItems = useProgressStore((s) => s.items);
|
|
const bookmarks = useBookmarkStore((s) => s.bookmarks);
|
|
const removeBookmark = useBookmarkStore((s) => s.removeBookmark);
|
|
const [editing, setEditing] = useState(false);
|
|
const [gridRef] = useAutoAnimate<HTMLDivElement>();
|
|
|
|
const pressTimerRef = useRef<NodeJS.Timeout | null>(null);
|
|
|
|
const items = useMemo(() => {
|
|
let output: MediaItem[] = [];
|
|
Object.entries(bookmarks).forEach((entry) => {
|
|
output.push({
|
|
id: entry[0],
|
|
...entry[1],
|
|
});
|
|
});
|
|
output = output.sort((a, b) => {
|
|
const bookmarkA = bookmarks[a.id];
|
|
const bookmarkB = bookmarks[b.id];
|
|
const progressA = progressItems[a.id];
|
|
const progressB = progressItems[b.id];
|
|
|
|
const dateA = Math.max(bookmarkA.updatedAt, progressA?.updatedAt ?? 0);
|
|
const dateB = Math.max(bookmarkB.updatedAt, progressB?.updatedAt ?? 0);
|
|
|
|
return dateB - dateA;
|
|
});
|
|
return output;
|
|
}, [bookmarks, progressItems]);
|
|
|
|
useEffect(() => {
|
|
onItemsChange(items.length > 0);
|
|
}, [items, onItemsChange]);
|
|
|
|
const handleLongPress = () => {
|
|
// Find the button by ID and simulate a click
|
|
const editButton = document.getElementById("edit-button-bookmark");
|
|
if (editButton) {
|
|
(editButton as HTMLButtonElement).click();
|
|
}
|
|
};
|
|
|
|
const handleTouchStart = (e: React.TouchEvent<HTMLDivElement>) => {
|
|
e.preventDefault(); // Prevent default touch action
|
|
pressTimerRef.current = setTimeout(handleLongPress, LONG_PRESS_DURATION);
|
|
};
|
|
|
|
const handleTouchEnd = () => {
|
|
if (pressTimerRef.current) {
|
|
clearTimeout(pressTimerRef.current);
|
|
pressTimerRef.current = null;
|
|
}
|
|
};
|
|
|
|
const handleMouseDown = (e: React.MouseEvent<HTMLDivElement>) => {
|
|
e.preventDefault(); // Prevent default mouse action
|
|
pressTimerRef.current = setTimeout(handleLongPress, LONG_PRESS_DURATION);
|
|
};
|
|
|
|
const handleMouseUp = () => {
|
|
if (pressTimerRef.current) {
|
|
clearTimeout(pressTimerRef.current);
|
|
pressTimerRef.current = null;
|
|
}
|
|
};
|
|
|
|
if (items.length === 0) return null;
|
|
|
|
return (
|
|
<div
|
|
className="relative"
|
|
style={{ userSelect: "none" }} // Disable text selection
|
|
onContextMenu={(e: React.MouseEvent<HTMLDivElement>) =>
|
|
e.preventDefault()
|
|
} // Prevent right-click context menu
|
|
onTouchStart={handleTouchStart} // Handle touch start
|
|
onTouchEnd={handleTouchEnd} // Handle touch end
|
|
onMouseDown={handleMouseDown} // Handle mouse down
|
|
onMouseUp={handleMouseUp} // Handle mouse up
|
|
>
|
|
<SectionHeading
|
|
title={t("home.bookmarks.sectionTitle") || "Bookmarks"}
|
|
icon={Icons.BOOKMARK}
|
|
>
|
|
<EditButton
|
|
editing={editing}
|
|
onEdit={setEditing}
|
|
id="edit-button-bookmark"
|
|
/>
|
|
</SectionHeading>
|
|
<MediaGrid ref={gridRef}>
|
|
{items.map((v) => (
|
|
<WatchedMediaCard
|
|
key={v.id}
|
|
media={v}
|
|
closable={editing}
|
|
onClose={() => removeBookmark(v.id)}
|
|
/>
|
|
))}
|
|
</MediaGrid>
|
|
</div>
|
|
);
|
|
}
|