diff --git a/app/Http/Controllers/ComicController.php b/app/Http/Controllers/ComicController.php index 21fd55e..2a008b3 100644 --- a/app/Http/Controllers/ComicController.php +++ b/app/Http/Controllers/ComicController.php @@ -12,6 +12,7 @@ use Illuminate\Contracts\Routing\ResponseFactory; use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Foundation\Application; use Illuminate\Http\JsonResponse; +use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; use Illuminate\Http\Response as IlluminateHttpResponse; use Illuminate\Support\Facades\Cache; @@ -169,9 +170,14 @@ class ComicController extends Controller // Do an upsert Chapter::upsert($arrayForUpsert, uniqueBy: 'chapter_uuid'); + // Get history + $histories = $request->user()->readingHistories()->where('reading_histories.comic_id', $comicObject->id) + ->distinct()->select('chapter_uuid')->get()->pluck('chapter_uuid'); + return Inertia::render('Comic/Chapters', [ 'comic' => $comic, 'chapters' => $chapters, + 'histories' => $histories ]); } @@ -213,12 +219,32 @@ class ComicController extends Controller // Do an upsert Image::upsert($arrayForUpsert, uniqueBy: 'url'); + // Update history + $request->user()->readingHistories()->attach($chapterObj->id, ['comic_id' => $comicObj->id]); + return Inertia::render('Comic/Read', [ 'comic' => $comic, 'chapter' => $chapter, ]); } + public function histories(Request $request): Response + { + // Get history + $histories = $request->user()->readingHistories()->with(['comic:id,name,pathword'])->orderByDesc('reading_histories.created_at') + ->select(['reading_histories.id as hid', 'reading_histories.created_at', 'chapters.comic_id', 'chapters.name'])->paginate(50)->toArray(); + + return Inertia::render('Comic/Histories', [ + 'histories' => $histories + ]); + } + + public function destroyHistories(Request $request): RedirectResponse + { + $histories = $request->user()->readingHistories()->whereIn('reading_histories.id', $request->get('ids'))->delete(); + return redirect()->route('comics.histories'); + } + public function tags() { // TODO diff --git a/app/Models/User.php b/app/Models/User.php index 5976655..2bc51e9 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -56,7 +56,7 @@ class User extends Authenticatable implements MustVerifyEmail public function readingHistories(): BelongsToMany { - return $this->belongsToMany(Comic::class, 'reading_histories')->withTimestamps(); + return $this->belongsToMany(Chapter::class, 'reading_histories')->withTimestamps(); } } diff --git a/bun.lockb b/bun.lockb index dc90011..56f01cb 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index 5eae54c..eeb6c64 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "@radix-ui/react-tabs": "^1.1.2", "@radix-ui/react-toast": "^1.2.4", "@radix-ui/react-tooltip": "^1.1.6", + "@tanstack/react-table": "^8.20.6", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "lodash": "^4.17.21", diff --git a/resources/js/Layouts/AppLayout.jsx b/resources/js/Layouts/AppLayout.jsx index e4faa1b..949efab 100644 --- a/resources/js/Layouts/AppLayout.jsx +++ b/resources/js/Layouts/AppLayout.jsx @@ -1,5 +1,5 @@ import { SidebarInset, SidebarProvider, SidebarTrigger } from '@/components/ui/sidebar'; -import { AppSidebar } from '@/Components/ui/app-sidebar.jsx'; +import { AppSidebar } from '@/components/ui/app-sidebar.jsx'; import { Separator } from "@radix-ui/react-separator"; import { Breadcrumb, BreadcrumbItem, BreadcrumbLink, BreadcrumbList } from "@/components/ui/breadcrumb"; import { Toaster } from '@/components/ui/toaster'; diff --git a/resources/js/Pages/Comic/Chapters.jsx b/resources/js/Pages/Comic/Chapters.jsx index 46efb0e..800bc91 100644 --- a/resources/js/Pages/Comic/Chapters.jsx +++ b/resources/js/Pages/Comic/Chapters.jsx @@ -1,18 +1,18 @@ import { useState } from 'react'; import { Head, Link, router } from '@inertiajs/react'; -import { Moon, Plus, Star, ArrowDownNarrowWide, ArrowUpNarrowWide } from 'lucide-react'; +import { Plus, Star, ArrowDownNarrowWide, ArrowUpNarrowWide } from 'lucide-react'; import AppLayout from '@/Layouts/AppLayout.jsx'; +import { Badge } from "@/components/ui/badge"; import { BreadcrumbItem, BreadcrumbPage, BreadcrumbSeparator } from '@/components/ui/breadcrumb'; import { Button } from '@/components/ui/button'; -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; import { useToast } from '@/hooks/use-toast'; -import { Badge } from "@/components/ui/badge.jsx"; -export default function Chapters({ auth, comic, chapters }) { +export default function Chapters({ auth, comic, chapters, histories }) { const [group, setGroup] = useState('default'); const [favourites, setFavourites] = useState(auth.user.favourites); @@ -42,12 +42,13 @@ export default function Chapters({ auth, comic, chapters }) { const ComicChapterLink = (props) => { const isNew = Date.now() - Date.parse(props.datetime_created) < 6.048e+8; + const isRead = histories.includes(props.uuid); return ( - + + + + + Select + Comic + Chapter + Read at + + + + { histories.data.map((h, i) => ( + + + + + { h.name } + { h.comic.name } + { h.created_at } + + )) } + +
+
+ + + { histories.current_page > 1 && ( + + + + ) } + + { histories.current_page } + + + + + { histories.current_page < histories.last_page && ( + + + + ) } + + + +
+ + +); +} diff --git a/resources/js/components/ThemeProvider.tsx b/resources/js/components/ThemeProvider.tsx deleted file mode 100644 index 3b59c58..0000000 --- a/resources/js/components/ThemeProvider.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import { createContext, useContext, useEffect, useState } from "react" - -type Theme = "dark" | "light" | "system" - -type ThemeProviderProps = { - children: React.ReactNode - defaultTheme?: Theme - storageKey?: string -} - -type ThemeProviderState = { - theme: Theme - setTheme: (theme: Theme) => void -} - -const initialState: ThemeProviderState = { - theme: "system", - setTheme: () => null, -} - -const ThemeProviderContext = createContext(initialState) - -export function ThemeProvider({ - children, - defaultTheme = "system", - storageKey = "vite-ui-theme", - ...props - }: ThemeProviderProps) { - const [theme, setTheme] = useState( - () => (localStorage.getItem(storageKey) as Theme) || defaultTheme - ) - - useEffect(() => { - const root = window.document.documentElement - - root.classList.remove("light", "dark") - - if (theme === "system") { - const systemTheme = window.matchMedia("(prefers-color-scheme: dark)") - .matches - ? "dark" - : "light" - - root.classList.add(systemTheme) - return - } - - root.classList.add(theme) - }, [theme]) - - const value = { - theme, - setTheme: (theme: Theme) => { - console.log(storageKey) - localStorage.setItem(storageKey, theme) - setTheme(theme) - }, - } - - // @ts-ignore - return ( - - {children} - - ) -} - -export const useTheme = () => { - const context = useContext(ThemeProviderContext) - console.log(context); - if (context === undefined) - throw new Error("useTheme must be used within a ThemeProvider") - - return context -} diff --git a/resources/js/components/ui/app-sidebar.jsx b/resources/js/components/ui/app-sidebar.jsx index 3dc8434..97e0916 100644 --- a/resources/js/components/ui/app-sidebar.jsx +++ b/resources/js/components/ui/app-sidebar.jsx @@ -120,7 +120,7 @@ export function AppSidebar({ auth }) { Profile Favourites - History + History Log out diff --git a/routes/web.php b/routes/web.php index cd9b4c3..7996e8c 100644 --- a/routes/web.php +++ b/routes/web.php @@ -2,7 +2,6 @@ use App\Http\Controllers\ComicController; use App\Http\Controllers\ProfileController; -use Illuminate\Foundation\Application; use Illuminate\Support\Facades\Route; use Inertia\Inertia; @@ -21,6 +20,10 @@ Route::controller(ComicController::class)->middleware('auth')->name('comics.')-> // Toggle favourites Route::post('/favourites', 'postFavourite')->name('postFavourite'); + + // Histories + Route::get('/histories', 'histories')->name('histories'); + Route::patch('/histories', 'destroyHistories')->name('destroyHistories'); // Only patch accept params }); Route::get('/dashboard', function () {