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

Added Movie Database DBpedia example #103

Merged
merged 2 commits into from
Jan 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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: 0 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,3 @@ node_modules

package-lock.json
ontologies

next-movie-database
react-movie-database
3 changes: 3 additions & 0 deletions examples/next-movie-database/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": ["next", "next/core-web-vitals", "prettier"]
}
36 changes: 36 additions & 0 deletions examples/next-movie-database/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js
.yarn/install-state.gz

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# local env files
.env*.local

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts
5 changes: 5 additions & 0 deletions examples/next-movie-database/.vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.tabSize": 2
}
20 changes: 20 additions & 0 deletions examples/next-movie-database/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Linked Data Movie Database

This is a IMDb-like movie database based on Linked Data and DBpedia.

Build with [LDkit](https://ldkit.io), React, TailwindCSS and Next.js.

## Getting Started

1. Install dependencies
```bash
npm install
```

2. Run the development server:

```bash
npm run dev
```

Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
Binary file added examples/next-movie-database/app/favicon.ico
Binary file not shown.
21 changes: 21 additions & 0 deletions examples/next-movie-database/app/globals.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

:root {
--foreground-rgb: 0, 0, 0;
--background-start-rgb: 255, 255, 255;
--background-end-rgb: 255, 255, 255;
}

@media (prefers-color-scheme: dark) {
:root {
--foreground-rgb: 255, 255, 255;
--background-start-rgb: 0, 0, 0;
--background-end-rgb: 0, 0, 0;
}
}

body {
color: rgb(var(--foreground-rgb));
}
27 changes: 27 additions & 0 deletions examples/next-movie-database/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import type { Metadata } from "next";
import "./globals.css";
import { Header } from "@/components/Header";
import { Footer } from "@/components/Footer";

export const metadata: Metadata = {
title: "Linked Data Movie Database",
description: "Browse and search movies and actors from Wikidata",
};

export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en" className="bg-black min-h-screen">
<body className="bg-white">
<Header />
<main className="container max-w-6xl mx-auto px-8 py-8 min-h-[80vh]">
{children}
</main>
<Footer />
</body>
</html>
);
}
108 changes: 108 additions & 0 deletions examples/next-movie-database/app/movie/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import { Suspense } from "react";

import {
Movies,
MoviesActors,
MoviesDirectors,
MoviesWriters,
MoviesComposers,
} from "@/data/lens";
import { Thumbnail } from "@/components/Thumbnail";
import { Links } from "@/components/Links";
import { Loading } from "@/components/Loading";

export default async function MoviePage({
searchParams,
}: {
searchParams: Record<string, string>;
}) {
const { iri } = searchParams;

if (!iri || iri.length < 1) {
return <h1>No IRI found!</h1>;
}

const movie = await Movies.findOne({
$id: iri,
name: {
$langMatches: "en",
},
abstract: {
$langMatches: "en",
},
});

if (movie == null) {
return <h1>No movie found!</h1>;
}

return (
<div className="grid grid-cols-4 gap-4">
<article className="prose max-w-full pr-8 col-span-3">
<h1>{movie.name}</h1>
<p>{movie.abstract}</p>
<h2>Director</h2>

<Suspense fallback={<Loading />}>
<Director iri={movie.$id} />
</Suspense>
<h2>Cast</h2>
<Suspense fallback={<Loading />}>
<Cast iri={movie.$id} />
</Suspense>
<h2>Writer</h2>
<Suspense fallback={<Loading />}>
<Writer iri={movie.$id} />
</Suspense>
<h2>Composer</h2>
<Suspense fallback={<Loading />}>
<Composer iri={movie.$id} />
</Suspense>
</article>
<aside className="col-span-1 prose">
<Thumbnail imageUrl={movie.thumbnail} />
<Links iri={movie.$id} />
</aside>
</div>
);
}

async function Director({ iri }: { iri: string }) {
const movieWithDirectors = await MoviesDirectors.findByIri(iri);
return <PersonList>{movieWithDirectors?.directors}</PersonList>;
}

async function Writer({ iri }: { iri: string }) {
const movieWithWriters = await MoviesWriters.findByIri(iri);
return <PersonList>{movieWithWriters?.writers}</PersonList>;
}

async function Composer({ iri }: { iri: string }) {
const movieWithComposers = await MoviesComposers.findByIri(iri);
return <PersonList>{movieWithComposers?.composers}</PersonList>;
}

