5 Commits

Author SHA1 Message Date
3bfb31f8bf docs: update AniList search query in Bruno collection
Add the genre field to the AniList Search GraphQL query in the Bruno API
collection. This keeps the API documentation collection in sync with the
application's query structure, allowing for testing and verification of
genre data retrieval from the AniList API.
2026-03-19 20:55:48 -04:00
4739fb4344 build: update Wails generated models for genres support
Regenerate the Wails TypeScript models to include the new Genres field
in the MediaList type definition. This is an auto-generated file that
reflects the updated Go backend type structure with the genres []string
field added to the media object.
2026-03-19 20:55:48 -04:00
f4382304df feat(frontend): add genre display UI and enhance link component
- Anime.svelte: Add genre display section with clickable badges that link
  to AniList search results for each genre. Genres are now displayed above
  the existing tags section with consistent styling.

- WebsiteLink.svelte: Enhance component to support custom URLs via the `url`
  export parameter. Previously, the component only generated URLs based on
  service prefixes (a-, m-, s-). Now it accepts a direct URL parameter for
  flexible linking to AniList searches and other external resources.

These changes provide users with an improved browsing experience by making
genres interactive and easily searchable.
2026-03-19 20:55:21 -04:00
4400dfd637 feat(frontend): update TypeScript types for AniList genres support
Update the AniListCurrentUserWatchListType TypeScript interface to include
the genres field as a string array, matching the updated backend Go type
definition. This ensures type safety and proper IDE autocomplete when
working with genre data in the frontend.
2026-03-19 20:54:39 -04:00
b90d8eb2d3 feat(backend): add genres support to AniList integration
Add the `genres` field to AniList GraphQL queries and type definitions:
- Add genres field to GetAniListItem query for fetching single anime details
- Add genres field to AniListSearch query for search results
- Add genres field to GetAniListUserWatchingList query for user's watch list
- Update MediaList type definition to include Genres []string field

This enhancement allows the application to retrieve and display anime genre
information from the AniList API, providing users with better categorization
and discovery capabilities.
2026-03-19 20:54:39 -04:00
7 changed files with 59 additions and 27 deletions

View File

