diff options
| author | Bobby <[email protected]> | 2024-09-02 20:57:32 -0400 |
|---|---|---|
| committer | Bobby <[email protected]> | 2024-09-02 20:57:32 -0400 |
| commit | 763cc96fa2f38b2bf3109b036ba58320485c7de2 (patch) | |
| tree | 4b370f28c2f32aef4e2d4ea18ffd56c0a353387b | |
| parent | 773af5c376e2bef6fad28398b5add3c022c1dbf5 (diff) | |
| download | yugen-763cc96fa2f38b2bf3109b036ba58320485c7de2.tar.xz yugen-763cc96fa2f38b2bf3109b036ba58320485c7de2.zip | |
detail page + upcoming fix
| -rw-r--r-- | detail/__init__.py | 0 | ||||
| -rw-r--r-- | detail/admin.py | 3 | ||||
| -rw-r--r-- | detail/apps.py | 6 | ||||
| -rw-r--r-- | detail/migrations/__init__.py | 0 | ||||
| -rw-r--r-- | detail/models.py | 3 | ||||
| -rw-r--r-- | detail/tests.py | 3 | ||||
| -rw-r--r-- | detail/urls.py | 8 | ||||
| -rw-r--r-- | detail/views.py | 41 | ||||
| -rw-r--r-- | static/css/main.css | 269 | ||||
| -rw-r--r-- | templates/detail/detail.html | 276 | ||||
| -rw-r--r-- | templates/home/index.html | 2 | ||||
| -rw-r--r-- | templates/partials/datacard_render.html | 2 | ||||
| -rw-r--r-- | templates/partials/datacardcompact_render.html | 2 | ||||
| -rw-r--r-- | templates/partials/datacardwide_render.html | 2 | ||||
| -rw-r--r-- | watch/utils.py | 16 | ||||
| -rw-r--r-- | watch/views.py | 7 | ||||
| -rw-r--r-- | yugen/settings.py | 4 | ||||
| -rw-r--r-- | yugen/urls.py | 1 |
18 files changed, 643 insertions, 2 deletions
diff --git a/detail/__init__.py b/detail/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/detail/__init__.py diff --git a/detail/admin.py b/detail/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/detail/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/detail/apps.py b/detail/apps.py new file mode 100644 index 0000000..c7f8bff --- /dev/null +++ b/detail/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class DetailConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "detail" diff --git a/detail/migrations/__init__.py b/detail/migrations/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/detail/migrations/__init__.py diff --git a/detail/models.py b/detail/models.py new file mode 100644 index 0000000..71a8362 --- /dev/null +++ b/detail/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/detail/tests.py b/detail/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/detail/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/detail/urls.py b/detail/urls.py new file mode 100644 index 0000000..ca85152 --- /dev/null +++ b/detail/urls.py @@ -0,0 +1,8 @@ +from django.urls import path + +from . import views + +app_name = "detail" +urlpatterns = [ + path('<int:anime_id>/', views.detail, name='detail'), +] diff --git a/detail/views.py b/detail/views.py new file mode 100644 index 0000000..da67f3f --- /dev/null +++ b/detail/views.py @@ -0,0 +1,41 @@ +import json +import os +from django.shortcuts import render +import requests + +from watch.utils import get_all_episode_metadata, get_from_redis_cache, store_in_redis_cache + +def detail(request, anime_id): + anime_data = None + anime_episodes = None + anime_episodes_metadata = None + try: + anime_data = json.loads(get_from_redis_cache(f"anime_{anime_id}_anime_data")) + except: + base_url = f"{os.getenv("CONSUMET_URL")}/meta/anilist/info/{anime_id}?provider=zoro" + response = requests.get(base_url) + anime_data = response.json() + store_in_redis_cache(f"anime_{anime_id}_anime_data", json.dumps(anime_data)) + + try: + anime_episodes = json.loads(get_from_redis_cache(f"anime_{anime_id}_anime_episodes")) + except: + z_anime_id = anime_data["episodes"][0]["id"].split("$")[0] if len(anime_data["episodes"]) > 0 else None + if z_anime_id is not None: + base_url = f"{os.getenv("ZORO_URL")}/anime/episodes/{z_anime_id}" + response = requests.get(base_url) + anime_episodes = response.json() + store_in_redis_cache(f"anime_{anime_id}_anime_episodes", json.dumps(anime_episodes)) + + if anime_episodes is not None: + anime_episodes_metadata = get_all_episode_metadata(anime_data) + # attach metadata to episodes + for i, episode in enumerate(anime_episodes["episodes"]): + episode["metadata"] = anime_episodes_metadata[i] + + context = { + "anime": anime_data, + "episodes": anime_episodes, + } + + return render(request, "detail/detail.html", context)
\ No newline at end of file diff --git a/static/css/main.css b/static/css/main.css index a0ea458..28015bf 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -669,6 +669,46 @@ video { top: 100%; } +.top-48 { + top: 12rem; +} + +.top-16 { + top: 4rem; +} + +.top-32 { + top: 8rem; +} + +.top-36 { + top: 9rem; +} + +.left-8 { + left: 2rem; +} + +.left-\[18rem\] { + left: 18rem; +} + +.top-96 { + top: 24rem; +} + +.top-72 { + top: 18rem; +} + +.-top-64 { + top: -16rem; +} + +.top-64 { + top: 16rem; +} + .z-10 { z-index: 10; } @@ -701,6 +741,11 @@ video { margin-bottom: 2rem; } +.my-2 { + margin-top: 0.5rem; + margin-bottom: 0.5rem; +} + .mb-2 { margin-bottom: 0.5rem; } @@ -741,6 +786,22 @@ video { margin-top: 2rem; } +.mt-12 { + margin-top: 3rem; +} + +.mt-16 { + margin-top: 4rem; +} + +.mt-20 { + margin-top: 5rem; +} + +.mb-8 { + margin-bottom: 2rem; +} + .block { display: block; } @@ -757,6 +818,10 @@ video { display: inline-flex; } +.grid { + display: grid; +} + .hidden { display: none; } @@ -829,6 +894,14 @@ video { height: 100%; } +.h-32 { + height: 8rem; +} + +.h-28 { + height: 7rem; +} + .max-h-24 { max-height: 6rem; } @@ -898,10 +971,46 @@ video { width: max-content; } +.w-1\/6 { + width: 16.666667%; +} + +.w-1\/12 { + width: 8.333333%; +} + +.w-2\/12 { + width: 16.666667%; +} + +.w-10\/12 { + width: 83.333333%; +} + +.w-4\/12 { + width: 33.333333%; +} + +.w-8\/12 { + width: 66.666667%; +} + +.w-\[100vw\] { + width: 100vw; +} + .min-w-32 { min-width: 8rem; } +.min-w-\[100vw\] { + min-width: 100vw; +} + +.min-w-\[80vw\] { + min-width: 80vw; +} + .max-w-7xl { max-width: 80rem; } @@ -927,10 +1036,22 @@ video { max-width: max-content; } +.max-w-\[100vw\] { + max-width: 100vw; +} + +.max-w-\[80vw\] { + max-width: 80vw; +} + .flex-1 { flex: 1 1 0%; } +.flex-shrink-0 { + flex-shrink: 0; +} + .origin-left { transform-origin: left; } @@ -987,6 +1108,10 @@ video { list-style-position: inside; } +.grid-cols-1 { + grid-template-columns: repeat(1, minmax(0, 1fr)); +} + .flex-row { flex-direction: row; } @@ -1061,6 +1186,10 @@ video { overflow: hidden; } +.overflow-x-auto { + overflow-x: auto; +} + .overflow-y-auto { overflow-y: auto; } @@ -1390,6 +1519,11 @@ video { background-color: rgb(161 98 7 / var(--tw-bg-opacity)); } +.bg-gray-800 { + --tw-bg-opacity: 1; + background-color: rgb(31 41 55 / var(--tw-bg-opacity)); +} + .bg-opacity-10 { --tw-bg-opacity: 0.1; } @@ -1402,6 +1536,10 @@ video { --tw-bg-opacity: 0.4; } +.bg-opacity-30 { + --tw-bg-opacity: 0.3; +} + .bg-cover { background-size: cover; } @@ -1486,6 +1624,11 @@ video { padding-bottom: 2rem; } +.px-8 { + padding-left: 2rem; + padding-right: 2rem; +} + .pb-4 { padding-bottom: 1rem; } @@ -1510,6 +1653,10 @@ video { padding-top: 140%; } +.pb-2 { + padding-bottom: 0.5rem; +} + .text-center { text-align: center; } @@ -2160,6 +2307,10 @@ main { --tw-bg-opacity: 0.3; } +.hover\:bg-opacity-50:hover { + --tw-bg-opacity: 0.5; +} + .hover\:text-purple-600:hover { --tw-text-opacity: 1; color: rgb(147 51 234 / var(--tw-text-opacity)); @@ -2236,6 +2387,10 @@ main { } @media (min-width: 640px) { + .sm\:top-96 { + top: 24rem; + } + .sm\:size-3 { width: 0.75rem; height: 0.75rem; @@ -2246,10 +2401,26 @@ main { height: 1rem; } + .sm\:max-w-full { + max-width: 100%; + } + + .sm\:grid-cols-2 { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + .sm\:gap-2 { gap: 0.5rem; } + .sm\:overflow-scroll { + overflow: scroll; + } + + .sm\:overflow-x-scroll { + overflow-x: scroll; + } + .sm\:p-2 { padding: 0.5rem; } @@ -2259,6 +2430,16 @@ main { padding-right: 1.5rem; } + .sm\:px-4 { + padding-left: 1rem; + padding-right: 1rem; + } + + .sm\:py-2 { + padding-top: 0.5rem; + padding-bottom: 0.5rem; + } + .sm\:text-sm { font-size: 0.875rem; line-height: 1.25rem; @@ -2281,6 +2462,43 @@ main { } @media (min-width: 1024px) { + .lg\:top-48 { + top: 12rem; + } + + .lg\:top-56 { + top: 14rem; + } + + .lg\:bottom-12 { + bottom: 3rem; + } + + .lg\:bottom-0 { + bottom: 0px; + } + + .lg\:top-96 { + top: 24rem; + } + + .lg\:top-64 { + top: 16rem; + } + + .lg\:left-\[18rem\] { + left: 18rem; + } + + .lg\:top-0 { + top: 0px; + } + + .lg\:my-4 { + margin-top: 1rem; + margin-bottom: 1rem; + } + .lg\:block { display: block; } @@ -2297,6 +2515,10 @@ main { height: 39vw; } + .lg\:h-96 { + height: 24rem; + } + .lg\:max-h-\[761px\] { max-height: 761px; } @@ -2321,10 +2543,40 @@ main { width: 15rem; } + .lg\:w-10\/12 { + width: 83.333333%; + } + + .lg\:w-2\/12 { + width: 16.666667%; + } + + .lg\:w-full { + width: 100%; + } + + .lg\:w-fit { + width: -moz-fit-content; + width: fit-content; + } + + .lg\:max-w-fit { + max-width: -moz-fit-content; + max-width: fit-content; + } + + .lg\:grid-cols-3 { + grid-template-columns: repeat(3, minmax(0, 1fr)); + } + .lg\:flex-row { flex-direction: row; } + .lg\:flex-col { + flex-direction: column; + } + .lg\:flex-nowrap { flex-wrap: nowrap; } @@ -2341,6 +2593,10 @@ main { justify-content: flex-end; } + .lg\:gap-4 { + gap: 1rem; + } + .lg\:bg-white { --tw-bg-opacity: 1; background-color: rgb(255 255 255 / var(--tw-bg-opacity)); @@ -2363,6 +2619,15 @@ main { padding-left: 2rem; padding-right: 2rem; } + + .lg\:pb-0 { + padding-bottom: 0px; + } + + .lg\:text-4xl { + font-size: 2.25rem; + line-height: 2.5rem; + } } @media (min-width: 1280px) { @@ -2386,6 +2651,10 @@ main { width: 24rem; } + .xl\:grid-cols-4 { + grid-template-columns: repeat(4, minmax(0, 1fr)); + } + .xl\:flex-row { flex-direction: row; } diff --git a/templates/detail/detail.html b/templates/detail/detail.html new file mode 100644 index 0000000..360fd2c --- /dev/null +++ b/templates/detail/detail.html @@ -0,0 +1,276 @@ +{% extends "partials/base.html" %} +{% load custom_filters %} + +{% block content %} +<div style="background-image: url('{{ anime.cover }}')" class="relative bg-center bg-cover h-32 lg:h-96 my-4 rounded-lg" > + <div class="absolute inset-0" style="background: linear-gradient(45deg, rgb(8, 8, 8) 15%, transparent 60%), linear-gradient(0deg, rgb(8, 8, 8) 0%, transparent 60%);"></div> + <div class="w-4/12 lg:w-2/12 absolute px-2 lg:px-8 top-16 lg:top-48"> + <img src="{{ anime.image }}" alt="{{ anime.title.english }}" class="rounded-lg" class="w-full h-full rounded-lg object-cover"/> + <a href="{% url "watch:watch" anime.id %}?forward=detail" class="bg-{{ user.preferences.accent_colour }}-600 hover:bg-{{ user.preferences.accent_colour }}-700 text-white text-xs sm:text-sm font-bold py-1 px-2 sm:py-2 sm:px-4 rounded-lg my-2 flex flex-row items-center justify-center gap-1"> + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="size-4"> + <path fill-rule="evenodd" d="M4.5 5.653c0-1.427 1.529-2.33 2.779-1.643l11.54 6.347c1.295.712 1.295 2.573 0 3.286L7.28 19.99c-1.25.687-2.779-.217-2.779-1.643V5.653Z" clip-rule="evenodd" /> + </svg> + <span>Watch Now</span> + </a> + <div class="flex flex-row gap-2 my-2"> + <a href="https://anilist.co/anime/{{ anime.id }}" target="_blank" class="text-xs font-bold bg-{{ user.preferences.accent_colour }}-400 bg-opacity-30 flex-1 justify-center flex hover:bg-opacity-50 rounded px-2 py-1"> + <svg stroke="currentColor" fill="currentColor" stroke-width="0" role="img" viewBox="0 0 24 24" height="1.5rem" width="1.5rem" xmlns="http://www.w3.org/2000/svg"><path d="M24 17.53v2.421c0 .71-.391 1.101-1.1 1.101h-5l-.057-.165L11.84 3.736c.106-.502.46-.788 1.053-.788h2.422c.71 0 1.1.391 1.1 1.1v12.38H22.9c.71 0 1.1.392 1.1 1.101zM11.034 2.947l6.337 18.104h-4.918l-1.052-3.131H6.019l-1.077 3.131H0L6.361 2.948h4.673zm-.66 10.96-1.69-5.014-1.541 5.015h3.23z"></path></svg> + </a> + <a href="https://myanimelist.net/anime/{{ anime.malId }}" target="_blank" class="ext-xs font-bold bg-{{ user.preferences.accent_colour }}-400 bg-opacity-30 flex-1 justify-center flex hover:bg-opacity-50 rounded px-2 py-1"> + <svg stroke="currentColor" fill="currentColor" stroke-width="0" role="img" viewBox="0 0 24 24" height="1.5rem" width="1.5rem" xmlns="http://www.w3.org/2000/svg"><path d="M8.273 7.247v8.423l-2.103-.003v-5.216l-2.03 2.404-1.989-2.458-.02 5.285H.001L0 7.247h2.203l1.865 2.545 2.015-2.546 2.19.001zm8.628 2.069l.025 6.335h-2.365l-.008-2.871h-2.8c.07.499.21 1.266.417 1.779.155.381.298.751.583 1.128l-1.705 1.125c-.349-.636-.622-1.337-.878-2.082a9.296 9.296 0 0 1-.507-2.179c-.085-.75-.097-1.471.107-2.212a3.908 3.908 0 0 1 1.161-1.866c.313-.293.749-.5 1.1-.687.351-.187.743-.264 1.107-.359a7.405 7.405 0 0 1 1.191-.183c.398-.034 1.107-.066 2.39-.028l.545 1.749H14.51c-.593.008-.878.001-1.341.209a2.236 2.236 0 0 0-1.278 1.92l2.663.033.038-1.81h2.309zm3.992-2.099v6.627l3.107.032-.43 1.775h-4.807V7.187l2.13.03z"></path></svg> + </a> + </div> + <div class="flex flex-row overflow-x-auto lg:flex-col gap-4 my-4 pb-2 lg:pb-0"> + <div class="whitespace-nowrap"> + <span class="font-bold">Format: </span>{{ anime.type }} + </div> + <div class="whitespace-nowrap"> + <span class="font-bold">Episodes: </span>{{ anime.totalEpisodes }} + </div> + <div class="whitespace-nowrap"> + <span class="font-bold">Year: </span>{{ anime.releaseDate }} + </div> + <div class="whitespace-nowrap"> + <span class="font-bold">Duration: </span>{{ anime.duration }} mins + </div> + <div class="whitespace-nowrap"> + <span class="font-bold">Status: </span>{{ anime.status }} + </div> + <div class="whitespace-nowrap capitalize"> + <span class="font-bold">Season: </span>{{ anime.season }} + </div> + <div class="whitespace-nowrap"> + <span class="font-bold">Rating: </span>{{ anime.rating }} / 100 + </div> + <div class="whitespace-nowrap"> + <span class="font-bold">Popularity: </span>{{ anime.popularity }} + </div> + <div class="whitespace-nowrap"> + <span class="font-bold">Country: </span>{{ anime.countryOfOrigin }} + </div> + <div class="whitespace-nowrap"> + <span class="font-bold">Studios: </span> + {% for studio in anime.studios %} + <span>{{ studio }}</span>{% if not forloop.last %}, {% endif %} + {% endfor %} + </div> + </div> + </div> + <div class="w-8/12 lg:w-10/12 absolute top-36 lg:top-48 right-0"> + <div class="inline-flex gap-1 sm:gap-2 flex-wrap"> + <span {% if anime.color %}style="background-color: {{ anime.color }}3a;"{% endif %} class="text-[10px] sm:text-xs font-bold bg-white bg-opacity-10 p-1 rounded flex items-center gap-1"> + <svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="size-3 sm:size-4"> + <path d="M3.604 7.197l7.138 -3.109a.96 .96 0 0 1 1.27 .527l4.924 11.902a1 1 0 0 1 -.514 1.304l-7.137 3.109a.96 .96 0 0 1 -1.271 -.527l-4.924 -11.903a1 1 0 0 1 .514 -1.304z"></path> + <path d="M15 4h1a1 1 0 0 1 1 1v3.5"></path> + <path d="M20 6c.264 .112 .52 .217 .768 .315a1 1 0 0 1 .53 1.311l-2.298 5.374"></path> + </svg> + {{ anime.type }} + </span> + <span {% if anime.color %}style="background-color: {{ anime.color }}3a;"{% endif %} class="text-[10px] sm:text-xs font-bold bg-white bg-opacity-10 p-1 rounded flex items-center gap-1"> + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="size-3 sm:size-4"> + <path fill-rule="evenodd" d="M6.75 2.25A.75.75 0 0 1 7.5 3v1.5h9V3A.75.75 0 0 1 18 3v1.5h.75a3 3 0 0 1 3 3v11.25a3 3 0 0 1-3 3H5.25a3 3 0 0 1-3-3V7.5a3 3 0 0 1 3-3H6V3a.75.75 0 0 1 .75-.75Zm13.5 9a1.5 1.5 0 0 0-1.5-1.5H5.25a1.5 1.5 0 0 0-1.5 1.5v7.5a1.5 1.5 0 0 0 1.5 1.5h13.5a1.5 1.5 0 0 0 1.5-1.5v-7.5Z" clip-rule="evenodd" /> + </svg> + {{ anime.releaseDate }} + </span> + <span {% if anime.color %}style="background-color: {{ anime.color }}3a;"{% endif %} class="text-[10px] sm:text-xs font-bold bg-white bg-opacity-10 p-1 rounded flex items-center gap-1"> + {% if anime.status == "Ongoing" %} + <span class="text-green-500"> + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="size-2 sm:size-3"> + <circle cx="12" cy="12" r="12" /> + </svg> + </span> + {% elif anime.status == "Not yet aired" %} + <span class="text-yellow-500"> + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="size-2 sm:size-3"> + <circle cx="12" cy="12" r="12" /> + </svg> + </span> + {% else %} + <span class="text-blue-500"> + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="size-2 sm:size-3"> + <circle cx="12" cy="12" r="12" /> + </svg> + </span> + {% endif %} + {{ anime.status }} + </span> + <span {% if anime.color %}style="background-color: {{ anime.color }}3a;"{% endif %} class="text-[10px] sm:text-xs font-bold bg-white bg-opacity-10 p-1 rounded flex items-center gap-1"> + <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-3 sm:size-4"> + <path stroke-linecap="round" stroke-linejoin="round" d="M11.48 3.499a.562.562 0 0 1 1.04 0l2.125 5.111a.563.563 0 0 0 .475.345l5.518.442c.499.04.701.663.321.988l-4.204 3.602a.563.563 0 0 0-.182.557l1.285 5.385a.562.562 0 0 1-.84.61l-4.725-2.885a.562.562 0 0 0-.586 0L6.982 20.54a.562.562 0 0 1-.84-.61l1.285-5.386a.562.562 0 0 0-.182-.557l-4.204-3.602a.562.562 0 0 1 .321-.988l5.518-.442a.563.563 0 0 0 .475-.345L11.48 3.5Z"/> + </svg> + {{ anime.rating }} + </span> + <span class="text-xs sm:text-sm font-bold flex gap-2 flex-row flex-wrap items-center"> + {% for genre in anime.genres %} + {% if genre == "Action" %} + <span class="text-xs font-bold bg-green-100 bg-opacity-20 text-green-300 py-1 px-2 rounded-full"> + {% elif genre == "Adventure" %} + <span class="text-xs font-bold bg-pink-100 bg-opacity-20 text-pink-300 py-1 px-2 rounded-full"> + {% elif genre == "Cars" %} + <span class="text-xs font-bold bg-orange-100 bg-opacity-20 text-orange-300 py-1 px-2 rounded-full"> + {% elif genre == "Comedy" %} + <span class="text-xs font-bold bg-purple-100 bg-opacity-20 text-purple-300 py-1 px-2 rounded-full"> + {% elif genre == "Drama" %} + <span class="text-xs font-bold bg-blue-100 bg-opacity-20 text-blue-300 py-1 px-2 rounded-full"> + {% elif genre == "Fantasy" %} + <span class="text-xs font-bold bg-yellow-100 bg-opacity-20 text-yellow-300 py-1 px-2 rounded-full"> + {% elif genre == "Horror" %} + <span class="text-xs font-bold bg-red-100 bg-opacity-20 text-red-300 py-1 px-2 rounded-full"> + {% elif genre == "Mahou Shoujo" %} + <span class="text-xs font-bold bg-teal-100 bg-opacity-20 text-teal-300 py-1 px-2 rounded-full"> + {% elif genre == "Mecha" %} + <span class="text-xs font-bold bg-indigo-100 bg-opacity-20 text-indigo-300 py-1 px-2 rounded-full"> + {% elif genre == "Music" %} + <span class="text-xs font-bold bg-pink-100 bg-opacity-20 text-pink-300 py-1 px-2 rounded-full"> + {% elif genre == "Mystery" %} + <span class="text-xs font-bold bg-purple-100 bg-opacity-20 text-purple-300 py-1 px-2 rounded-full"> + {% elif genre == "Psychological" %} + <span class="text-xs font-bold bg-blue-100 bg-opacity-20 text-blue-300 py-1 px-2 rounded-full"> + {% elif genre == "Romance" %} + <span class="text-xs font-bold bg-yellow-100 bg-opacity-20 text-yellow-300 py-1 px-2 rounded-full"> + {% elif genre == "Sci-Fi" %} + <span class="text-xs font-bold bg-red-100 bg-opacity-20 text-red-300 py-1 px-2 rounded-full"> + {% elif genre == "Slice of Life" %} + <span class="text-xs font-bold bg-teal-100 bg-opacity-20 text-teal-300 py-1 px-2 rounded-full"> + {% elif genre == "Sports" %} + <span class="text-xs font-bold bg-indigo-100 bg-opacity-20 text-indigo-300 py-1 px-2 rounded-full"> + {% elif genre == "Supernatural" %} + <span class="text-xs font-bold bg-green-100 bg-opacity-20 text-green-300 py-1 px-2 rounded-full"> + {% elif genre == "Thriller" %} + <span class="text-xs font-bold bg-orange-100 bg-opacity-20 text-orange-300 py-1 px-2 rounded-full"> + {% else %} + <span class="text-xs font-bold bg-white bg-opacity-20 text-white py-1 px-2 rounded-full"> + {% endif %} + {{ genre }} + </span> + {% endfor %} + </span> + </div> + <h2 class="text-2xl lg:text-4xl font-bold text-transparent bg-clip-text my-2" style="background: linear-gradient(-45deg, {% if anime.color %}{{ anime.color }}{% else %}white{% endif %}, white); -webkit-background-clip: text; background-clip: text;"> + {% if user.preferences.title_language == "english" and anime.title.english %} + {{ anime.title.english }} + {% elif user.preferences.title_language == "native" and anime.title.native %} + {{ anime.title.native }} + {% else %} + {{ anime.title.romaji }} + {% endif %} + </h2> + <p class="max-h-24 overflow-auto text-sm text-white mb-4 no-scrollbar"> + {{ anime.description|strip_html }} + </p> + </div> +</div> +<section class="w-full lg:w-10/12 flex flex-col items-center justify-center p-2 relative top-64 lg:top-0 lg:left-[18rem]"> + <section class="inline-flex w-max flex-row gap-4 rounded-full mb-8 bg-white bg-opacity-10 mx-auto"> + <button class="flex flex-row items-center focus:outline-none gap-2 text-sm font-bold py-2 px-4 rounded-full category-switch" data-target="characters"> + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="size-4"> + <path fill-rule="evenodd" d="M18.685 19.097A9.723 9.723 0 0 0 21.75 12c0-5.385-4.365-9.75-9.75-9.75S2.25 6.615 2.25 12a9.723 9.723 0 0 0 3.065 7.097A9.716 9.716 0 0 0 12 21.75a9.716 9.716 0 0 0 6.685-2.653Zm-12.54-1.285A7.486 7.486 0 0 1 12 15a7.486 7.486 0 0 1 5.855 2.812A8.224 8.224 0 0 1 12 20.25a8.224 8.224 0 0 1-5.855-2.438ZM15.75 9a3.75 3.75 0 1 1-7.5 0 3.75 3.75 0 0 1 7.5 0Z" clip-rule="evenodd" /> + </svg> + <span>Characters</span> + </button> + <button class="flex flex-row items-center focus:outline-none gap-2 text-sm font-bold py-2 px-4 rounded-full category-switch bg-{{ user.preferences.accent_colour }}-600" data-target="episodes"> + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="size-4"> + <path fill-rule="evenodd" d="M1.5 5.625c0-1.036.84-1.875 1.875-1.875h17.25c1.035 0 1.875.84 1.875 1.875v12.75c0 1.035-.84 1.875-1.875 1.875H3.375A1.875 1.875 0 0 1 1.5 18.375V5.625Zm1.5 0v1.5c0 .207.168.375.375.375h1.5a.375.375 0 0 0 .375-.375v-1.5a.375.375 0 0 0-.375-.375h-1.5A.375.375 0 0 0 3 5.625Zm16.125-.375a.375.375 0 0 0-.375.375v1.5c0 .207.168.375.375.375h1.5A.375.375 0 0 0 21 7.125v-1.5a.375.375 0 0 0-.375-.375h-1.5ZM21 9.375A.375.375 0 0 0 20.625 9h-1.5a.375.375 0 0 0-.375.375v1.5c0 .207.168.375.375.375h1.5a.375.375 0 0 0 .375-.375v-1.5Zm0 3.75a.375.375 0 0 0-.375-.375h-1.5a.375.375 0 0 0-.375.375v1.5c0 .207.168.375.375.375h1.5a.375.375 0 0 0 .375-.375v-1.5Zm0 3.75a.375.375 0 0 0-.375-.375h-1.5a.375.375 0 0 0-.375.375v1.5c0 .207.168.375.375.375h1.5a.375.375 0 0 0 .375-.375v-1.5ZM4.875 18.75a.375.375 0 0 0 .375-.375v-1.5a.375.375 0 0 0-.375-.375h-1.5a.375.375 0 0 0-.375.375v1.5c0 .207.168.375.375.375h1.5ZM3.375 15h1.5a.375.375 0 0 0 .375-.375v-1.5a.375.375 0 0 0-.375-.375h-1.5a.375.375 0 0 0-.375.375v1.5c0 .207.168.375.375.375Zm0-3.75h1.5a.375.375 0 0 0 .375-.375v-1.5A.375.375 0 0 0 4.875 9h-1.5A.375.375 0 0 0 3 9.375v1.5c0 .207.168.375.375.375Zm4.125 0a.75.75 0 0 0 0 1.5h9a.75.75 0 0 0 0-1.5h-9Z" clip-rule="evenodd" /> + </svg> + <span>Episodes</span> + </button> + <button class="flex flex-row items-center focus:outline-none gap-2 text-sm font-bold py-2 px-4 rounded-full category-switch" data-target="trailer"> + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="size-4"> + <path d="M4.5 4.5a3 3 0 0 0-3 3v9a3 3 0 0 0 3 3h8.25a3 3 0 0 0 3-3v-9a3 3 0 0 0-3-3H4.5ZM19.94 18.75l-2.69-2.69V7.94l2.69-2.69c.944-.945 2.56-.276 2.56 1.06v11.38c0 1.336-1.616 2.005-2.56 1.06Z" /> + </svg> + <span>Trailer</span> + </button> + </section> + <section id="characters" class="w-full hidden"> + <div class="flex flex-wrap"> + {% for character in anime.characters %} + <div class="w-full lg:w-1/2 px-4 py-2 flex justify-between"> + <div class="flex flex-row gap-2 items-center"> + <img src="{{ character.image }}" alt="{{ character.name }}" class="rounded-full w-16 h-16 object-cover"/> + <div class="flex flex-col gap-2"> + <span class="font-bold"> + {% if user.preferences.character_name_language == "romaji" %} + {{ character.name.full }} + {% else %} + {{ character.name.native }} + {% endif %} + </span> + <span class="capitalize">{{ character.role }}</span> + </div> + </div> + <div class="flex flex-col items-end"> + {% for voice_actor in character.voiceActors|slice:":1" %} + <div class="flex flex-row gap-2 items-center mb-2"> + <div class="flex flex-col gap-2 text-right"> + <span class="font-bold"> + {% if user.preferences.character_name_language == "romaji" %} + {{ voice_actor.name.full }} + {% else %} + {{ voice_actor.name.native }} + {% endif %} + </span> + <span class="capitalize">{{ voice_actor.language }}</span> + </div> + <img src="{{ voice_actor.image }}" alt="{{ voice_actor.name }}" class="rounded-full w-16 h-16 object-cover"/> + </div> + {% endfor %} + </div> + </div> + {% endfor %} + </div> + </section> + <section id="episodes" class="w-full flex-wrap flex justify-start"> + {% for episode in episodes.episodes %} + <a href="{% url "watch:watch_episode" anime.id episode.number %}" class="w-full lg:w-1/2 px-2 mb-2"> + <div class="flex flex-row w-full bg-neutral-950 rounded hover:bg-{{ user.preferences.accent_colour }}-600 hover:bg-opacity-30 p-2 gap-4"> + <div class="flex-shrink-0"> + <img src="{{ episode.metadata.image }}" alt="{{ episode.title }}" class="w-48 h-28 object-cover rounded-lg"/> + </div> + <div class="flex flex-col gap-2"> + <h2 class="font-bold">{{ episode.metadata.title }}</h2> + <p class="text-sm">{{ episode.metadata.description }}</p> + </div> + </div> + </a> + {% endfor %} + {% if not episodes %} + <div class="w-full h-96 flex items-center justify-center"> + <span>No Episodes Available</span> + </div> + {% endif %} + </section> + <section id="trailer" class="w-full hidden"> + {% if anime.trailer and anime.trailer.site == "youtube" %} + <iframe src="https://www.youtube.com/embed/{{ anime.trailer.id }}" title="{{ anime.title.english }} Trailer" class="w-full aspect-video" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" credentialless allowfullscreen></iframe> + {% else %} + <div class="w-full h-96 flex items-center justify-center"> + <span>No Trailer Available</span> + </div> + {% endif %} + </section> +</section> +{% endblock content %} +{% block scripts %} +<script> + const categorySwitch = document.querySelectorAll(".category-switch"); + categorySwitch.forEach((button) => { + button.addEventListener("click", () => { + categorySwitch.forEach((button) => { + button.classList.remove("active-category"); + button.classList.remove("bg-{{ user.preferences.accent_colour }}-600"); + }); + button.classList.add("active-category"); + button.classList.add("bg-{{ user.preferences.accent_colour }}-600"); + + const target = button.getAttribute("data-target"); + const sections = [document.getElementById("characters"), document.getElementById("episodes"), document.getElementById("trailer")]; + sections.forEach((section) => { + section.classList.add("hidden"); + section.classList.remove("flex"); + }); + document.getElementById(target).classList.remove("hidden"); + document.getElementById(target).classList.add("flex"); + }); + }); +</script> +{% endblock scripts %}
\ No newline at end of file diff --git a/templates/home/index.html b/templates/home/index.html index 423441e..9f770fa 100644 --- a/templates/home/index.html +++ b/templates/home/index.html @@ -57,7 +57,7 @@ </div> </div> <div class="flex flex-row w-full lg:w-1/2 gap-2 lg:justify-end items-end"> - <a href="#future_anime_details" class="bg-white bg-opacity-10 text-white text-sm font-bold py-3 px-6 rounded-full flex items-center gap-2"> + <a href="{% url "detail:detail" anime.id %}" class="bg-white bg-opacity-10 text-white text-sm font-bold py-3 px-6 rounded-full flex items-center gap-2"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6"> <path stroke-linecap="round" stroke-linejoin="round" d="m11.25 11.25.041-.02a.75.75 0 0 1 1.063.852l-.708 2.836a.75.75 0 0 0 1.063.853l.041-.021M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9-3.75h.008v.008H12V8.25Z"/> </svg> diff --git a/templates/partials/datacard_render.html b/templates/partials/datacard_render.html index a2b40ee..00fae91 100644 --- a/templates/partials/datacard_render.html +++ b/templates/partials/datacard_render.html @@ -1,4 +1,5 @@ {% for anime in data %} +{% if not anime.status == "Not yet aired"%} <a href="{% url "watch:watch" anime.id %}" class="w-1/2 md:w-1/3 lg:w-1/4 xl:w-1/5 2xl:w-1/6 text-gray-500 p-1 sm:p-2 mb-4 hover:text-white flex flex-col"> <div class="relative pt-[140%]"> <img src="{{ anime.image }}" alt="{{ anime.title.english }}" class="absolute top-0 left-0 w-full h-full rounded-lg object-cover"/> @@ -58,4 +59,5 @@ </div> </div> </a> +{% endif %} {% endfor %}
\ No newline at end of file diff --git a/templates/partials/datacardcompact_render.html b/templates/partials/datacardcompact_render.html index 461c450..4aecc1c 100644 --- a/templates/partials/datacardcompact_render.html +++ b/templates/partials/datacardcompact_render.html @@ -1,4 +1,5 @@ {% for anime in data %} +{% if not anime.status == "Not yet aired"%} <div class="w-full max-w-full px-2 mb-2"> <a href="{% url 'watch:watch' anime.id %}"> <div class="flex flex-row bg-neutral-950 rounded hover:bg-{{ user.preferences.accent_colour }}-600 hover:bg-opacity-30 p-2 gap-4 items-center"> @@ -122,4 +123,5 @@ </div> </a> </div> +{% endif %} {% endfor %}
\ No newline at end of file diff --git a/templates/partials/datacardwide_render.html b/templates/partials/datacardwide_render.html index 93886dd..827270b 100644 --- a/templates/partials/datacardwide_render.html +++ b/templates/partials/datacardwide_render.html @@ -1,5 +1,6 @@ {% load custom_filters %} {% for anime in data %} +{% if not anime.status == "Not yet aired"%} <div class="w-full lg:w-1/2 px-2 mb-2"> <div class="flex flex-row w-full bg-neutral-950 rounded hover:bg-{{ user.preferences.accent_colour }}-600 hover:bg-opacity-30 p-2 gap-4"> <div class="flex flex-col gap-2 min-w-32"> @@ -134,4 +135,5 @@ </a> </div> </div> +{% endif %} {% endfor %}
\ No newline at end of file diff --git a/watch/utils.py b/watch/utils.py index c6b5180..af3b74f 100644 --- a/watch/utils.py +++ b/watch/utils.py @@ -18,7 +18,6 @@ r = redis.Redis( # print("Redis cache flushed") def get_episode_metadata(anime_data, episode): - # Special Cases: special_case = False if anime_data["title"]["english"] == "Attack on Titan Final Season THE FINAL CHAPTERS Special 1" and episode == 2: episode = 1 @@ -34,6 +33,21 @@ def get_episode_metadata(anime_data, episode): return current_episode_metadata +def get_all_episode_metadata(anime_data): + episode_metadata = get_from_redis_cache(f"anime_{anime_data['id']}_episode_metadata") + if episode_metadata: + episode_metadata = json.loads(episode_metadata) + else: + episode_metadata = get_anime_episodes(anime_data) + store_in_redis_cache(f"anime_{anime_data['id']}_episode_metadata", json.dumps(episode_metadata)) + + # Special cases + if anime_data["title"]["english"] == "Attack on Titan Final Season THE FINAL CHAPTERS Special 1": + episode_metadata.insert(0, episode_metadata[0]) + + return episode_metadata + + def update_anime_user_history(user, anime_id, episode, time_watched): # per episode history history, created = UserHistory.objects.get_or_create(user=user, anime_id=anime_id, episode=episode) diff --git a/watch/views.py b/watch/views.py index 7d3b981..e5ccb06 100644 --- a/watch/views.py +++ b/watch/views.py @@ -11,6 +11,10 @@ import json dotenv.load_dotenv() def watch(request, anime_id, episode=None): + forward_detail = request.GET.get("forward") == "detail" + if not episode and request.user.preferences.default_watch_page == "detail" and not forward_detail: + return redirect("detail:detail", anime_id=anime_id) + anime_history = get_anime_user_history(request.user, anime_id) watched_episodes = [h.episode for h in anime_history] @@ -60,6 +64,9 @@ def watch(request, anime_id, episode=None): anime_data = response.json() store_in_redis_cache(f"anime_{anime_id}_anime_data", json.dumps(anime_data)) + if anime_data["status"] == "Not yet aired": + return redirect("detail:detail", anime_id=anime_id) + if not anime_selected_cached: z_anime_id = anime_data["episodes"][0]["id"].split("$")[0] if len(anime_data["episodes"]) > 0 else None if z_anime_id is not None: diff --git a/yugen/settings.py b/yugen/settings.py index c8dedbc..07052b9 100644 --- a/yugen/settings.py +++ b/yugen/settings.py @@ -32,7 +32,11 @@ DEBUG = True if os.environ.get("DEBUG") == "True" else False ALLOWED_HOSTS = ["127.0.0.1", "localhost", ".vercel.app", ".rize.moe"] AUTH_USER_MODEL = "authentication.User" +X_FRAME_OPTIONS = "SAMEORIGIN" +CORS_ALLOWED_ORIGINS = [ + "https://www.youtube.com", +] # Application definition INSTALLED_APPS = [ diff --git a/yugen/urls.py b/yugen/urls.py index b89fcff..db1aa79 100644 --- a/yugen/urls.py +++ b/yugen/urls.py @@ -22,6 +22,7 @@ from django.conf.urls.static import static urlpatterns = [ path("", include("homepage.urls", namespace="home")), path("watch/", include("watch.urls", namespace="watch")), + path("detail/", include("detail.urls", namespace="detail")), path("profile/", include("user_profile.urls", namespace="profile")), path("auth/", include("authentication.urls", namespace="auth")), path("admin/", admin.site.urls), |
