Fixed images on rtl mode
This commit is contained in:
@@ -8,6 +8,8 @@ use App\Models\Comic;
|
|||||||
use App\Models\Image;
|
use App\Models\Image;
|
||||||
use App\Remote\CopyManga;
|
use App\Remote\CopyManga;
|
||||||
use App\Remote\ImageFetcher;
|
use App\Remote\ImageFetcher;
|
||||||
|
use Exception;
|
||||||
|
use GuzzleHttp\Exception\GuzzleException;
|
||||||
use Illuminate\Contracts\Routing\ResponseFactory;
|
use Illuminate\Contracts\Routing\ResponseFactory;
|
||||||
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||||
use Illuminate\Foundation\Application;
|
use Illuminate\Foundation\Application;
|
||||||
@@ -118,6 +120,13 @@ class ComicController extends Controller
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @param Request $request
|
||||||
|
* @return Response
|
||||||
|
* @throws GuzzleException
|
||||||
|
*/
|
||||||
public function index(Request $request): Response
|
public function index(Request $request): Response
|
||||||
{
|
{
|
||||||
$params = [];
|
$params = [];
|
||||||
@@ -152,7 +161,7 @@ class ComicController extends Controller
|
|||||||
{
|
{
|
||||||
$comics = $this->copyManga->search($search, 30, $request->header('offset', 0));
|
$comics = $this->copyManga->search($search, 30, $request->header('offset', 0));
|
||||||
|
|
||||||
// Seacrh API is limited, no upsert
|
// Search API is limited, no upsert
|
||||||
|
|
||||||
return Inertia::render('Comic/Index', [
|
return Inertia::render('Comic/Index', [
|
||||||
'comics' => $comics,
|
'comics' => $comics,
|
||||||
@@ -163,7 +172,7 @@ class ComicController extends Controller
|
|||||||
public function chapters(Request $request, string $pathword = ''): Response
|
public function chapters(Request $request, string $pathword = ''): Response
|
||||||
{
|
{
|
||||||
$comic = $this->copyManga->comic($pathword);
|
$comic = $this->copyManga->comic($pathword);
|
||||||
$chapters = $this->copyManga->chapters($pathword, 200, 0, [], $request->get('group', 'default'));
|
$chapters = $this->copyManga->chapters($pathword, 200, $request->header('offset', 0), [], $request->get('group', 'default'));
|
||||||
|
|
||||||
// Get the comic object and fill other parameters
|
// Get the comic object and fill other parameters
|
||||||
try {
|
try {
|
||||||
@@ -221,7 +230,8 @@ class ComicController extends Controller
|
|||||||
return Inertia::render('Comic/Chapters', [
|
return Inertia::render('Comic/Chapters', [
|
||||||
'comic' => $comic,
|
'comic' => $comic,
|
||||||
'chapters' => $chapters,
|
'chapters' => $chapters,
|
||||||
'histories' => $histories
|
'histories' => $histories,
|
||||||
|
'offset' => $request->header('offset', 0)
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -276,7 +286,7 @@ class ComicController extends Controller
|
|||||||
{
|
{
|
||||||
// Get history
|
// Get history
|
||||||
$histories = $request->user()->readingHistories()->with(['comic:id,name,pathword'])->orderByDesc('reading_histories.created_at')
|
$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();
|
->select(['reading_histories.id as hid', 'reading_histories.created_at as read_at', 'chapters.comic_id', 'chapters.name'])->paginate(50)->toArray();
|
||||||
|
|
||||||
return Inertia::render('Comic/Histories', [
|
return Inertia::render('Comic/Histories', [
|
||||||
'histories' => $histories
|
'histories' => $histories
|
||||||
|
|||||||
10
composer.lock
generated
10
composer.lock
generated
@@ -1654,16 +1654,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "league/commonmark",
|
"name": "league/commonmark",
|
||||||
"version": "2.6.0",
|
"version": "2.6.1",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/thephpleague/commonmark.git",
|
"url": "https://github.com/thephpleague/commonmark.git",
|
||||||
"reference": "d150f911e0079e90ae3c106734c93137c184f932"
|
"reference": "d990688c91cedfb69753ffc2512727ec646df2ad"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/thephpleague/commonmark/zipball/d150f911e0079e90ae3c106734c93137c184f932",
|
"url": "https://api.github.com/repos/thephpleague/commonmark/zipball/d990688c91cedfb69753ffc2512727ec646df2ad",
|
||||||
"reference": "d150f911e0079e90ae3c106734c93137c184f932",
|
"reference": "d990688c91cedfb69753ffc2512727ec646df2ad",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -1757,7 +1757,7 @@
|
|||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2024-12-07T15:34:16+00:00"
|
"time": "2024-12-29T14:10:59+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "league/config",
|
"name": "league/config",
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import { Link } from '@inertiajs/react';
|
|
||||||
|
|
||||||
export default function GuestLayout({ children }) {
|
export default function GuestLayout({ children }) {
|
||||||
return (
|
return (
|
||||||
<div className="flex min-h-svh w-full items-center justify-center p-6 md:p-10">
|
<div className="flex min-h-svh w-full items-center justify-center p-6 md:p-10">
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
|
import { Head, Link, useForm } from '@inertiajs/react';
|
||||||
|
import GuestLayout from "@/Layouts/GuestLayout.jsx";
|
||||||
|
|
||||||
import InputError from '@/components/InputError';
|
import InputError from '@/components/InputError';
|
||||||
import Checkbox from '@/components/Checkbox';
|
import Checkbox from '@/components/Checkbox';
|
||||||
import PrimaryButton from '@/components/PrimaryButton';
|
import PrimaryButton from '@/components/PrimaryButton';
|
||||||
import TextInput from '@/components/TextInput';
|
import TextInput from '@/components/TextInput';
|
||||||
import { Head, Link, useForm } from '@inertiajs/react';
|
|
||||||
import { Label } from "@/components/ui/label"
|
import { Label } from "@/components/ui/label"
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card.jsx";
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card.jsx";
|
||||||
import GuestLayout from "@/Layouts/GuestLayout.jsx";
|
|
||||||
import { Alert, AlertDescription } from "@/components/ui/alert.jsx";
|
import { Alert, AlertDescription } from "@/components/ui/alert.jsx";
|
||||||
|
|
||||||
export default function Login({ status, canResetPassword }) {
|
export default function Login({ status, canResetPassword }) {
|
||||||
@@ -45,7 +46,7 @@ export default function Login({ status, canResetPassword }) {
|
|||||||
<div className="grid gap-2">
|
<div className="grid gap-2">
|
||||||
<Label htmlFor="email">E-mail Address</Label>
|
<Label htmlFor="email">E-mail Address</Label>
|
||||||
<TextInput type="email" id="email" placeholder="me@yumj.in" value={ data.email }
|
<TextInput type="email" id="email" placeholder="me@yumj.in" value={ data.email }
|
||||||
onChange={ (e) => setData('email', e.target.value) } required />
|
tabIndex="1" onChange={ (e) => setData('email', e.target.value) } required />
|
||||||
<InputError message={ errors.email } className="mt-2" />
|
<InputError message={ errors.email } className="mt-2" />
|
||||||
</div>
|
</div>
|
||||||
<div className="grid gap-2">
|
<div className="grid gap-2">
|
||||||
@@ -57,26 +58,23 @@ export default function Login({ status, canResetPassword }) {
|
|||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<TextInput id="password" type="password" name="password" value={ data.password }
|
<TextInput id="password" type="password" name="password" value={ data.password }
|
||||||
autoComplete="current-password"
|
autoComplete="current-password" tabIndex="2"
|
||||||
onChange={ (e) => setData('password', e.target.value) } />
|
onChange={ (e) => setData('password', e.target.value) } />
|
||||||
<InputError message={ errors.password } className="mt-2" />
|
<InputError message={ errors.password } className="mt-2" />
|
||||||
</div>
|
</div>
|
||||||
<div className="grid gap-2">
|
<div className="grid gap-2">
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
<Checkbox
|
<Checkbox id="remember" tabIndex="3" name="remember" checked={ data.remember }
|
||||||
id="remember"
|
onChange={ (e) => setData('remember', e.target.checked) } />
|
||||||
name="remember"
|
|
||||||
checked={ data.remember }
|
|
||||||
onChange={ (e) =>
|
|
||||||
setData('remember', e.target.checked)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<label htmlFor="remember"
|
<label htmlFor="remember"
|
||||||
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70">Remember me</label>
|
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70">
|
||||||
|
Remember me
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<PrimaryButton type="submit" disabled={ processing }
|
<PrimaryButton type="submit" disabled={ processing } tabIndex="4" className="w-full">
|
||||||
className="w-full">Login</PrimaryButton>
|
Login
|
||||||
|
</PrimaryButton>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-4 text-center text-sm">
|
<div className="mt-4 text-center text-sm">
|
||||||
Don't have an account?
|
Don't have an account?
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { Head, Link, router } from '@inertiajs/react';
|
import { Head, Link, router } from '@inertiajs/react';
|
||||||
import { Plus, Star, ArrowDownNarrowWide, ArrowUpNarrowWide } from 'lucide-react';
|
import { Plus, Star, ArrowDownNarrowWide, ArrowUpNarrowWide, ChevronsLeft, ChevronsRight } from 'lucide-react';
|
||||||
|
|
||||||
import AppLayout from '@/Layouts/AppLayout.jsx';
|
import AppLayout from '@/Layouts/AppLayout.jsx';
|
||||||
|
|
||||||
@@ -12,7 +12,7 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
|||||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
|
||||||
import { useToast } from '@/hooks/use-toast';
|
import { useToast } from '@/hooks/use-toast';
|
||||||
|
|
||||||
export default function Chapters({ auth, comic, chapters, histories }) {
|
export default function Chapters({ auth, comic, chapters, histories, offset }) {
|
||||||
|
|
||||||
const [group, setGroup] = useState('default');
|
const [group, setGroup] = useState('default');
|
||||||
const [favourites, setFavourites] = useState(auth.user.favourites);
|
const [favourites, setFavourites] = useState(auth.user.favourites);
|
||||||
@@ -113,11 +113,13 @@ export default function Chapters({ auth, comic, chapters, histories }) {
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td className="text-right pr-3">Authors</td>
|
<td className="text-right pr-3">Authors</td>
|
||||||
<td>{ comic.comic.author.map(a => (
|
<td>
|
||||||
|
{ comic.comic.author.map(a => (
|
||||||
<Badge key={ a.path_word } className="m-2" variant="outline">
|
<Badge key={ a.path_word } className="m-2" variant="outline">
|
||||||
<Link href={ route('comics.author', [a.path_word]) }>{ a.name }</Link>
|
<Link href={ route('comics.author', [a.path_word]) }>{ a.name }</Link>
|
||||||
</Badge>
|
</Badge>
|
||||||
) ) }</td>
|
) ) }
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td className="text-right pr-3">Description</td>
|
<td className="text-right pr-3">Description</td>
|
||||||
@@ -125,7 +127,11 @@ export default function Chapters({ auth, comic, chapters, histories }) {
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td className="text-right pr-3">Updated At</td>
|
<td className="text-right pr-3">Updated At</td>
|
||||||
<td>{ comic.comic.datetime_updated }</td>
|
<td>
|
||||||
|
<Link href={ route('comics.read', [comic.comic.path_word, comic.comic.last_chapter.uuid])}>
|
||||||
|
{ comic.comic.datetime_updated } - { comic.comic.last_chapter.name }
|
||||||
|
</Link>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
@@ -151,9 +157,23 @@ export default function Chapters({ auth, comic, chapters, histories }) {
|
|||||||
</div>
|
</div>
|
||||||
<TabsContent value={ group }>
|
<TabsContent value={ group }>
|
||||||
<div className="w-full grid grid-cols-1 md:grid-cols-2 lg:grid-cols-6 xl:grid-cols-12 gap-1">
|
<div className="w-full grid grid-cols-1 md:grid-cols-2 lg:grid-cols-6 xl:grid-cols-12 gap-1">
|
||||||
|
{ (chapters.total > chapters.limit && chapters.offset > 0) && (
|
||||||
|
<Button size="sm" variant="outline" asChild>
|
||||||
|
<Link href="?" only={['chapters', 'offset']} headers={{ offset: parseInt(chapters.offset) - chapters.limit }}>
|
||||||
|
<ChevronsLeft /> Prev
|
||||||
|
</Link>
|
||||||
|
</Button>
|
||||||
|
) }
|
||||||
{ chapters.list.sort((a, b) => ascending ? (a.index - b.index) : (b.index - a.index)).map(c => (
|
{ chapters.list.sort((a, b) => ascending ? (a.index - b.index) : (b.index - a.index)).map(c => (
|
||||||
<ComicChapterLink key={ c.uuid } { ...c } />
|
<ComicChapterLink key={ c.uuid } { ...c } />
|
||||||
) ) }
|
) ) }
|
||||||
|
{ (chapters.total > chapters.limit && chapters.offset === 0) && (
|
||||||
|
<Button size="sm" variant="outline" asChild>
|
||||||
|
<Link href="?" only={['chapters', 'offset']} headers={{ offset: parseInt(chapters.offset) + chapters.limit }}>
|
||||||
|
Next <ChevronsRight />
|
||||||
|
</Link>
|
||||||
|
</Button>
|
||||||
|
) }
|
||||||
</div>
|
</div>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ export default function Histories({ auth, histories }) {
|
|||||||
{ h.comic.name }
|
{ h.comic.name }
|
||||||
</Link>
|
</Link>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className="hidden lg:block">{ datetimeConversion(h.created_at) }</TableCell>
|
<TableCell className="hidden lg:block">{ datetimeConversion(h.read_at) }</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
)) }
|
)) }
|
||||||
</TableBody>
|
</TableBody>
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ export default function Index({ comics, offset, auth }) {
|
|||||||
<CardContent>
|
<CardContent>
|
||||||
<CardTitle><Link href={ `/comic/${ props.path_word }` }>{ props.name }</Link></CardTitle>
|
<CardTitle><Link href={ `/comic/${ props.path_word }` }>{ props.name }</Link></CardTitle>
|
||||||
<CardDescription className="pt-2">
|
<CardDescription className="pt-2">
|
||||||
{ props.author.map(a => (
|
{ props.author && props.author.map(a => (
|
||||||
<Badge className="m-1" key={ a.path_word } variant="outline">
|
<Badge className="m-1" key={ a.path_word } variant="outline">
|
||||||
{ a.name }
|
{ a.name }
|
||||||
</Badge>)
|
</Badge>)
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ export default function Read({ auth, comic, chapter }) {
|
|||||||
} else if (divDimensions[0] > divDimensions[1] && readingMode === 'utd') {
|
} else if (divDimensions[0] > divDimensions[1] && readingMode === 'utd') {
|
||||||
imgStyles = { width: '50%' };
|
imgStyles = { width: '50%' };
|
||||||
} else if (readingMode === 'rtl') {
|
} else if (readingMode === 'rtl') {
|
||||||
imgStyles = { height: 'calc(100dvh - 90px)' };
|
imgStyles = { width: '100%', height: 'calc(100dvh - 90px)', objectFit: 'contain' };
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleImageClick = (e) => {
|
const handleImageClick = (e) => {
|
||||||
@@ -156,6 +156,10 @@ export default function Read({ auth, comic, chapter }) {
|
|||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
|
<Button variant="ghost">
|
||||||
|
{ currentImage } / { chapter.sorted.length }
|
||||||
|
</Button>
|
||||||
|
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger>
|
<TooltipTrigger>
|
||||||
@@ -204,10 +208,6 @@ export default function Read({ auth, comic, chapter }) {
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
) }
|
) }
|
||||||
|
|
||||||
<Button variant="ghost">
|
|
||||||
{ currentImage } / { chapter.sorted.length }
|
|
||||||
</Button>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { Link, router, usePage } from '@inertiajs/react';
|
import { Link, router, usePage } from '@inertiajs/react';
|
||||||
|
|
||||||
import { BadgeCheck, ChevronsUpDown, Star, History, ChevronDown, LogOut, Search, Book } from 'lucide-react';
|
import { BadgeCheck, ChevronsUpDown, Star, History, ChevronDown, LogOut, Search, Book, TableOfContents } from 'lucide-react';
|
||||||
|
|
||||||
import { Avatar, AvatarFallback } from '@/components/ui/avatar';
|
import { Avatar, AvatarFallback } from '@/components/ui/avatar';
|
||||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible';
|
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible';
|
||||||
@@ -57,7 +57,7 @@ export function AppSidebar({ auth }) {
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-0.5 leading-none">
|
<div className="flex flex-col gap-0.5 leading-none">
|
||||||
<span className="font-semibold">Comic</span>
|
<span className="font-semibold">Comic</span>
|
||||||
<span>0.0.0</span>
|
<span>0.0.1</span>
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
</SidebarMenuButton>
|
</SidebarMenuButton>
|
||||||
@@ -73,6 +73,21 @@ export function AppSidebar({ auth }) {
|
|||||||
</SidebarGroupContent>
|
</SidebarGroupContent>
|
||||||
</SidebarGroup>
|
</SidebarGroup>
|
||||||
</SidebarHeader>
|
</SidebarHeader>
|
||||||
|
<SidebarGroup>
|
||||||
|
<SidebarGroupLabel>Others</SidebarGroupLabel>
|
||||||
|
<SidebarGroupContent>
|
||||||
|
<SidebarMenu>
|
||||||
|
<SidebarMenuItem>
|
||||||
|
<SidebarMenuButton asChild>
|
||||||
|
<Link href={ route('comics.index')}>
|
||||||
|
<TableOfContents />
|
||||||
|
<span>Manual</span>
|
||||||
|
</Link>
|
||||||
|
</SidebarMenuButton>
|
||||||
|
</SidebarMenuItem>
|
||||||
|
</SidebarMenu>
|
||||||
|
</SidebarGroupContent>
|
||||||
|
</SidebarGroup>
|
||||||
<Collapsible defaultOpen className="group/collapsible">
|
<Collapsible defaultOpen className="group/collapsible">
|
||||||
<SidebarGroup>
|
<SidebarGroup>
|
||||||
<SidebarGroupLabel asChild>
|
<SidebarGroupLabel asChild>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
|
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=0, minimal-ui">
|
||||||
<title inertia>{{ config('app.name', 'Laravel') }}</title>
|
<title inertia>{{ config('app.name', 'Laravel') }}</title>
|
||||||
@routes
|
@routes
|
||||||
@viteReactRefresh
|
@viteReactRefresh
|
||||||
|
|||||||
Reference in New Issue
Block a user