async function Cast({ iri }: { iri: string }) {
const movieWithActors = await MoviesActors.findByIri(iri);
return <PersonList>{movieWithActors?.starring}</PersonList>;
}

function PersonList({
children,
}: {
children: { $id: string; name: string }[] | undefined;
}) {
if (children === undefined || children.length < 1) {
return <p>No records found.</p>;
}

return (
<ul>
{children.map((person) => (
<li key={person.$id}>
<a href={`/person?iri=${person.$id}`}>{person.name}</a>
</li>
))}
</ul>
);
}
127 changes: 127 additions & 0 deletions examples/next-movie-database/app/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import { Loading } from "@/components/Loading";
import { Search, SearchInterface } from "@/data/lens";
import { yago } from "@/data/namespaces";
import { schema } from "ldkit/namespaces";
import { Suspense } from "react";

export default function Home() {
return (
<>
<p className="text-center text-lg pb-8 pt-2">
Use the search bar above to look for people or movies, or pick some
selection below.
</p>
<div className="grid grid-cols-3">
<section className="prose">
<h1>Top Movies</h1>
<Suspense fallback={<Loading />}>
<TopMovies />
</Suspense>
</section>
<section className="prose">
<h1>Top Actors</h1>
<Suspense fallback={<Loading />}>
<TopActors />
</Suspense>
</section>
<section className="prose">
<h1>Top Directors</h1>
<Suspense fallback={<Loading />}>
<TopDirectors />
</Suspense>
</section>
</div>
</>
);
}

async function TopMovies() {
const movies = await search(
[
"http://dbpedia.org/resource/The_Shawshank_Redemption",
"http://dbpedia.org/resource/The_Godfather",
"http://dbpedia.org/resource/The_Dark_Knight",
"http://dbpedia.org/resource/Pulp_Fiction",
"http://dbpedia.org/resource/Schindler's_List",
"http://dbpedia.org/resource/The_Matrix",
"http://dbpedia.org/resource/Fight_Club",
"http://dbpedia.org/resource/Goodfellas",
"http://dbpedia.org/resource/Terminator_2:_Judgment_Day",
"http://dbpedia.org/resource/Inception",
],
schema.Movie
);
return <SearchResults items={movies} />;
}

async function TopActors() {
const movies = await search(
[
"http://dbpedia.org/resource/Christian_Bale",
"http://dbpedia.org/resource/Helena_Bonham_Carter",
"http://dbpedia.org/resource/Robert_De_Niro",
"http://dbpedia.org/resource/Al_Pacino",
"http://dbpedia.org/resource/Tom_Hanks",
"http://dbpedia.org/resource/Leonardo_DiCaprio",
"http://dbpedia.org/resource/Meryl_Streep",
"http://dbpedia.org/resource/Brad_Pitt",
"http://dbpedia.org/resource/Tom_Cruise",
"http://dbpedia.org/resource/Sigourney_Weaver",
],
yago.Actor109765278
);
return <SearchResults items={movies} />;
}

async function TopDirectors() {
const movies = await search(
[
"http://dbpedia.org/resource/Stanley_Kubrick",
"http://dbpedia.org/resource/Quentin_Tarantino",
"http://dbpedia.org/resource/Christopher_Nolan",
"http://dbpedia.org/resource/Ridley_Scott",
"http://dbpedia.org/resource/Steven_Spielberg",
"http://dbpedia.org/resource/Martin_Scorsese",
"http://dbpedia.org/resource/Alfred_Hitchcock",
"http://dbpedia.org/resource/David_Fincher",
"http://dbpedia.org/resource/Tim_Burton",
"http://dbpedia.org/resource/Guillermo_del_Toro",
],
yago.Director110014939
);
return <SearchResults items={movies} />;
}

function SearchResults({ items }: { items: SearchInterface[] }) {
return (
<ul>
{items.map((x) => (
<SearchResult key={x.$id} {...x} />
))}
</ul>
);
}

function SearchResult(props: SearchInterface) {
const mode = props.types.includes(schema.Movie) ? "movie" : "person";

return (
<li>
<a href={`/${mode}?iri=${props.$id}`}>{props.name}</a>
</li>
);
}

async function search(ids: string[], type: string) {
return Search.find({
where: {
$id: ids,
name: {
$langMatches: "en",
},
types: {
$in: [type],
},
},
});
}
Loading
Loading