Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add extensions browsing view #4

Merged
merged 24 commits into from
Nov 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
16e46a7
chore: Update .gitignore and README with installation instructions
LightInn Nov 6, 2024
cfad7b7
feat: Update component imports and add global styles
LightInn Nov 6, 2024
36270a7
feat: Update Browse page with Link component
LightInn Nov 6, 2024
b994263
feat: Add ExtensionBrowse component and loader styles
LightInn Nov 6, 2024
f8aa402
feat: Add global background color
LightInn Nov 6, 2024
62bd9e9
feat: Update import path for App component
LightInn Nov 6, 2024
7fc7942
feat: Update import and add new route for MangaDetails
LightInn Nov 6, 2024
e810ba0
chore: Update dependencies versions
LightInn Nov 6, 2024
97e6391
feat: Add MangaImage component for displaying manga images
LightInn Nov 6, 2024
32bb118
feat: Add Source to Extension, Implement MangaImage component
LightInn Nov 6, 2024
7b3c531
feat: Add MangaDetails component for displaying manga details and list
LightInn Nov 6, 2024
3c0e2fb
feat: Update Browse component rendering logic
LightInn Nov 7, 2024
39308f3
feat: Update import paths for extensions and services
LightInn Nov 7, 2024
863f0b3
chore: Update dependencies versions
LightInn Nov 7, 2024
11b9cfb
feat: Update route paths in App component
LightInn Nov 7, 2024
239b99d
feat: Add memoization to getExtension function
LightInn Nov 7, 2024
113a476
fix: Update import paths in Extensions.tsx
LightInn Nov 7, 2024
397a138
feat: Improve fallback image handling
LightInn Nov 7, 2024
4d41d01
feat: Refactor Browse component for improved readability
LightInn Nov 7, 2024
dcf6d72
feat: Refactor ExtensionBrowse component for improved functionality
LightInn Nov 7, 2024
418b19a
feat: Refactor MangaDetails component for improved readability and da…
LightInn Nov 7, 2024
1190187
feat: Update return type of getMangaList function
LightInn Nov 7, 2024
e5ce2b0
fix: Removed unused import of MangaList
LightInn Nov 7, 2024
2728a74
feat: Improve component structure and readability
LightInn Nov 7, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,6 @@ dist/

# Zed Editor
.zed/

# intelij idea
.idea/
16 changes: 15 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,21 @@

## Development

I assume you have installed Rust, Cargo, Tauri and Deno.
I assume you have installed Rust, Cargo.

### Install Deno

```bash
curl -fsSL https://deno.land/x/install/install.sh | sh
```

### Then install Tauri CLI

```bash
cargo install tauri-cli --version "^2.0.0" --locked
```

### Run the app