@@ -93,6 +93,7 @@ func (a *App) GetAniListItem(aniId int, login bool) AniListGetSingleAnime {
timeUntilAiring timeUntilAiring
episode episode
} }
genres
tags{ tags{
id id
name name
@@ -222,6 +223,7 @@ func (a *App) AniListSearch(query string) any {
timeUntilAiring timeUntilAiring
episode episode
} }
genres
tags{ tags{
id id
name name
@@ -307,6 +309,7 @@ func (a *App) GetAniListUserWatchingList(page int, perPage int, sort string) Ani
timeUntilAiring timeUntilAiring
episode episode
} }
genres
tags{ tags{
id id
name name

View File

@@ -74,7 +74,8 @@ type MediaList struct {
TimeUntilAiring int `json:"timeUntilAiring"` TimeUntilAiring int `json:"timeUntilAiring"`
Episode int `json:"episode"` Episode int `json:"episode"`
} `json:"nextAiringEpisode"` } `json:"nextAiringEpisode"`
Tags []struct { Genres []string `json:"genres"`
Tags []struct {
Id int `json:"id"` Id int `json:"id"`
Name string `json:"name"` Name string `json:"name"`
Description string `json:"description"` Description string `json:"description"`

View File

@@ -33,6 +33,7 @@ body:graphql {
english english
native native
} }
genre
description description
coverImage { coverImage {
large large

View File

@@ -6,7 +6,12 @@
} from "../helperModules/GlobalVariablesAndHelperFunctions.svelte"; } from "../helperModules/GlobalVariablesAndHelperFunctions.svelte";
import {userStore} from "../helperFunctions/userStore" import {userStore} from "../helperFunctions/userStore"
import { push } from "svelte-spa-router"; import { push } from "svelte-spa-router";
<<<<<<< HEAD
import type { AniListGetSingleAnime } from "../types/AniListCurrentUserWatchListType"; import type { AniListGetSingleAnime } from "../types/AniListCurrentUserWatchListType";
=======
import WebsiteLink from "./WebsiteLink.svelte";
import type { AniListGetSingleAnime } from "../anilist/types/AniListCurrentUserWatchListType";
>>>>>>> a222c6b (feat(frontend): add genre display UI and enhance link component)
import Rating from "./Rating.svelte"; import Rating from "./Rating.svelte";
import { import {
convertAniListDateToString, convertAniListDateToString,
@@ -38,7 +43,7 @@
} from "../../wailsjs/go/main/App"; } from "../../wailsjs/go/main/App";
import { AddAnimeServiceToTable } from "../helperModules/AddAnimeServiceToTable.svelte"; import { AddAnimeServiceToTable } from "../helperModules/AddAnimeServiceToTable.svelte";
import Datepicker from "./Datepicker.svelte"; import Datepicker from "./Datepicker.svelte";
import {Badge, Tooltip} from "flowbite-svelte"; import { Badge, Tooltip } from "flowbite-svelte";
const re = /^([0-9]{4})-([0-9]{2})-([0-9]{2})/; const re = /^([0-9]{4})-([0-9]{2})-([0-9]{2})/;
let currentAniListAnime: AniListGetSingleAnime; let currentAniListAnime: AniListGetSingleAnime;
@@ -817,15 +822,32 @@
<div class="flex m-5"> <div class="flex m-5">
<div> <div>
<h3 class="text-2xl">Genres</h3>
{#each currentAniListAnime.data.MediaList.media.genres as genre}
<div>
<Badge large border color="blue" class="m-1 w-52">
<div>
<WebsiteLink
id={genre}
url="https://anilist.co/search/anime/{genre}"
/>
</div>
</Badge>
<Tooltip>{genre}</Tooltip>
</div>
{/each}
<h3 class="text-2xl">Tags</h3> <h3 class="text-2xl">Tags</h3>
<div class="mt-2"> <div class="mt-2">
{#each currentAniListAnime.data.MediaList.media.tags as tag} {#each currentAniListAnime.data.MediaList.media.tags as tag}
<div> <div>
<Badge large border color="blue" class="m-1 w-52"> <Badge large border color="blue" class="m-1 w-52">
<div> <div>
{tag.name} - <WebsiteLink
<span class="text-xs">{tag.rank}%</span> id={tag.name}
</div> url="https://anilist.co/search/anime/?genres={tag.name}"
/>
<span class="text-xs">({tag.rank}%)</span>
</div>
</Badge> </Badge>
<Tooltip>{tag.description}</Tooltip> <Tooltip>{tag.description}</Tooltip>
</div> </div>

View File

@@ -1,28 +1,32 @@
<script lang="ts"> <script lang="ts">
import {BrowserOpenURL} from "../../wailsjs/runtime" import { BrowserOpenURL } from "../../wailsjs/runtime";
export let id: string export let id: string;
let url = "" export let url = "";
let isAniList = false let isAniList = false;
let isMAL = false let isMAL = false;
let isSimkl = false let isSimkl = false;
let newId = id let newId = id;
let re = /[ams]?-?(.*)/ let re = /[ams]?-?(.*)/;
if (id !== undefined && id.length > 0) { if (id !== undefined && id.length > 0) {
isAniList = id.includes("a-") isAniList = id.includes("a-");
isMAL = id.includes("m-") isMAL = id.includes("m-");
isSimkl = id.includes("s-") isSimkl = id.includes("s-");
newId = id.match(re)[1] if (isAniList || isMAL || isSimkl) newId = id.match(re)[1];
} else newId = id;
}
if (isAniList) url = `https://anilist.co/anime/${newId}`;
if (isAniList) url = `https://anilist.co/anime/${newId}` if (isMAL) url = `https://myanimelist.net/anime/${newId}`;
if (isMAL) url = `https://myanimelist.net/anime/${newId}` if (isSimkl) url = `https://simkl.com/anime/${newId}`;
if (isSimkl) url = `https://simkl.com/anime/${newId}`
</script> </script>
{#if url.length > 0} {#if url.length > 0}
<button class="underline underline-offset-2 px-4 py-1" on:click={() => BrowserOpenURL(url)}>{newId}</button> <button
type="button"
class="underline underline-offset-2 px-4 py-1"
on:click={() => BrowserOpenURL(url)}>{newId}</button
>
{:else} {:else}
{id} {id}
{/if} {/if}

View File

@@ -44,6 +44,7 @@ export interface MediaList {
timeUntilAiring: number; timeUntilAiring: number;
episode: number; episode: number;
}; };
genres: string[];
tags: [ tags: [
{ {
id: number; id: number;

View File

@@ -364,7 +364,7 @@ export namespace main {
id: number; id: number;
mediaId: number; mediaId: number;
userId: number; userId: number;
// Go type: struct { ID int "json:\"id\""; IDMal int "json:\"idMal\""; Title struct { Romaji string "json:\"romaji\""; English string "json:\"english\""; Native string "json:\"native\"" } "json:\"title\""; Description string "json:\"description\""; CoverImage struct { Large string "json:\"large\"" } "json:\"coverImage\""; Season string "json:\"season\""; SeasonYear int "json:\"seasonYear\""; Status string "json:\"status\""; Episodes int "json:\"episodes\""; NextAiringEpisode struct { AiringAt int "json:\"airingAt\""; TimeUntilAiring int "json:\"timeUntilAiring\""; Episode int "json:\"episode\"" } "json:\"nextAiringEpisode\""; Tags []struct { Id int "json:\"id\""; Name string "json:\"name\""; Description string "json:\"description\""; Rank int "json:\"rank\""; IsMediaSpoiler bool "json:\"isMediaSpoiler\""; IsAdult bool "json:\"isAdult\"" } "json:\"tags\""; IsAdult bool "json:\"isAdult\"" } // Go type: struct { ID int "json:\"id\""; IDMal int "json:\"idMal\""; Title struct { Romaji string "json:\"romaji\""; English string "json:\"english\""; Native string "json:\"native\"" } "json:\"title\""; Description string "json:\"description\""; CoverImage struct { Large string "json:\"large\"" } "json:\"coverImage\""; Season string "json:\"season\""; SeasonYear int "json:\"seasonYear\""; Status string "json:\"status\""; Episodes int "json:\"episodes\""; NextAiringEpisode struct { AiringAt int "json:\"airingAt\""; TimeUntilAiring int "json:\"timeUntilAiring\""; Episode int "json:\"episode\"" } "json:\"nextAiringEpisode\""; Genres []string "json:\"genres\""; Tags []struct { Id int "json:\"id\""; Name string "json:\"name\""; Description string "json:\"description\""; Rank int "json:\"rank\""; IsMediaSpoiler bool "json:\"isMediaSpoiler\""; IsAdult bool "json:\"isAdult\"" } "json:\"tags\""; IsAdult bool "json:\"isAdult\"" }
media: any; media: any;
status: string; status: string;
// Go type: struct { Year int "json:\"year\""; Month int "json:\"month\""; Day int "json:\"day\"" } // Go type: struct { Year int "json:\"year\""; Month int "json:\"month\""; Day int "json:\"day\"" }