```bash
cargo tauri dev
Expand Down
22 changes: 11 additions & 11 deletions deno.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"tasks": {
"dev": "deno run -A npm:vite dev",
"build": "deno run -A npm:vite build",
"check": "deno check src-www/",
"check": "deno check src-www/ --unstable-sloppy-imports",
"lint": "deno lint && deno fmt --check",
"format": "deno lint --fix && deno fmt"
},
Expand All @@ -20,16 +20,16 @@
"lib": ["dom", "deno.ns"]
},
"imports": {
"@tauri-apps/api": "npm:@tauri-apps/api@^2.0.2",
"@tauri-apps/plugin-store": "npm:@tauri-apps/plugin-store@^2.1.0",
"@types/react": "npm:@types/react@^18.3.11",
"@types/react-dom": "npm:@types/react-dom@^18.3.1",
"@types/react-router-dom": "npm:@types/react-router-dom@^5.3.3",
"@vitejs/plugin-react": "npm:@vitejs/plugin-react@^4.3.2",
"react": "npm:react@^18.3.1",
"react-dom": "npm:react-dom@^18.3.1",
"react-router-dom": "npm:react-router-dom@^6.27.0",
"vite": "npm:vite@^5.4.9"
"@tauri-apps/api": "npm:@tauri-apps/[email protected]",
"@tauri-apps/plugin-store": "npm:@tauri-apps/[email protected]",
"@types/react": "npm:@types/[email protected]",
"@types/react-dom": "npm:@types/[email protected]",
"@types/react-router-dom": "npm:@types/[email protected]",
"@vitejs/plugin-react": "npm:@vitejs/[email protected]",
"react": "npm:[email protected]",
"react-dom": "npm:[email protected]",
"react-router-dom": "npm:[email protected]",
"vite": "npm:[email protected]"
},
"nodeModulesDir": "auto"
}
290 changes: 112 additions & 178 deletions deno.lock

Large diffs are not rendered by default.

22 changes: 17 additions & 5 deletions src-www/App.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,28 @@
import { createBrowserRouter, RouterProvider } from "react-router-dom";

import Browse from "./pages/Browse.tsx";
import NavBar from "./components/NavBar.tsx";
import NavItem from "./components/NavItem.tsx";
import Extensions from "./pages/Extensions.tsx";
import More from "./pages/More.tsx";
import Browse from "./pages/Browse/Browse";
import ExtensionBrowser from "./pages/Browse/ExtensionBrowse";
import NavBar from "./components/NavBar";
import NavItem from "./components/NavItem";
import Extensions from "./pages/Extensions";
import More from "./pages/More";
import MangaDetails from "./pages/MangaDetails";

import "./style/global.css";

const router = createBrowserRouter([
{
path: "/",
element: <Browse />,
},
{
path: "/browse/:extensionId",
element: <ExtensionBrowser />,
},
{
path: "/browse/:extensionId/:mangaId",
element: <MangaDetails />,
},
{
path: "/extensions",
element: <Extensions />,
Expand Down
24 changes: 24 additions & 0 deletions src-www/components/MangaImage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
function MangaImage(
{ src, alt, fallBackSrc = "/fallback-img.jpg" }: {
src: string;
alt: string;
fallBackSrc?: string;
},
) {
return (
<img
src={src}
alt={alt}
style={{
width: "100%",
height: "280px",
objectFit: "cover",
borderRadius: "8px",
boxShadow: "0 4px 8px rgba(0, 0, 0, 0.1)",
}}
onError={(e) => (e.currentTarget.src = fallBackSrc)}
/>
);
}

export default MangaImage;
2 changes: 1 addition & 1 deletion src-www/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import ReactDOM from "react-dom/client";

import App from "./App.tsx";
import App from "./App";

ReactDOM.createRoot(document.getElementById("root")!).render(<App />);
56 changes: 0 additions & 56 deletions src-www/pages/Browse.tsx

This file was deleted.

55 changes: 55 additions & 0 deletions src-www/pages/Browse/Browse.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { useEffect, useState } from "react";

import { Extension } from "../../types/extension";
import { getIconUrl } from "../../services/extensions.service";
import { getExtensions } from "../../services/tauri.service";
import { Link } from "react-router-dom";

export default function Browse() {
const [extensions, setExtensions] = useState<Extension[]>([]);

useEffect(() => {
getExtensions().then(setExtensions);
}, []);

return (
<ul
style={{
display: "flex",
flexDirection: "column",
gap: 8,
padding: "0 2rem",
}}
>
{extensions.map((extension: Extension) => {
return (
<li
key={extension.id}
style={{ display: "flex", alignItems: "center", gap: 12 }}
>
<img
src={getIconUrl(extension.iconPath)}
style={{ width: 48, height: 48 }}
/>
<div>
<span style={{ display: "block", fontSize: 16 }}>
{extension.source.name}
</span>
<div style={{ opacity: 0.7, fontSize: 14 }}>
{extension.source.language}
</div>
</div>
<Link
to={`/browse/${extension.id}`}
style={{
marginLeft: "auto",
}}
>
Browse
</Link>
</li>
);
})}
</ul>
);
}
150 changes: 150 additions & 0 deletions src-www/pages/Browse/ExtensionBrowse.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import { useEffect, useState } from "react";
import { Extension } from "../../types/extension";
import { Manga } from "../../types/manga";
import { Link, useParams } from "react-router-dom";
import {
getExtension,
getIconUrl,
getMangaList,
} from "../../services/extensions.service";
import "../../style/loader.css";
import MangaImage from "../../components/MangaImage";

export default function ExtensionBrowse() {
const { extensionId } = useParams();
const [extension, setExtension] = useState<Extension | null>(null);
const [mangas, setMangas] = useState<Array<Manga>>([]);
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState<boolean>(true);
const [pagination, setPagination] = useState<number>(1);

// Charger l'extension une seule fois au démarrage
useEffect(() => {
if (!extensionId) return;
getExtension(extensionId)
.then(setExtension)
.catch(() => setError("Erreur lors du chargement de l'extension."));
}, [extensionId]);

// Charger la liste de mangas pour chaque pagination
useEffect(() => {
if (!extension) return;

setLoading(true); // Activer le chargement pendant la requête
getMangaList(extension.id, [], pagination)
.then((data) => setMangas([...mangas, ...data[0]]))
.catch(() => setError("Erreur lors du chargement des mangas."))
.finally(() => setLoading(false));
}, [extension, pagination]);

// Gestion du scroll infini
useEffect(() => {
const handleScroll = () => {
if (
document.body.scrollHeight - 300 <
globalThis.scrollY + globalThis.innerHeight
) {
if (!loading) {
setPagination(pagination + 1);
}
}
};
globalThis.addEventListener("scroll", handleScroll);
return () => globalThis.removeEventListener("scroll", handleScroll);
}, [loading]);

// Afficher un message d'erreur si nécessaire
if (error) return <ErrorMessage error={error} />;

// Si l'extension est encore en chargement
if (!extension) return <Loader />;

return (
<div style={{ padding: "0 2rem" }}>
<ExtensionHeader extension={extension} />
<MangaGrid mangas={mangas} extensionId={extension.id} />
{loading && <Loader />}
</div>
);
}

// Composant de message d'erreur
const ErrorMessage = ({ error }: { error: string }) => (
<div style={{ padding: "0 2rem" }}>
<p style={{ color: "red" }}>{error}</p>
</div>
);

// Composant de loader
const Loader = () => (
<div
style={{
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
}}
>
<div style={{ textAlign: "center", padding: "20px 0" }}>Chargement...</div>
<div className="loader"></div>
</div>
);

// En-tête de l'extension avec le nom et l'icône
const ExtensionHeader = ({ extension }: { extension: Extension }) => (
<div
style={{ display: "flex", alignItems: "center", gap: 12, marginBottom: 20 }}
>
<img
src={getIconUrl(extension.iconPath)}
alt={extension.source.name}
style={{ width: 48, height: 48 }}
/>
<h2 style={{ fontSize: 24 }}>{extension.source.name}</h2>
</div>
);

// Grille de mangas
const MangaGrid = (
{ mangas, extensionId }: { mangas: Array<Manga>; extensionId: string },
) => (
<ul
style={{
display: "grid",
gridTemplateColumns: "repeat(auto-fill, minmax(180px, 5fr))",
gap: "16px",
listStyle: "none",
padding: 0,
}}
>
{mangas.map((manga) => (
<li key={manga.id} style={{ textAlign: "center" }}>
<Link
to={{ pathname: `/browse/${extensionId}/${manga.id}` }}
state={manga}
>
<MangaImage src={manga.coverUrl} alt={manga.title} />
</Link>
<a
href={manga.url}
target="_blank"
rel="noopener noreferrer"
style={{ textDecoration: "none", color: "#333" }}
>
<p
style={{
marginTop: 8,
fontSize: 14,
fontWeight: "bold",
overflow: "hidden",
textOverflow: "ellipsis",
whiteSpace: "nowrap",
}}
>
{manga.title}
</p>
</a>
</li>
))}
</ul>
);
Loading