mirror of
https://github.com/ThaUnknown/miru.git
synced 2026-04-21 09:31:57 +00:00
fix: scrolling in view anime page
feat: new relations tree
This commit is contained in:
parent
4efd9449d6
commit
b7e06e89cb
11 changed files with 483 additions and 56 deletions
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "ui",
|
"name": "ui",
|
||||||
"version": "6.4.128",
|
"version": "6.4.129",
|
||||||
"license": "BUSL-1.1",
|
"license": "BUSL-1.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"packageManager": "pnpm@9.15.5",
|
"packageManager": "pnpm@9.15.5",
|
||||||
|
|
@ -51,6 +51,7 @@
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@cloudflare/speedtest": "^1.6.0",
|
"@cloudflare/speedtest": "^1.6.0",
|
||||||
|
"@dagrejs/dagre": "^1.1.5",
|
||||||
"@fontsource-variable/nunito": "^5.2.6",
|
"@fontsource-variable/nunito": "^5.2.6",
|
||||||
"@fontsource/geist-mono": "^5.2.6",
|
"@fontsource/geist-mono": "^5.2.6",
|
||||||
"@prgm/sveltekit-progress-bar": "2.0.0",
|
"@prgm/sveltekit-progress-bar": "2.0.0",
|
||||||
|
|
@ -62,6 +63,7 @@
|
||||||
"@urql/exchange-request-policy": "^2.0.0",
|
"@urql/exchange-request-policy": "^2.0.0",
|
||||||
"@urql/exchange-retry": "^2.0.0",
|
"@urql/exchange-retry": "^2.0.0",
|
||||||
"@urql/svelte": "^5.0.0",
|
"@urql/svelte": "^5.0.0",
|
||||||
|
"@xyflow/svelte": "^0.1.36",
|
||||||
"abslink": "^1.1.2",
|
"abslink": "^1.1.2",
|
||||||
"anitomyscript": "github:thaunknown/anitomyscript",
|
"anitomyscript": "github:thaunknown/anitomyscript",
|
||||||
"bittorrent-tracker": "10.0.12",
|
"bittorrent-tracker": "10.0.12",
|
||||||
|
|
|
||||||
157
pnpm-lock.yaml
157
pnpm-lock.yaml
|
|
@ -11,6 +11,9 @@ importers:
|
||||||
'@cloudflare/speedtest':
|
'@cloudflare/speedtest':
|
||||||
specifier: ^1.6.0
|
specifier: ^1.6.0
|
||||||
version: 1.6.0
|
version: 1.6.0
|
||||||
|
'@dagrejs/dagre':
|
||||||
|
specifier: ^1.1.5
|
||||||
|
version: 1.1.5
|
||||||
'@fontsource-variable/nunito':
|
'@fontsource-variable/nunito':
|
||||||
specifier: ^5.2.6
|
specifier: ^5.2.6
|
||||||
version: 5.2.6
|
version: 5.2.6
|
||||||
|
|
@ -44,6 +47,9 @@ importers:
|
||||||
'@urql/svelte':
|
'@urql/svelte':
|
||||||
specifier: ^5.0.0
|
specifier: ^5.0.0
|
||||||
version: 5.0.0(@urql/core@6.0.1(graphql@16.10.0))(svelte@4.2.19)
|
version: 5.0.0(@urql/core@6.0.1(graphql@16.10.0))(svelte@4.2.19)
|
||||||
|
'@xyflow/svelte':
|
||||||
|
specifier: ^0.1.36
|
||||||
|
version: 0.1.36(svelte@4.2.19)
|
||||||
abslink:
|
abslink:
|
||||||
specifier: ^1.1.2
|
specifier: ^1.1.2
|
||||||
version: 1.1.2
|
version: 1.1.2
|
||||||
|
|
@ -254,6 +260,13 @@ packages:
|
||||||
resolution: {integrity: sha512-5EfTvWcDCAK6zOJpl7i4Ablzvxje7+dgVmhJxdK/uDuTIivyUVat/cCnxE67YYpuxKs+gbo569PbmHl+oI5eFA==}
|
resolution: {integrity: sha512-5EfTvWcDCAK6zOJpl7i4Ablzvxje7+dgVmhJxdK/uDuTIivyUVat/cCnxE67YYpuxKs+gbo569PbmHl+oI5eFA==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
'@dagrejs/dagre@1.1.5':
|
||||||
|
resolution: {integrity: sha512-Ghgrh08s12DCL5SeiR6AoyE80mQELTWhJBRmXfFoqDiFkR458vPEdgTbbjA0T+9ETNxUblnD0QW55tfdvi5pjQ==}
|
||||||
|
|
||||||
|
'@dagrejs/graphlib@2.2.4':
|
||||||
|
resolution: {integrity: sha512-mepCf/e9+SKYy1d02/UkvSy6+6MoyXhVxP8lLDfA7BPE1X1d4dR0sZznmbM8/XVJ1GPM+Svnx7Xj6ZweByWUkw==}
|
||||||
|
engines: {node: '>17.0.0'}
|
||||||
|
|
||||||
'@esbuild/aix-ppc64@0.21.5':
|
'@esbuild/aix-ppc64@0.21.5':
|
||||||
resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==}
|
resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
|
@ -670,6 +683,11 @@ packages:
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
eslint: '>=9.0.0'
|
eslint: '>=9.0.0'
|
||||||
|
|
||||||
|
'@svelte-put/shortcut@3.1.1':
|
||||||
|
resolution: {integrity: sha512-2L5EYTZXiaKvbEelVkg5znxqvfZGZai3m97+cAiUBhLZwXnGtviTDpHxOoZBsqz41szlfRMcamW/8o0+fbW3ZQ==}
|
||||||
|
peerDependencies:
|
||||||
|
svelte: ^3.55.0 || ^4.0.0 || ^5.0.0
|
||||||
|
|
||||||
'@sveltejs/acorn-typescript@1.0.5':
|
'@sveltejs/acorn-typescript@1.0.5':
|
||||||
resolution: {integrity: sha512-IwQk4yfwLdibDlrXVE04jTZYlLnwsTT2PIOQQGNLWfjavGifnk1JD1LcZjZaBTRcxZu2FfPfNLOE04DSu9lqtQ==}
|
resolution: {integrity: sha512-IwQk4yfwLdibDlrXVE04jTZYlLnwsTT2PIOQQGNLWfjavGifnk1JD1LcZjZaBTRcxZu2FfPfNLOE04DSu9lqtQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
|
|
@ -723,6 +741,24 @@ packages:
|
||||||
'@types/cookie@0.6.0':
|
'@types/cookie@0.6.0':
|
||||||
resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==}
|
resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==}
|
||||||
|
|
||||||
|
'@types/d3-color@3.1.3':
|
||||||
|
resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==}
|
||||||
|
|
||||||
|
'@types/d3-drag@3.0.7':
|
||||||
|
resolution: {integrity: sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==}
|
||||||
|
|
||||||
|
'@types/d3-interpolate@3.0.4':
|
||||||
|
resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==}
|
||||||
|
|
||||||
|
'@types/d3-selection@3.0.11':
|
||||||
|
resolution: {integrity: sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==}
|
||||||
|
|
||||||
|
'@types/d3-transition@3.0.9':
|
||||||
|
resolution: {integrity: sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==}
|
||||||
|
|
||||||
|
'@types/d3-zoom@3.0.8':
|
||||||
|
resolution: {integrity: sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==}
|
||||||
|
|
||||||
'@types/debug@4.1.12':
|
'@types/debug@4.1.12':
|
||||||
resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==}
|
resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==}
|
||||||
|
|
||||||
|
|
@ -845,6 +881,14 @@ packages:
|
||||||
'@urql/core': ^6.0.0
|
'@urql/core': ^6.0.0
|
||||||
svelte: ^3.0.0 || ^4.0.0 || ^5.0.0
|
svelte: ^3.0.0 || ^4.0.0 || ^5.0.0
|
||||||
|
|
||||||
|
'@xyflow/svelte@0.1.36':
|
||||||
|
resolution: {integrity: sha512-kvu5qz7hGj50Rf6COMFhikhS9766lCm+QPjuxpf6SEppAMhml3r9G98+Pf78MGKDC/C5m2YShpktbtoAl72J6g==}
|
||||||
|
peerDependencies:
|
||||||
|
svelte: ^3.0.0 || ^4.0.0 || ^5.0.0
|
||||||
|
|
||||||
|
'@xyflow/system@0.0.56':
|
||||||
|
resolution: {integrity: sha512-Xc3LvEumjJD+CqPqlYkrlszJ4hWQ0DE+r5M4e5WpS/hKT4T6ktAjt7zeMNJ+vvTsXHulGnEoDRA8zbIfB6tPdQ==}
|
||||||
|
|
||||||
abslink@1.1.2:
|
abslink@1.1.2:
|
||||||
resolution: {integrity: sha512-vnuzpBWd09oAdyFmseonzDoyfZtjKN9XuFzoXQUgU0Sme4ksYRwdH9YgOeserpDdtDMq7nd82gnKbTrYQQ3FnQ==}
|
resolution: {integrity: sha512-vnuzpBWd09oAdyFmseonzDoyfZtjKN9XuFzoXQUgU0Sme4ksYRwdH9YgOeserpDdtDMq7nd82gnKbTrYQQ3FnQ==}
|
||||||
|
|
||||||
|
|
@ -1064,6 +1108,9 @@ packages:
|
||||||
chrome-dgram@3.0.6:
|
chrome-dgram@3.0.6:
|
||||||
resolution: {integrity: sha512-bqBsUuaOiXiqxXt/zA/jukNJJ4oaOtc7ciwqJpZVEaaXwwxqgI2/ZdG02vXYWUhHGziDlvGMQWk0qObgJwVYKA==}
|
resolution: {integrity: sha512-bqBsUuaOiXiqxXt/zA/jukNJJ4oaOtc7ciwqJpZVEaaXwwxqgI2/ZdG02vXYWUhHGziDlvGMQWk0qObgJwVYKA==}
|
||||||
|
|
||||||
|
classcat@5.0.5:
|
||||||
|
resolution: {integrity: sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w==}
|
||||||
|
|
||||||
clone@2.1.2:
|
clone@2.1.2:
|
||||||
resolution: {integrity: sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==}
|
resolution: {integrity: sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==}
|
||||||
engines: {node: '>=0.8'}
|
engines: {node: '>=0.8'}
|
||||||
|
|
@ -1137,6 +1184,18 @@ packages:
|
||||||
resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==}
|
resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
d3-dispatch@3.0.1:
|
||||||
|
resolution: {integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
d3-drag@3.0.0:
|
||||||
|
resolution: {integrity: sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
d3-ease@3.0.1:
|
||||||
|
resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
d3-format@3.1.0:
|
d3-format@3.1.0:
|
||||||
resolution: {integrity: sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==}
|
resolution: {integrity: sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
|
@ -1149,6 +1208,10 @@ packages:
|
||||||
resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==}
|
resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
d3-selection@3.0.0:
|
||||||
|
resolution: {integrity: sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
d3-time-format@4.1.0:
|
d3-time-format@4.1.0:
|
||||||
resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==}
|
resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
|
@ -1157,6 +1220,20 @@ packages:
|
||||||
resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==}
|
resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
d3-timer@3.0.1:
|
||||||
|
resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
d3-transition@3.0.1:
|
||||||
|
resolution: {integrity: sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
peerDependencies:
|
||||||
|
d3-selection: 2 - 3
|
||||||
|
|
||||||
|
d3-zoom@3.0.0:
|
||||||
|
resolution: {integrity: sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
data-view-buffer@1.0.2:
|
data-view-buffer@1.0.2:
|
||||||
resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==}
|
resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
@ -2871,6 +2948,12 @@ snapshots:
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- encoding
|
- encoding
|
||||||
|
|
||||||
|
'@dagrejs/dagre@1.1.5':
|
||||||
|
dependencies:
|
||||||
|
'@dagrejs/graphlib': 2.2.4
|
||||||
|
|
||||||
|
'@dagrejs/graphlib@2.2.4': {}
|
||||||
|
|
||||||
'@esbuild/aix-ppc64@0.21.5':
|
'@esbuild/aix-ppc64@0.21.5':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
|
@ -3191,6 +3274,10 @@ snapshots:
|
||||||
estraverse: 5.3.0
|
estraverse: 5.3.0
|
||||||
picomatch: 4.0.3
|
picomatch: 4.0.3
|
||||||
|
|
||||||
|
'@svelte-put/shortcut@3.1.1(svelte@4.2.19)':
|
||||||
|
dependencies:
|
||||||
|
svelte: 4.2.19
|
||||||
|
|
||||||
'@sveltejs/acorn-typescript@1.0.5(acorn@8.15.0)':
|
'@sveltejs/acorn-typescript@1.0.5(acorn@8.15.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
acorn: 8.15.0
|
acorn: 8.15.0
|
||||||
|
|
@ -3276,6 +3363,27 @@ snapshots:
|
||||||
|
|
||||||
'@types/cookie@0.6.0': {}
|
'@types/cookie@0.6.0': {}
|
||||||
|
|
||||||
|
'@types/d3-color@3.1.3': {}
|
||||||
|
|
||||||
|
'@types/d3-drag@3.0.7':
|
||||||
|
dependencies:
|
||||||
|
'@types/d3-selection': 3.0.11
|
||||||
|
|
||||||
|
'@types/d3-interpolate@3.0.4':
|
||||||
|
dependencies:
|
||||||
|
'@types/d3-color': 3.1.3
|
||||||
|
|
||||||
|
'@types/d3-selection@3.0.11': {}
|
||||||
|
|
||||||
|
'@types/d3-transition@3.0.9':
|
||||||
|
dependencies:
|
||||||
|
'@types/d3-selection': 3.0.11
|
||||||
|
|
||||||
|
'@types/d3-zoom@3.0.8':
|
||||||
|
dependencies:
|
||||||
|
'@types/d3-interpolate': 3.0.4
|
||||||
|
'@types/d3-selection': 3.0.11
|
||||||
|
|
||||||
'@types/debug@4.1.12':
|
'@types/debug@4.1.12':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/ms': 2.1.0
|
'@types/ms': 2.1.0
|
||||||
|
|
@ -3433,6 +3541,23 @@ snapshots:
|
||||||
svelte: 4.2.19
|
svelte: 4.2.19
|
||||||
wonka: 6.3.5
|
wonka: 6.3.5
|
||||||
|
|
||||||
|
'@xyflow/svelte@0.1.36(svelte@4.2.19)':
|
||||||
|
dependencies:
|
||||||
|
'@svelte-put/shortcut': 3.1.1(svelte@4.2.19)
|
||||||
|
'@xyflow/system': 0.0.56
|
||||||
|
classcat: 5.0.5
|
||||||
|
svelte: 4.2.19
|
||||||
|
|
||||||
|
'@xyflow/system@0.0.56':
|
||||||
|
dependencies:
|
||||||
|
'@types/d3-drag': 3.0.7
|
||||||
|
'@types/d3-selection': 3.0.11
|
||||||
|
'@types/d3-transition': 3.0.9
|
||||||
|
'@types/d3-zoom': 3.0.8
|
||||||
|
d3-drag: 3.0.0
|
||||||
|
d3-selection: 3.0.0
|
||||||
|
d3-zoom: 3.0.0
|
||||||
|
|
||||||
abslink@1.1.2: {}
|
abslink@1.1.2: {}
|
||||||
|
|
||||||
acorn-jsx@5.3.2(acorn@8.15.0):
|
acorn-jsx@5.3.2(acorn@8.15.0):
|
||||||
|
|
@ -3697,6 +3822,8 @@ snapshots:
|
||||||
inherits: 2.0.4
|
inherits: 2.0.4
|
||||||
run-series: 1.1.9
|
run-series: 1.1.9
|
||||||
|
|
||||||
|
classcat@5.0.5: {}
|
||||||
|
|
||||||
clone@2.1.2: {}
|
clone@2.1.2: {}
|
||||||
|
|
||||||
clsx@2.1.1: {}
|
clsx@2.1.1: {}
|
||||||
|
|
@ -3764,6 +3891,15 @@ snapshots:
|
||||||
|
|
||||||
d3-color@3.1.0: {}
|
d3-color@3.1.0: {}
|
||||||
|
|
||||||
|
d3-dispatch@3.0.1: {}
|
||||||
|
|
||||||
|
d3-drag@3.0.0:
|
||||||
|
dependencies:
|
||||||
|
d3-dispatch: 3.0.1
|
||||||
|
d3-selection: 3.0.0
|
||||||
|
|
||||||
|
d3-ease@3.0.1: {}
|
||||||
|
|
||||||
d3-format@3.1.0: {}
|
d3-format@3.1.0: {}
|
||||||
|
|
||||||
d3-interpolate@3.0.1:
|
d3-interpolate@3.0.1:
|
||||||
|
|
@ -3778,6 +3914,8 @@ snapshots:
|
||||||
d3-time: 3.1.0
|
d3-time: 3.1.0
|
||||||
d3-time-format: 4.1.0
|
d3-time-format: 4.1.0
|
||||||
|
|
||||||
|
d3-selection@3.0.0: {}
|
||||||
|
|
||||||
d3-time-format@4.1.0:
|
d3-time-format@4.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
d3-time: 3.1.0
|
d3-time: 3.1.0
|
||||||
|
|
@ -3786,6 +3924,25 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
d3-array: 3.2.4
|
d3-array: 3.2.4
|
||||||
|
|
||||||
|
d3-timer@3.0.1: {}
|
||||||
|
|
||||||
|
d3-transition@3.0.1(d3-selection@3.0.0):
|
||||||
|
dependencies:
|
||||||
|
d3-color: 3.1.0
|
||||||
|
d3-dispatch: 3.0.1
|
||||||
|
d3-ease: 3.0.1
|
||||||
|
d3-interpolate: 3.0.1
|
||||||
|
d3-selection: 3.0.0
|
||||||
|
d3-timer: 3.0.1
|
||||||
|
|
||||||
|
d3-zoom@3.0.0:
|
||||||
|
dependencies:
|
||||||
|
d3-dispatch: 3.0.1
|
||||||
|
d3-drag: 3.0.0
|
||||||
|
d3-interpolate: 3.0.1
|
||||||
|
d3-selection: 3.0.0
|
||||||
|
d3-transition: 3.0.1(d3-selection@3.0.0)
|
||||||
|
|
||||||
data-view-buffer@1.0.2:
|
data-view-buffer@1.0.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
call-bound: 1.0.4
|
call-bound: 1.0.4
|
||||||
|
|
|
||||||
|
|
@ -94,7 +94,7 @@
|
||||||
--custom: #fff;
|
--custom: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark {
|
html.dark {
|
||||||
--background: 240 10% 3.9%;
|
--background: 240 10% 3.9%;
|
||||||
--foreground: 0 0% 98%;
|
--foreground: 0 0% 98%;
|
||||||
|
|
||||||
|
|
|
||||||
144
src/lib/components/ui/relations/Relations.svelte
Normal file
144
src/lib/components/ui/relations/Relations.svelte
Normal file
|
|
@ -0,0 +1,144 @@
|
||||||
|
<script lang='ts'>
|
||||||
|
import Dagre from '@dagrejs/dagre'
|
||||||
|
import { SvelteFlow, Background, useSvelteFlow, type Node, type Edge, Controls, ControlButton, type NodeTypes } from '@xyflow/svelte'
|
||||||
|
import '@xyflow/svelte/dist/style.css'
|
||||||
|
import Maximize2 from 'lucide-svelte/icons/maximize-2'
|
||||||
|
import Minimize2 from 'lucide-svelte/icons/minimize-2'
|
||||||
|
import { writable } from 'simple-store-svelte'
|
||||||
|
import { onMount } from 'svelte'
|
||||||
|
|
||||||
|
import TextNode from './TextNode.svelte'
|
||||||
|
|
||||||
|
import type { Media } from '$lib/modules/anilist'
|
||||||
|
|
||||||
|
import { client } from '$lib/modules/anilist'
|
||||||
|
|
||||||
|
export let media: Media
|
||||||
|
|
||||||
|
export let expanded: boolean
|
||||||
|
|
||||||
|
// WARN: this is non-reactive, only set on init, but it shouldn't matter as the anime page can only navigate to entries already visible in the graph
|
||||||
|
// this is done to make sure the graph doesn't reset when navigating to a relation
|
||||||
|
const nodesStore = client.relationsTree(media)
|
||||||
|
|
||||||
|
const nodes = writable<Node[]>([])
|
||||||
|
const edges = writable<Edge[]>([])
|
||||||
|
|
||||||
|
$: $nodes = [...$nodesStore.nodes.values()]
|
||||||
|
$: $edges = [...$nodesStore.edges.values()]
|
||||||
|
|
||||||
|
const { fitView } = useSvelteFlow()
|
||||||
|
|
||||||
|
$: media && onLayout()
|
||||||
|
|
||||||
|
$: $nodesStore && fitAndLayout()
|
||||||
|
|
||||||
|
function getLayoutedElements (nodes: Node[], edges: Edge[]) {
|
||||||
|
const g = new Dagre.graphlib.Graph().setDefaultEdgeLabel(() => ({}))
|
||||||
|
g.setGraph({ rankdir: 'LR', edgesep: 50, nodesep: 50, ranksep: 120, ranker: 'tight-tree' })
|
||||||
|
// TODO: switch between longest-path and tight-tree based on number of nodes?
|
||||||
|
|
||||||
|
edges.forEach((edge) => g.setEdge(edge.source, edge.target))
|
||||||
|
nodes.forEach((node) =>
|
||||||
|
g.setNode(node.id, {
|
||||||
|
...node,
|
||||||
|
width: node.measured?.width ?? 120,
|
||||||
|
height: node.measured?.height ?? 32
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
Dagre.layout(g)
|
||||||
|
|
||||||
|
return {
|
||||||
|
nodes: nodes.map((node) => {
|
||||||
|
const position = g.node(node.id)
|
||||||
|
// We are shifting the dagre node position (anchor=center center) to the top left
|
||||||
|
// so it matches the Svelte Flow node anchor point (top left).
|
||||||
|
const x = position.x - (node.measured?.width ?? 0) / 2
|
||||||
|
const y = position.y - (node.measured?.height ?? 0) / 2
|
||||||
|
|
||||||
|
return {
|
||||||
|
...node,
|
||||||
|
data: {
|
||||||
|
...node.data,
|
||||||
|
current: node.data.id === media.id
|
||||||
|
},
|
||||||
|
type: 'customText',
|
||||||
|
position: { x, y },
|
||||||
|
sourcePosition: 'right',
|
||||||
|
targetPosition: 'left'
|
||||||
|
}
|
||||||
|
}) as Node[],
|
||||||
|
edges: edges.map(e => ({
|
||||||
|
...e,
|
||||||
|
style: (e.data?.ids as number[]).includes(media.id) ? '--xy-edge-stroke: var(--custom)' : '',
|
||||||
|
labelStyle: (e.data?.ids as number[]).includes(media.id) ? '--xy-edge-label-color: var(--custom)' : ''
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onLayout () {
|
||||||
|
const { nodes, edges } = getLayoutedElements($nodes, $edges)
|
||||||
|
|
||||||
|
$nodes = nodes
|
||||||
|
$edges = edges
|
||||||
|
}
|
||||||
|
function fitAndLayout () {
|
||||||
|
onLayout()
|
||||||
|
fitView()
|
||||||
|
}
|
||||||
|
|
||||||
|
// turbo hacky but cba
|
||||||
|
let frameId: number
|
||||||
|
function loopFitView () {
|
||||||
|
cancelAnimationFrame(frameId)
|
||||||
|
fitView()
|
||||||
|
frameId = requestAnimationFrame(loopFitView)
|
||||||
|
}
|
||||||
|
|
||||||
|
let timeoutId: ReturnType<typeof setTimeout>
|
||||||
|
function expand () {
|
||||||
|
expanded = !expanded
|
||||||
|
loopFitView()
|
||||||
|
clearTimeout(timeoutId)
|
||||||
|
timeoutId = setTimeout(() => {
|
||||||
|
cancelAnimationFrame(frameId)
|
||||||
|
if (expanded) document.querySelector('.svelte-flow')?.scrollIntoView({ behavior: 'smooth', block: 'center', inline: 'nearest' })
|
||||||
|
fitView()
|
||||||
|
}, 150)
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
fitAndLayout()
|
||||||
|
setTimeout(fitAndLayout)
|
||||||
|
})
|
||||||
|
|
||||||
|
const nodeTypes = {
|
||||||
|
customText: TextNode as NodeTypes['customText']
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<SvelteFlow {nodes} {edges} colorMode='dark'
|
||||||
|
proOptions={{ hideAttribution: true }}
|
||||||
|
nodesConnectable={false}
|
||||||
|
nodesDraggable={false}
|
||||||
|
panOnScroll={false}
|
||||||
|
zoomOnScroll={expanded}
|
||||||
|
preventScrolling={expanded}
|
||||||
|
zoomActivationKey={['Control', 'Meta', 'Ctrl', 'Shift', 'ShiftLeft']}
|
||||||
|
onlyRenderVisibleElements={true}
|
||||||
|
minZoom={0}
|
||||||
|
maxZoom={1.2}
|
||||||
|
{nodeTypes}
|
||||||
|
elementsSelectable={false}>
|
||||||
|
<Background bgColor='black' />
|
||||||
|
<Controls showLock={false} orientation='horizontal'>
|
||||||
|
<ControlButton on:click={expand}>
|
||||||
|
{#if expanded}
|
||||||
|
<Minimize2 />
|
||||||
|
{:else}
|
||||||
|
<Maximize2 />
|
||||||
|
{/if}
|
||||||
|
</ControlButton>
|
||||||
|
</Controls>
|
||||||
|
</SvelteFlow>
|
||||||
30
src/lib/components/ui/relations/TextNode.svelte
Normal file
30
src/lib/components/ui/relations/TextNode.svelte
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
<script lang='ts'>
|
||||||
|
|
||||||
|
import { Handle } from '@xyflow/svelte'
|
||||||
|
import { Position } from '@xyflow/system'
|
||||||
|
|
||||||
|
import { cn } from '$lib/utils'
|
||||||
|
|
||||||
|
export let data: { label: string, id: number, current?: boolean }
|
||||||
|
export let id: string
|
||||||
|
export let targetPosition: Position = Position.Top
|
||||||
|
export let sourcePosition: Position = Position.Bottom
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
||||||
|
$$restProps
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<a class={cn('node p-2.5 w-[150px] text-xs text-center border bg-[#1e1e1e] rounded-sm cursor-pointer block font-semibold transition-colors', data.current ? 'border-custom text-custom' : 'border-border text-white')} href='/app/anime/{data.id}'>
|
||||||
|
<div class='relative'>
|
||||||
|
<Handle type='target' position={targetPosition} />
|
||||||
|
{data?.label}
|
||||||
|
<Handle type='source' position={sourcePosition} />
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.node {
|
||||||
|
--xy-handle-background-color: none;
|
||||||
|
--xy-handle-border-color: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
2
src/lib/components/ui/relations/index.ts
Normal file
2
src/lib/components/ui/relations/index.ts
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
export { default as Relations } from './Relations.svelte'
|
||||||
|
export { default as TextNode } from './TextNode.svelte'
|
||||||
|
|
@ -1,13 +1,14 @@
|
||||||
import { queryStore, type OperationResultState, gql as _gql } from '@urql/svelte'
|
import { queryStore, type OperationResultState, gql as _gql } from '@urql/svelte'
|
||||||
import Debug from 'debug'
|
import Debug from 'debug'
|
||||||
import lavenshtein from 'js-levenshtein'
|
import lavenshtein from 'js-levenshtein'
|
||||||
import { derived, readable, writable, type Writable } from 'svelte/store'
|
import { derived, get, readable, writable, type Writable } from 'svelte/store'
|
||||||
|
|
||||||
import { Comments, DeleteEntry, DeleteThreadComment, Entry, Following, IDMedia, SaveThreadComment, Schedule, Search, Threads, ToggleFavourite, ToggleLike, UserLists } from './queries'
|
import { Comments, DeleteEntry, DeleteThreadComment, Entry, Following, type FullMedia, IDMedia, RecrusiveRelations, SaveThreadComment, Schedule, Search, Threads, ToggleFavourite, ToggleLike, UserLists } from './queries'
|
||||||
import urqlClient from './urql-client'
|
import urqlClient from './urql-client'
|
||||||
import { currentSeason, currentYear, lastSeason, lastYear, nextSeason, nextYear } from './util'
|
import { currentSeason, currentYear, lastSeason, lastYear, nextSeason, nextYear } from './util'
|
||||||
|
|
||||||
import type { Media } from './types'
|
import type { Media } from './types'
|
||||||
|
import type { Edge, Node } from '@xyflow/svelte'
|
||||||
import type { ResultOf, VariablesOf } from 'gql.tada'
|
import type { ResultOf, VariablesOf } from 'gql.tada'
|
||||||
import type { AnyVariables, OperationContext, RequestPolicy, TypedDocumentNode } from 'urql'
|
import type { AnyVariables, OperationContext, RequestPolicy, TypedDocumentNode } from 'urql'
|
||||||
|
|
||||||
|
|
@ -252,8 +253,82 @@ class AnilistClient {
|
||||||
debug('deleteComment: deleting comment with ID', id, 'rootCommentId', rootCommentId)
|
debug('deleteComment: deleting comment with ID', id, 'rootCommentId', rootCommentId)
|
||||||
return await this.client.mutation(DeleteThreadComment, { id, rootCommentId })
|
return await this.client.mutation(DeleteThreadComment, { id, rootCommentId })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_relationsTreeCache = new Map<number, RelationsStore>()
|
||||||
|
|
||||||
|
relationsTree (media: ResultOf<typeof FullMedia>): RelationsStore {
|
||||||
|
if (this._relationsTreeCache.has(media.id)) return this._relationsTreeCache.get(media.id)!
|
||||||
|
|
||||||
|
const store: RelationsStore = writable({ nodes: new Map(), edges: new Map() })
|
||||||
|
|
||||||
|
this._generateRelationsTree(store, media)
|
||||||
|
|
||||||
|
return store
|
||||||
|
}
|
||||||
|
|
||||||
|
async _generateRelationsTree (store: RelationsStore, media: NonNullable<NonNullable<ResultOf<typeof RecrusiveRelations>['Page']>['media']>[0]) {
|
||||||
|
const { nodes, edges } = get(store)
|
||||||
|
|
||||||
|
const startMedia = media
|
||||||
|
|
||||||
|
const position = { x: 0, y: 0 }
|
||||||
|
|
||||||
|
const lastEdgeMedia: number[] = []
|
||||||
|
|
||||||
|
const processEdges = (media: typeof startMedia) => {
|
||||||
|
if (!media) return
|
||||||
|
if ('type' in media && media.type !== 'ANIME') return
|
||||||
|
if (!nodes.has(media.id)) {
|
||||||
|
if (!media.relations) lastEdgeMedia.push(media.id)
|
||||||
|
nodes.set(media.id, {
|
||||||
|
id: '' + media.id,
|
||||||
|
data: { label: media.title?.userPreferred ?? 'No title', id: media.id },
|
||||||
|
position
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const edge of media.relations?.edges ?? []) {
|
||||||
|
if (!edge?.node) continue
|
||||||
|
const { node, relationType } = edge
|
||||||
|
if (node.type !== 'ANIME' || relationType === 'CHARACTER') continue
|
||||||
|
const edgeName = [node.id, media.id].sort((a, b) => a - b).join('-')
|
||||||
|
if (!edges.has(edgeName)) {
|
||||||
|
const isPrequel = relationType === 'PREQUEL'
|
||||||
|
edges.set(edgeName, {
|
||||||
|
id: 'e' + edgeName,
|
||||||
|
source: '' + (isPrequel ? node.id : media.id),
|
||||||
|
target: '' + (isPrequel ? media.id : node.id),
|
||||||
|
data: { ids: [media.id, node.id] },
|
||||||
|
animated: true,
|
||||||
|
label: isPrequel ? 'SEQUEL' : relationType?.replaceAll('_', ' ') ?? ''
|
||||||
|
})
|
||||||
|
|
||||||
|
// @ts-expect-error yeah recursive, last node has different types since it doesnt have relations
|
||||||
|
processEdges(node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const totalSize = nodes.size + edges.size
|
||||||
|
processEdges(startMedia)
|
||||||
|
|
||||||
|
for (const id of nodes.keys()) this._relationsTreeCache.set(id, store)
|
||||||
|
if (totalSize !== (nodes.size + edges.size)) store.set({ nodes, edges })
|
||||||
|
|
||||||
|
if (!lastEdgeMedia.length) return
|
||||||
|
const res = await this.client.query(RecrusiveRelations, { ids: lastEdgeMedia }, { requestPolicy: 'cache-first' })
|
||||||
|
if (res.error) console.error(res.error)
|
||||||
|
|
||||||
|
if (res.data?.Page) {
|
||||||
|
for (const media of res.data.Page.media ?? []) {
|
||||||
|
await this._generateRelationsTree(store, media)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type RelationsStore = Writable<{ nodes: Map<number, Node>, edges: Map<string, Edge> }>
|
||||||
|
|
||||||
// sveltekit/vite does the funny and evaluates at compile, this is a hack to fix development mode
|
// sveltekit/vite does the funny and evaluates at compile, this is a hack to fix development mode
|
||||||
const client = (typeof indexedDB !== 'undefined' && new AnilistClient()) as AnilistClient
|
const client = (typeof indexedDB !== 'undefined' && new AnilistClient()) as AnilistClient
|
||||||
|
|
||||||
|
|
|
||||||
13
src/lib/modules/anilist/graphql-turbo.d.ts
vendored
13
src/lib/modules/anilist/graphql-turbo.d.ts
vendored
|
|
@ -1,21 +1,22 @@
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
/* prettier-ignore */
|
/* prettier-ignore */
|
||||||
import type { TadaDocumentNode, $tada } from 'gql.tada';
|
import type { TadaDocumentNode, $tada } from 'gql.tada';
|
||||||
|
import type { introspection } from './graphql-env.d'
|
||||||
|
|
||||||
declare module 'gql.tada' {
|
declare module 'gql.tada' {
|
||||||
interface setupCache {
|
interface setupCache {
|
||||||
"\n fragment FullMediaList on MediaList @_unmask {\n id,\n status,\n progress,\n repeat,\n score(format: POINT_10),\n customLists(asArray: true)\n }\n":
|
"\n fragment FullMediaList on MediaList @_unmask {\n id,\n status,\n progress,\n repeat,\n score(format: POINT_10),\n customLists(asArray: true)\n }\n":
|
||||||
TadaDocumentNode<{ id: number; status: "CURRENT" | "PLANNING" | "COMPLETED" | "DROPPED" | "PAUSED" | "REPEATING" | null; progress: number | null; repeat: number | null; score: number | null; customLists: unknown; }, {}, { fragment: "FullMediaList"; on: "MediaList"; masked: false; }>;
|
TadaDocumentNode<{ id: number; status: "CURRENT" | "PLANNING" | "COMPLETED" | "DROPPED" | "PAUSED" | "REPEATING" | null; progress: number | null; repeat: number | null; score: number | null; customLists: unknown; }, {}, { fragment: "FullMediaList"; on: "MediaList"; masked: false; }>;
|
||||||
"\n fragment MediaEdgeFrag on MediaEdge @_unmask {\n relationType(version:2),\n node {\n id,\n title {userPreferred},\n coverImage {medium},\n type,\n status,\n format,\n episodes,\n synonyms,\n season,\n seasonYear,\n startDate {\n year,\n month,\n day\n },\n endDate {\n year,\n month,\n day\n }\n }\n }\n":
|
"\n fragment MediaEdgeFrag on MediaEdge @_unmask {\n relationType(version:2),\n node {\n id,\n title {userPreferred},\n coverImage {medium},\n type,\n status,\n format,\n episodes,\n synonyms,\n season,\n seasonYear,\n relations {\n edges {\n relationType(version:2),\n node {\n id,\n type,\n title { userPreferred },\n coverImage { medium }\n }\n }\n },\n startDate {\n year,\n month,\n day\n },\n endDate {\n year,\n month,\n day\n }\n }\n }\n":
|
||||||
TadaDocumentNode<{ relationType: "ADAPTATION" | "PREQUEL" | "SEQUEL" | "PARENT" | "SIDE_STORY" | "CHARACTER" | "SUMMARY" | "ALTERNATIVE" | "SPIN_OFF" | "OTHER" | "SOURCE" | "COMPILATION" | "CONTAINS" | null; node: { id: number; title: { userPreferred: string | null; } | null; coverImage: { medium: string | null; } | null; type: "ANIME" | "MANGA" | null; status: "FINISHED" | "RELEASING" | "NOT_YET_RELEASED" | "CANCELLED" | "HIATUS" | null; format: "MANGA" | "TV" | "TV_SHORT" | "MOVIE" | "SPECIAL" | "OVA" | "ONA" | "MUSIC" | "NOVEL" | "ONE_SHOT" | null; episodes: number | null; synonyms: (string | null)[] | null; season: "WINTER" | "SPRING" | "SUMMER" | "FALL" | null; seasonYear: number | null; startDate: { year: number | null; month: number | null; day: number | null; } | null; endDate: { year: number | null; month: number | null; day: number | null; } | null; } | null; }, {}, { fragment: "MediaEdgeFrag"; on: "MediaEdge"; masked: false; }>;
|
TadaDocumentNode<{ relationType: "ADAPTATION" | "PREQUEL" | "SEQUEL" | "PARENT" | "SIDE_STORY" | "CHARACTER" | "SUMMARY" | "ALTERNATIVE" | "SPIN_OFF" | "OTHER" | "SOURCE" | "COMPILATION" | "CONTAINS" | null; node: { id: number; title: { userPreferred: string | null; } | null; coverImage: { medium: string | null; } | null; type: "ANIME" | "MANGA" | null; status: "FINISHED" | "RELEASING" | "NOT_YET_RELEASED" | "CANCELLED" | "HIATUS" | null; format: "MANGA" | "TV" | "TV_SHORT" | "MOVIE" | "SPECIAL" | "OVA" | "ONA" | "MUSIC" | "NOVEL" | "ONE_SHOT" | null; episodes: number | null; synonyms: (string | null)[] | null; season: "WINTER" | "SPRING" | "SUMMER" | "FALL" | null; seasonYear: number | null; relations: { edges: ({ relationType: "ADAPTATION" | "PREQUEL" | "SEQUEL" | "PARENT" | "SIDE_STORY" | "CHARACTER" | "SUMMARY" | "ALTERNATIVE" | "SPIN_OFF" | "OTHER" | "SOURCE" | "COMPILATION" | "CONTAINS" | null; node: { id: number; type: "ANIME" | "MANGA" | null; title: { userPreferred: string | null; } | null; coverImage: { medium: string | null; } | null; } | null; } | null)[] | null; } | null; startDate: { year: number | null; month: number | null; day: number | null; } | null; endDate: { year: number | null; month: number | null; day: number | null; } | null; } | null; }, {}, { fragment: "MediaEdgeFrag"; on: "MediaEdge"; masked: false; }>;
|
||||||
"\n fragment FullMedia on Media @_unmask {\nid,\nidMal,\ntitle {\n romaji,\n english,\n native,\n userPreferred\n},\ndescription(asHtml: false),\nseason,\nseasonYear,\nformat,\nstatus,\nepisodes,\nduration,\naverageScore,\ngenres,\nisFavourite,\ncoverImage {\n extraLarge,\n medium,\n color,\n},\nsource,\ncountryOfOrigin,\nisAdult,\nbannerImage,\nsynonyms,\nnextAiringEpisode {\n id,\n timeUntilAiring,\n episode\n},\nstartDate {\n year,\n month,\n day\n},\ntrailer {\n id,\n site\n},\nmediaListEntry {\n ...FullMediaList\n},\nstudios(isMain: true) {\n nodes {\n id,\n name\n }\n},\nnotaired: airingSchedule(page: 1, perPage: 50, notYetAired: true) {\n n: nodes {\n a: airingAt,\n e: episode\n }\n},\naired: airingSchedule(page: 1, perPage: 50, notYetAired: false) {\n n: nodes {\n a: airingAt,\n e: episode\n }\n},\nrelations {\n edges {\n ...MediaEdgeFrag\n }\n}\n}":
|
"\n fragment FullMedia on Media @_unmask {\nid,\nidMal,\ntitle {\n romaji,\n english,\n native,\n userPreferred\n},\ndescription(asHtml: false),\nseason,\nseasonYear,\nformat,\nstatus,\nepisodes,\nduration,\naverageScore,\ngenres,\nisFavourite,\ncoverImage {\n extraLarge,\n medium,\n color,\n},\nsource,\ncountryOfOrigin,\nisAdult,\nbannerImage,\nsynonyms,\nnextAiringEpisode {\n id,\n timeUntilAiring,\n episode\n},\nstartDate {\n year,\n month,\n day\n},\ntrailer {\n id,\n site\n},\nmediaListEntry {\n ...FullMediaList\n},\nstudios(isMain: true) {\n nodes {\n id,\n name\n }\n},\nnotaired: airingSchedule(page: 1, perPage: 50, notYetAired: true) {\n n: nodes {\n a: airingAt,\n e: episode\n }\n},\naired: airingSchedule(page: 1, perPage: 50, notYetAired: false) {\n n: nodes {\n a: airingAt,\n e: episode\n }\n},\nrelations {\n edges {\n ...MediaEdgeFrag\n }\n}\n}":
|
||||||
TadaDocumentNode<{ id: number; idMal: number | null; title: { romaji: string | null; english: string | null; native: string | null; userPreferred: string | null; } | null; description: string | null; season: "WINTER" | "SPRING" | "SUMMER" | "FALL" | null; seasonYear: number | null; format: "MANGA" | "TV" | "TV_SHORT" | "MOVIE" | "SPECIAL" | "OVA" | "ONA" | "MUSIC" | "NOVEL" | "ONE_SHOT" | null; status: "FINISHED" | "RELEASING" | "NOT_YET_RELEASED" | "CANCELLED" | "HIATUS" | null; episodes: number | null; duration: number | null; averageScore: number | null; genres: (string | null)[] | null; isFavourite: boolean; coverImage: { extraLarge: string | null; medium: string | null; color: string | null; } | null; source: "OTHER" | "ANIME" | "MANGA" | "NOVEL" | "ORIGINAL" | "LIGHT_NOVEL" | "VISUAL_NOVEL" | "VIDEO_GAME" | "DOUJINSHI" | "WEB_NOVEL" | "LIVE_ACTION" | "GAME" | "COMIC" | "MULTIMEDIA_PROJECT" | "PICTURE_BOOK" | null; countryOfOrigin: unknown; isAdult: boolean | null; bannerImage: string | null; synonyms: (string | null)[] | null; nextAiringEpisode: { id: number; timeUntilAiring: number; episode: number; } | null; startDate: { year: number | null; month: number | null; day: number | null; } | null; trailer: { id: string | null; site: string | null; } | null; mediaListEntry: { id: number; status: "CURRENT" | "PLANNING" | "COMPLETED" | "DROPPED" | "PAUSED" | "REPEATING" | null; progress: number | null; repeat: number | null; score: number | null; customLists: unknown; } | null; studios: { nodes: ({ id: number; name: string; } | null)[] | null; } | null; notaired: { n: ({ a: number; e: number; } | null)[] | null; } | null; aired: { n: ({ a: number; e: number; } | null)[] | null; } | null; relations: { edges: ({ relationType: "ADAPTATION" | "PREQUEL" | "SEQUEL" | "PARENT" | "SIDE_STORY" | "CHARACTER" | "SUMMARY" | "ALTERNATIVE" | "SPIN_OFF" | "OTHER" | "SOURCE" | "COMPILATION" | "CONTAINS" | null; node: { id: number; title: { userPreferred: string | null; } | null; coverImage: { medium: string | null; } | null; type: "ANIME" | "MANGA" | null; status: "FINISHED" | "RELEASING" | "NOT_YET_RELEASED" | "CANCELLED" | "HIATUS" | null; format: "MANGA" | "TV" | "TV_SHORT" | "MOVIE" | "SPECIAL" | "OVA" | "ONA" | "MUSIC" | "NOVEL" | "ONE_SHOT" | null; episodes: number | null; synonyms: (string | null)[] | null; season: "WINTER" | "SPRING" | "SUMMER" | "FALL" | null; seasonYear: number | null; startDate: { year: number | null; month: number | null; day: number | null; } | null; endDate: { year: number | null; month: number | null; day: number | null; } | null; } | null; } | null)[] | null; } | null; }, {}, { fragment: "FullMedia"; on: "Media"; masked: false; }>;
|
TadaDocumentNode<{ id: number; idMal: number | null; title: { romaji: string | null; english: string | null; native: string | null; userPreferred: string | null; } | null; description: string | null; season: "WINTER" | "SPRING" | "SUMMER" | "FALL" | null; seasonYear: number | null; format: "MANGA" | "TV" | "TV_SHORT" | "MOVIE" | "SPECIAL" | "OVA" | "ONA" | "MUSIC" | "NOVEL" | "ONE_SHOT" | null; status: "FINISHED" | "RELEASING" | "NOT_YET_RELEASED" | "CANCELLED" | "HIATUS" | null; episodes: number | null; duration: number | null; averageScore: number | null; genres: (string | null)[] | null; isFavourite: boolean; coverImage: { extraLarge: string | null; medium: string | null; color: string | null; } | null; source: "OTHER" | "ANIME" | "MANGA" | "NOVEL" | "ORIGINAL" | "LIGHT_NOVEL" | "VISUAL_NOVEL" | "VIDEO_GAME" | "DOUJINSHI" | "WEB_NOVEL" | "LIVE_ACTION" | "GAME" | "COMIC" | "MULTIMEDIA_PROJECT" | "PICTURE_BOOK" | null; countryOfOrigin: unknown; isAdult: boolean | null; bannerImage: string | null; synonyms: (string | null)[] | null; nextAiringEpisode: { id: number; timeUntilAiring: number; episode: number; } | null; startDate: { year: number | null; month: number | null; day: number | null; } | null; trailer: { id: string | null; site: string | null; } | null; mediaListEntry: { id: number; status: "CURRENT" | "PLANNING" | "COMPLETED" | "DROPPED" | "PAUSED" | "REPEATING" | null; progress: number | null; repeat: number | null; score: number | null; customLists: unknown; } | null; studios: { nodes: ({ id: number; name: string; } | null)[] | null; } | null; notaired: { n: ({ a: number; e: number; } | null)[] | null; } | null; aired: { n: ({ a: number; e: number; } | null)[] | null; } | null; relations: { edges: ({ relationType: "ADAPTATION" | "PREQUEL" | "SEQUEL" | "PARENT" | "SIDE_STORY" | "CHARACTER" | "SUMMARY" | "ALTERNATIVE" | "SPIN_OFF" | "OTHER" | "SOURCE" | "COMPILATION" | "CONTAINS" | null; node: { id: number; title: { userPreferred: string | null; } | null; coverImage: { medium: string | null; } | null; type: "ANIME" | "MANGA" | null; status: "FINISHED" | "RELEASING" | "NOT_YET_RELEASED" | "CANCELLED" | "HIATUS" | null; format: "MANGA" | "TV" | "TV_SHORT" | "MOVIE" | "SPECIAL" | "OVA" | "ONA" | "MUSIC" | "NOVEL" | "ONE_SHOT" | null; episodes: number | null; synonyms: (string | null)[] | null; season: "WINTER" | "SPRING" | "SUMMER" | "FALL" | null; seasonYear: number | null; relations: { edges: ({ relationType: "ADAPTATION" | "PREQUEL" | "SEQUEL" | "PARENT" | "SIDE_STORY" | "CHARACTER" | "SUMMARY" | "ALTERNATIVE" | "SPIN_OFF" | "OTHER" | "SOURCE" | "COMPILATION" | "CONTAINS" | null; node: { id: number; type: "ANIME" | "MANGA" | null; title: { userPreferred: string | null; } | null; coverImage: { medium: string | null; } | null; } | null; } | null)[] | null; } | null; startDate: { year: number | null; month: number | null; day: number | null; } | null; endDate: { year: number | null; month: number | null; day: number | null; } | null; } | null; } | null)[] | null; } | null; }, {}, { fragment: "FullMedia"; on: "Media"; masked: false; }>;
|
||||||
"\n fragment UserFrag on User @_unmask {\n id,\n bannerImage,\n about,\n isFollowing,\n isFollower,\n donatorBadge,\n options {\n profileColor\n },\n createdAt,\n name,\n avatar {\n large\n },\n statistics {\n anime {\n count,\n minutesWatched,\n episodesWatched,\n genres(limit: 3, sort: COUNT_DESC) {\n genre,\n count\n }\n }\n }\n }\n":
|
"\n fragment UserFrag on User @_unmask {\n id,\n bannerImage,\n about,\n isFollowing,\n isFollower,\n donatorBadge,\n options {\n profileColor\n },\n createdAt,\n name,\n avatar {\n large\n },\n statistics {\n anime {\n count,\n minutesWatched,\n episodesWatched,\n genres(limit: 3, sort: COUNT_DESC) {\n genre,\n count\n }\n }\n }\n }\n":
|
||||||
TadaDocumentNode<{ id: number; bannerImage: string | null; about: string | null; isFollowing: boolean | null; isFollower: boolean | null; donatorBadge: string | null; options: { profileColor: string | null; } | null; createdAt: number | null; name: string; avatar: { large: string | null; } | null; statistics: { anime: { count: number; minutesWatched: number; episodesWatched: number; genres: ({ genre: string | null; count: number; } | null)[] | null; } | null; } | null; }, {}, { fragment: "UserFrag"; on: "User"; masked: false; }>;
|
TadaDocumentNode<{ id: number; bannerImage: string | null; about: string | null; isFollowing: boolean | null; isFollower: boolean | null; donatorBadge: string | null; options: { profileColor: string | null; } | null; createdAt: number | null; name: string; avatar: { large: string | null; } | null; statistics: { anime: { count: number; minutesWatched: number; episodesWatched: number; genres: ({ genre: string | null; count: number; } | null)[] | null; } | null; } | null; }, {}, { fragment: "UserFrag"; on: "User"; masked: false; }>;
|
||||||
"\n query Search($page: Int, $perPage: Int, $search: String, $genre: [String], $format: [MediaFormat], $status: [MediaStatus], $statusNot: [MediaStatus], $season: MediaSeason, $seasonYear: Int, $isAdult: Boolean, $sort: [MediaSort], $onList: Boolean, $ids: [Int]) {\n Page(page: $page, perPage: $perPage) {\n pageInfo {\n hasNextPage\n },\n media(type: ANIME, format_not: MUSIC, id_in: $ids, search: $search, genre_in: $genre, format_in: $format, status_in: $status, status_not_in: $statusNot, season: $season, seasonYear: $seasonYear, isAdult: $isAdult, sort: $sort, onList: $onList) {\n ...FullMedia\n }\n }\n }\n":
|
"\n query Search($page: Int, $perPage: Int, $search: String, $genre: [String], $format: [MediaFormat], $status: [MediaStatus], $statusNot: [MediaStatus], $season: MediaSeason, $seasonYear: Int, $isAdult: Boolean, $sort: [MediaSort], $onList: Boolean, $ids: [Int]) {\n Page(page: $page, perPage: $perPage) {\n pageInfo {\n hasNextPage\n },\n media(type: ANIME, format_not: MUSIC, id_in: $ids, search: $search, genre_in: $genre, format_in: $format, status_in: $status, status_not_in: $statusNot, season: $season, seasonYear: $seasonYear, isAdult: $isAdult, sort: $sort, onList: $onList) {\n ...FullMedia\n }\n }\n }\n":
|
||||||
TadaDocumentNode<{ Page: { pageInfo: { hasNextPage: boolean | null; } | null; media: ({ id: number; idMal: number | null; title: { romaji: string | null; english: string | null; native: string | null; userPreferred: string | null; } | null; description: string | null; season: "WINTER" | "SPRING" | "SUMMER" | "FALL" | null; seasonYear: number | null; format: "MANGA" | "TV" | "TV_SHORT" | "MOVIE" | "SPECIAL" | "OVA" | "ONA" | "MUSIC" | "NOVEL" | "ONE_SHOT" | null; status: "FINISHED" | "RELEASING" | "NOT_YET_RELEASED" | "CANCELLED" | "HIATUS" | null; episodes: number | null; duration: number | null; averageScore: number | null; genres: (string | null)[] | null; isFavourite: boolean; coverImage: { extraLarge: string | null; medium: string | null; color: string | null; } | null; source: "OTHER" | "ANIME" | "MANGA" | "NOVEL" | "ORIGINAL" | "LIGHT_NOVEL" | "VISUAL_NOVEL" | "VIDEO_GAME" | "DOUJINSHI" | "WEB_NOVEL" | "LIVE_ACTION" | "GAME" | "COMIC" | "MULTIMEDIA_PROJECT" | "PICTURE_BOOK" | null; countryOfOrigin: unknown; isAdult: boolean | null; bannerImage: string | null; synonyms: (string | null)[] | null; nextAiringEpisode: { id: number; timeUntilAiring: number; episode: number; } | null; startDate: { year: number | null; month: number | null; day: number | null; } | null; trailer: { id: string | null; site: string | null; } | null; mediaListEntry: { id: number; status: "CURRENT" | "PLANNING" | "COMPLETED" | "DROPPED" | "PAUSED" | "REPEATING" | null; progress: number | null; repeat: number | null; score: number | null; customLists: unknown; } | null; studios: { nodes: ({ id: number; name: string; } | null)[] | null; } | null; notaired: { n: ({ a: number; e: number; } | null)[] | null; } | null; aired: { n: ({ a: number; e: number; } | null)[] | null; } | null; relations: { edges: ({ relationType: "ADAPTATION" | "PREQUEL" | "SEQUEL" | "PARENT" | "SIDE_STORY" | "CHARACTER" | "SUMMARY" | "ALTERNATIVE" | "SPIN_OFF" | "OTHER" | "SOURCE" | "COMPILATION" | "CONTAINS" | null; node: { id: number; title: { userPreferred: string | null; } | null; coverImage: { medium: string | null; } | null; type: "ANIME" | "MANGA" | null; status: "FINISHED" | "RELEASING" | "NOT_YET_RELEASED" | "CANCELLED" | "HIATUS" | null; format: "MANGA" | "TV" | "TV_SHORT" | "MOVIE" | "SPECIAL" | "OVA" | "ONA" | "MUSIC" | "NOVEL" | "ONE_SHOT" | null; episodes: number | null; synonyms: (string | null)[] | null; season: "WINTER" | "SPRING" | "SUMMER" | "FALL" | null; seasonYear: number | null; startDate: { year: number | null; month: number | null; day: number | null; } | null; endDate: { year: number | null; month: number | null; day: number | null; } | null; } | null; } | null)[] | null; } | null; } | null)[] | null; } | null; }, { ids?: (number | null)[] | null | undefined; onList?: boolean | null | undefined; sort?: ("ID" | "ID_DESC" | "TITLE_ROMAJI" | "TITLE_ROMAJI_DESC" | "TITLE_ENGLISH" | "TITLE_ENGLISH_DESC" | "TITLE_NATIVE" | "TITLE_NATIVE_DESC" | "TYPE" | "TYPE_DESC" | "FORMAT" | "FORMAT_DESC" | "START_DATE" | "START_DATE_DESC" | "END_DATE" | "END_DATE_DESC" | "SCORE" | "SCORE_DESC" | "POPULARITY" | "POPULARITY_DESC" | "TRENDING" | "TRENDING_DESC" | "EPISODES" | "EPISODES_DESC" | "DURATION" | "DURATION_DESC" | "STATUS" | "STATUS_DESC" | "CHAPTERS" | "CHAPTERS_DESC" | "VOLUMES" | "VOLUMES_DESC" | "UPDATED_AT" | "UPDATED_AT_DESC" | "SEARCH_MATCH" | "FAVOURITES" | "FAVOURITES_DESC" | null)[] | null | undefined; isAdult?: boolean | null | undefined; seasonYear?: number | null | undefined; season?: "WINTER" | "SPRING" | "SUMMER" | "FALL" | null | undefined; statusNot?: ("FINISHED" | "RELEASING" | "NOT_YET_RELEASED" | "CANCELLED" | "HIATUS" | null)[] | null | undefined; status?: ("FINISHED" | "RELEASING" | "NOT_YET_RELEASED" | "CANCELLED" | "HIATUS" | null)[] | null | undefined; format?: ("MANGA" | "TV" | "TV_SHORT" | "MOVIE" | "SPECIAL" | "OVA" | "ONA" | "MUSIC" | "NOVEL" | "ONE_SHOT" | null)[] | null | undefined; genre?: (string | null)[] | null | undefined; search?: string | null | undefined; perPage?: number | null | undefined; page?: number | null | undefined; }, void>;
|
TadaDocumentNode<{ Page: { pageInfo: { hasNextPage: boolean | null; } | null; media: ({ id: number; idMal: number | null; title: { romaji: string | null; english: string | null; native: string | null; userPreferred: string | null; } | null; description: string | null; season: "WINTER" | "SPRING" | "SUMMER" | "FALL" | null; seasonYear: number | null; format: "MANGA" | "TV" | "TV_SHORT" | "MOVIE" | "SPECIAL" | "OVA" | "ONA" | "MUSIC" | "NOVEL" | "ONE_SHOT" | null; status: "FINISHED" | "RELEASING" | "NOT_YET_RELEASED" | "CANCELLED" | "HIATUS" | null; episodes: number | null; duration: number | null; averageScore: number | null; genres: (string | null)[] | null; isFavourite: boolean; coverImage: { extraLarge: string | null; medium: string | null; color: string | null; } | null; source: "OTHER" | "ANIME" | "MANGA" | "NOVEL" | "ORIGINAL" | "LIGHT_NOVEL" | "VISUAL_NOVEL" | "VIDEO_GAME" | "DOUJINSHI" | "WEB_NOVEL" | "LIVE_ACTION" | "GAME" | "COMIC" | "MULTIMEDIA_PROJECT" | "PICTURE_BOOK" | null; countryOfOrigin: unknown; isAdult: boolean | null; bannerImage: string | null; synonyms: (string | null)[] | null; nextAiringEpisode: { id: number; timeUntilAiring: number; episode: number; } | null; startDate: { year: number | null; month: number | null; day: number | null; } | null; trailer: { id: string | null; site: string | null; } | null; mediaListEntry: { id: number; status: "CURRENT" | "PLANNING" | "COMPLETED" | "DROPPED" | "PAUSED" | "REPEATING" | null; progress: number | null; repeat: number | null; score: number | null; customLists: unknown; } | null; studios: { nodes: ({ id: number; name: string; } | null)[] | null; } | null; notaired: { n: ({ a: number; e: number; } | null)[] | null; } | null; aired: { n: ({ a: number; e: number; } | null)[] | null; } | null; relations: { edges: ({ relationType: "ADAPTATION" | "PREQUEL" | "SEQUEL" | "PARENT" | "SIDE_STORY" | "CHARACTER" | "SUMMARY" | "ALTERNATIVE" | "SPIN_OFF" | "OTHER" | "SOURCE" | "COMPILATION" | "CONTAINS" | null; node: { id: number; title: { userPreferred: string | null; } | null; coverImage: { medium: string | null; } | null; type: "ANIME" | "MANGA" | null; status: "FINISHED" | "RELEASING" | "NOT_YET_RELEASED" | "CANCELLED" | "HIATUS" | null; format: "MANGA" | "TV" | "TV_SHORT" | "MOVIE" | "SPECIAL" | "OVA" | "ONA" | "MUSIC" | "NOVEL" | "ONE_SHOT" | null; episodes: number | null; synonyms: (string | null)[] | null; season: "WINTER" | "SPRING" | "SUMMER" | "FALL" | null; seasonYear: number | null; relations: { edges: ({ relationType: "ADAPTATION" | "PREQUEL" | "SEQUEL" | "PARENT" | "SIDE_STORY" | "CHARACTER" | "SUMMARY" | "ALTERNATIVE" | "SPIN_OFF" | "OTHER" | "SOURCE" | "COMPILATION" | "CONTAINS" | null; node: { id: number; type: "ANIME" | "MANGA" | null; title: { userPreferred: string | null; } | null; coverImage: { medium: string | null; } | null; } | null; } | null)[] | null; } | null; startDate: { year: number | null; month: number | null; day: number | null; } | null; endDate: { year: number | null; month: number | null; day: number | null; } | null; } | null; } | null)[] | null; } | null; } | null)[] | null; } | null; }, { ids?: (number | null)[] | null | undefined; onList?: boolean | null | undefined; sort?: ("ID" | "ID_DESC" | "TITLE_ROMAJI" | "TITLE_ROMAJI_DESC" | "TITLE_ENGLISH" | "TITLE_ENGLISH_DESC" | "TITLE_NATIVE" | "TITLE_NATIVE_DESC" | "TYPE" | "TYPE_DESC" | "FORMAT" | "FORMAT_DESC" | "START_DATE" | "START_DATE_DESC" | "END_DATE" | "END_DATE_DESC" | "SCORE" | "SCORE_DESC" | "POPULARITY" | "POPULARITY_DESC" | "TRENDING" | "TRENDING_DESC" | "EPISODES" | "EPISODES_DESC" | "DURATION" | "DURATION_DESC" | "STATUS" | "STATUS_DESC" | "CHAPTERS" | "CHAPTERS_DESC" | "VOLUMES" | "VOLUMES_DESC" | "UPDATED_AT" | "UPDATED_AT_DESC" | "SEARCH_MATCH" | "FAVOURITES" | "FAVOURITES_DESC" | null)[] | null | undefined; isAdult?: boolean | null | undefined; seasonYear?: number | null | undefined; season?: "WINTER" | "SPRING" | "SUMMER" | "FALL" | null | undefined; statusNot?: ("FINISHED" | "RELEASING" | "NOT_YET_RELEASED" | "CANCELLED" | "HIATUS" | null)[] | null | undefined; status?: ("FINISHED" | "RELEASING" | "NOT_YET_RELEASED" | "CANCELLED" | "HIATUS" | null)[] | null | undefined; format?: ("MANGA" | "TV" | "TV_SHORT" | "MOVIE" | "SPECIAL" | "OVA" | "ONA" | "MUSIC" | "NOVEL" | "ONE_SHOT" | null)[] | null | undefined; genre?: (string | null)[] | null | undefined; search?: string | null | undefined; perPage?: number | null | undefined; page?: number | null | undefined; }, void>;
|
||||||
"\n query IDMedia($id: Int!) {\n Media(id: $id, type: ANIME) {\n ...FullMedia\n }\n }\n":
|
"\n query IDMedia($id: Int!) {\n Media(id: $id, type: ANIME) {\n ...FullMedia\n }\n }\n":
|
||||||
TadaDocumentNode<{ Media: { id: number; idMal: number | null; title: { romaji: string | null; english: string | null; native: string | null; userPreferred: string | null; } | null; description: string | null; season: "WINTER" | "SPRING" | "SUMMER" | "FALL" | null; seasonYear: number | null; format: "MANGA" | "TV" | "TV_SHORT" | "MOVIE" | "SPECIAL" | "OVA" | "ONA" | "MUSIC" | "NOVEL" | "ONE_SHOT" | null; status: "FINISHED" | "RELEASING" | "NOT_YET_RELEASED" | "CANCELLED" | "HIATUS" | null; episodes: number | null; duration: number | null; averageScore: number | null; genres: (string | null)[] | null; isFavourite: boolean; coverImage: { extraLarge: string | null; medium: string | null; color: string | null; } | null; source: "OTHER" | "ANIME" | "MANGA" | "NOVEL" | "ORIGINAL" | "LIGHT_NOVEL" | "VISUAL_NOVEL" | "VIDEO_GAME" | "DOUJINSHI" | "WEB_NOVEL" | "LIVE_ACTION" | "GAME" | "COMIC" | "MULTIMEDIA_PROJECT" | "PICTURE_BOOK" | null; countryOfOrigin: unknown; isAdult: boolean | null; bannerImage: string | null; synonyms: (string | null)[] | null; nextAiringEpisode: { id: number; timeUntilAiring: number; episode: number; } | null; startDate: { year: number | null; month: number | null; day: number | null; } | null; trailer: { id: string | null; site: string | null; } | null; mediaListEntry: { id: number; status: "CURRENT" | "PLANNING" | "COMPLETED" | "DROPPED" | "PAUSED" | "REPEATING" | null; progress: number | null; repeat: number | null; score: number | null; customLists: unknown; } | null; studios: { nodes: ({ id: number; name: string; } | null)[] | null; } | null; notaired: { n: ({ a: number; e: number; } | null)[] | null; } | null; aired: { n: ({ a: number; e: number; } | null)[] | null; } | null; relations: { edges: ({ relationType: "ADAPTATION" | "PREQUEL" | "SEQUEL" | "PARENT" | "SIDE_STORY" | "CHARACTER" | "SUMMARY" | "ALTERNATIVE" | "SPIN_OFF" | "OTHER" | "SOURCE" | "COMPILATION" | "CONTAINS" | null; node: { id: number; title: { userPreferred: string | null; } | null; coverImage: { medium: string | null; } | null; type: "ANIME" | "MANGA" | null; status: "FINISHED" | "RELEASING" | "NOT_YET_RELEASED" | "CANCELLED" | "HIATUS" | null; format: "MANGA" | "TV" | "TV_SHORT" | "MOVIE" | "SPECIAL" | "OVA" | "ONA" | "MUSIC" | "NOVEL" | "ONE_SHOT" | null; episodes: number | null; synonyms: (string | null)[] | null; season: "WINTER" | "SPRING" | "SUMMER" | "FALL" | null; seasonYear: number | null; startDate: { year: number | null; month: number | null; day: number | null; } | null; endDate: { year: number | null; month: number | null; day: number | null; } | null; } | null; } | null)[] | null; } | null; } | null; }, { id: number; }, void>;
|
TadaDocumentNode<{ Media: { id: number; idMal: number | null; title: { romaji: string | null; english: string | null; native: string | null; userPreferred: string | null; } | null; description: string | null; season: "WINTER" | "SPRING" | "SUMMER" | "FALL" | null; seasonYear: number | null; format: "MANGA" | "TV" | "TV_SHORT" | "MOVIE" | "SPECIAL" | "OVA" | "ONA" | "MUSIC" | "NOVEL" | "ONE_SHOT" | null; status: "FINISHED" | "RELEASING" | "NOT_YET_RELEASED" | "CANCELLED" | "HIATUS" | null; episodes: number | null; duration: number | null; averageScore: number | null; genres: (string | null)[] | null; isFavourite: boolean; coverImage: { extraLarge: string | null; medium: string | null; color: string | null; } | null; source: "OTHER" | "ANIME" | "MANGA" | "NOVEL" | "ORIGINAL" | "LIGHT_NOVEL" | "VISUAL_NOVEL" | "VIDEO_GAME" | "DOUJINSHI" | "WEB_NOVEL" | "LIVE_ACTION" | "GAME" | "COMIC" | "MULTIMEDIA_PROJECT" | "PICTURE_BOOK" | null; countryOfOrigin: unknown; isAdult: boolean | null; bannerImage: string | null; synonyms: (string | null)[] | null; nextAiringEpisode: { id: number; timeUntilAiring: number; episode: number; } | null; startDate: { year: number | null; month: number | null; day: number | null; } | null; trailer: { id: string | null; site: string | null; } | null; mediaListEntry: { id: number; status: "CURRENT" | "PLANNING" | "COMPLETED" | "DROPPED" | "PAUSED" | "REPEATING" | null; progress: number | null; repeat: number | null; score: number | null; customLists: unknown; } | null; studios: { nodes: ({ id: number; name: string; } | null)[] | null; } | null; notaired: { n: ({ a: number; e: number; } | null)[] | null; } | null; aired: { n: ({ a: number; e: number; } | null)[] | null; } | null; relations: { edges: ({ relationType: "ADAPTATION" | "PREQUEL" | "SEQUEL" | "PARENT" | "SIDE_STORY" | "CHARACTER" | "SUMMARY" | "ALTERNATIVE" | "SPIN_OFF" | "OTHER" | "SOURCE" | "COMPILATION" | "CONTAINS" | null; node: { id: number; title: { userPreferred: string | null; } | null; coverImage: { medium: string | null; } | null; type: "ANIME" | "MANGA" | null; status: "FINISHED" | "RELEASING" | "NOT_YET_RELEASED" | "CANCELLED" | "HIATUS" | null; format: "MANGA" | "TV" | "TV_SHORT" | "MOVIE" | "SPECIAL" | "OVA" | "ONA" | "MUSIC" | "NOVEL" | "ONE_SHOT" | null; episodes: number | null; synonyms: (string | null)[] | null; season: "WINTER" | "SPRING" | "SUMMER" | "FALL" | null; seasonYear: number | null; relations: { edges: ({ relationType: "ADAPTATION" | "PREQUEL" | "SEQUEL" | "PARENT" | "SIDE_STORY" | "CHARACTER" | "SUMMARY" | "ALTERNATIVE" | "SPIN_OFF" | "OTHER" | "SOURCE" | "COMPILATION" | "CONTAINS" | null; node: { id: number; type: "ANIME" | "MANGA" | null; title: { userPreferred: string | null; } | null; coverImage: { medium: string | null; } | null; } | null; } | null)[] | null; } | null; startDate: { year: number | null; month: number | null; day: number | null; } | null; endDate: { year: number | null; month: number | null; day: number | null; } | null; } | null; } | null)[] | null; } | null; } | null; }, { id: number; }, void>;
|
||||||
"\n query Viewer {\n Viewer {\n ...UserFrag,\n mediaListOptions {\n animeList {\n customLists\n }\n }\n }\n }\n":
|
"\n query Viewer {\n Viewer {\n ...UserFrag,\n mediaListOptions {\n animeList {\n customLists\n }\n }\n }\n }\n":
|
||||||
TadaDocumentNode<{ Viewer: { id: number; bannerImage: string | null; about: string | null; isFollowing: boolean | null; isFollower: boolean | null; donatorBadge: string | null; options: { profileColor: string | null; } | null; createdAt: number | null; name: string; avatar: { large: string | null; } | null; statistics: { anime: { count: number; minutesWatched: number; episodesWatched: number; genres: ({ genre: string | null; count: number; } | null)[] | null; } | null; } | null; mediaListOptions: { animeList: { customLists: (string | null)[] | null; } | null; } | null; } | null; }, {}, void>;
|
TadaDocumentNode<{ Viewer: { id: number; bannerImage: string | null; about: string | null; isFollowing: boolean | null; isFollower: boolean | null; donatorBadge: string | null; options: { profileColor: string | null; } | null; createdAt: number | null; name: string; avatar: { large: string | null; } | null; statistics: { anime: { count: number; minutesWatched: number; episodesWatched: number; genres: ({ genre: string | null; count: number; } | null)[] | null; } | null; } | null; mediaListOptions: { animeList: { customLists: (string | null)[] | null; } | null; } | null; } | null; }, {}, void>;
|
||||||
"\n query UserLists($id: Int) {\n MediaListCollection(userId: $id, type: ANIME, forceSingleCompletedList: true, sort: UPDATED_TIME_DESC) {\n user {\n id\n }\n lists {\n status,\n entries {\n id,\n media {\n id,\n status,\n mediaListEntry {\n ...FullMediaList\n },\n nextAiringEpisode {\n episode\n },\n relations {\n edges {\n relationType(version:2)\n node {\n id\n }\n }\n }\n }\n }\n }\n }\n }\n":
|
"\n query UserLists($id: Int) {\n MediaListCollection(userId: $id, type: ANIME, forceSingleCompletedList: true, sort: UPDATED_TIME_DESC) {\n user {\n id\n }\n lists {\n status,\n entries {\n id,\n media {\n id,\n status,\n mediaListEntry {\n ...FullMediaList\n },\n nextAiringEpisode {\n episode\n },\n relations {\n edges {\n relationType(version:2)\n node {\n id\n }\n }\n }\n }\n }\n }\n }\n }\n":
|
||||||
|
|
@ -50,6 +51,8 @@ declare module 'gql.tada' {
|
||||||
TadaDocumentNode<{ SaveThreadComment: { id: number; comment: string | null; isLiked: boolean | null; likeCount: number; createdAt: number; user: { id: number; bannerImage: string | null; about: string | null; isFollowing: boolean | null; isFollower: boolean | null; donatorBadge: string | null; options: { profileColor: string | null; } | null; createdAt: number | null; name: string; avatar: { large: string | null; } | null; statistics: { anime: { count: number; minutesWatched: number; episodesWatched: number; genres: ({ genre: string | null; count: number; } | null)[] | null; } | null; } | null; } | null; childComments: unknown; isLocked: boolean | null; } | null; }, { comment?: string | null | undefined; parentCommentId?: number | null | undefined; threadId?: number | null | undefined; id?: number | null | undefined; }, void>;
|
TadaDocumentNode<{ SaveThreadComment: { id: number; comment: string | null; isLiked: boolean | null; likeCount: number; createdAt: number; user: { id: number; bannerImage: string | null; about: string | null; isFollowing: boolean | null; isFollower: boolean | null; donatorBadge: string | null; options: { profileColor: string | null; } | null; createdAt: number | null; name: string; avatar: { large: string | null; } | null; statistics: { anime: { count: number; minutesWatched: number; episodesWatched: number; genres: ({ genre: string | null; count: number; } | null)[] | null; } | null; } | null; } | null; childComments: unknown; isLocked: boolean | null; } | null; }, { comment?: string | null | undefined; parentCommentId?: number | null | undefined; threadId?: number | null | undefined; id?: number | null | undefined; }, void>;
|
||||||
"\n mutation DeleteThreadComment ($id: Int) {\n DeleteThreadComment(id: $id) {\n deleted\n }\n }\n":
|
"\n mutation DeleteThreadComment ($id: Int) {\n DeleteThreadComment(id: $id) {\n deleted\n }\n }\n":
|
||||||
TadaDocumentNode<{ DeleteThreadComment: { deleted: boolean | null; } | null; }, { id?: number | null | undefined; }, void>;
|
TadaDocumentNode<{ DeleteThreadComment: { deleted: boolean | null; } | null; }, { id?: number | null | undefined; }, void>;
|
||||||
|
"\n query RecrusiveRelations ($ids: [Int]!) {\n Page {\n pageInfo { hasNextPage },\n media(id_in: $ids, type: ANIME) {\n id,\n title { userPreferred },\n relations {\n edges {\n relationType,\n node {\n id,\n type,\n title { userPreferred },\n relations {\n edges {\n relationType,\n node {\n id,\n type,\n title { userPreferred }\n }\n }\n }\n }\n }\n }\n }\n }\n }\n":
|
||||||
|
TadaDocumentNode<{ Page: { pageInfo: { hasNextPage: boolean | null; } | null; media: ({ id: number; title: { userPreferred: string | null; } | null; relations: { edges: ({ relationType: "ADAPTATION" | "PREQUEL" | "SEQUEL" | "PARENT" | "SIDE_STORY" | "CHARACTER" | "SUMMARY" | "ALTERNATIVE" | "SPIN_OFF" | "OTHER" | "SOURCE" | "COMPILATION" | "CONTAINS" | null; node: { id: number; type: "ANIME" | "MANGA" | null; title: { userPreferred: string | null; } | null; relations: { edges: ({ relationType: "ADAPTATION" | "PREQUEL" | "SEQUEL" | "PARENT" | "SIDE_STORY" | "CHARACTER" | "SUMMARY" | "ALTERNATIVE" | "SPIN_OFF" | "OTHER" | "SOURCE" | "COMPILATION" | "CONTAINS" | null; node: { id: number; type: "ANIME" | "MANGA" | null; title: { userPreferred: string | null; } | null; } | null; } | null)[] | null; } | null; } | null; } | null)[] | null; } | null; } | null)[] | null; } | null; }, { ids: (number | null)[]; }, void>;
|
||||||
"fragment Med on Media {id, isFavourite}":
|
"fragment Med on Media {id, isFavourite}":
|
||||||
TadaDocumentNode<{ id: number; isFavourite: boolean; }, {}, { fragment: "Med"; on: "Media"; masked: true; }>;
|
TadaDocumentNode<{ id: number; isFavourite: boolean; }, {}, { fragment: "Med"; on: "Media"; masked: true; }>;
|
||||||
"fragment Med on Media {id, mediaListEntry {status, progress, repeat, score, customLists }}":
|
"fragment Med on Media {id, mediaListEntry {status, progress, repeat, score, customLists }}":
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,17 @@ export const MediaEdgeFrag = gql(`
|
||||||
synonyms,
|
synonyms,
|
||||||
season,
|
season,
|
||||||
seasonYear,
|
seasonYear,
|
||||||
|
relations {
|
||||||
|
edges {
|
||||||
|
relationType(version:2),
|
||||||
|
node {
|
||||||
|
id,
|
||||||
|
type,
|
||||||
|
title { userPreferred },
|
||||||
|
coverImage { medium }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
startDate {
|
startDate {
|
||||||
year,
|
year,
|
||||||
month,
|
month,
|
||||||
|
|
@ -461,3 +472,35 @@ export const DeleteThreadComment = gql(`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`)
|
`)
|
||||||
|
|
||||||
|
export const RecrusiveRelations = gql(`
|
||||||
|
query RecrusiveRelations ($ids: [Int]!) {
|
||||||
|
Page {
|
||||||
|
pageInfo { hasNextPage },
|
||||||
|
media(id_in: $ids, type: ANIME) {
|
||||||
|
id,
|
||||||
|
title { userPreferred },
|
||||||
|
relations {
|
||||||
|
edges {
|
||||||
|
relationType,
|
||||||
|
node {
|
||||||
|
id,
|
||||||
|
type,
|
||||||
|
title { userPreferred },
|
||||||
|
relations {
|
||||||
|
edges {
|
||||||
|
relationType,
|
||||||
|
node {
|
||||||
|
id,
|
||||||
|
type,
|
||||||
|
title { userPreferred }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
|
|
||||||
import type { LayoutData } from './$types'
|
import type { LayoutData } from './$types'
|
||||||
|
|
||||||
import { goto, onNavigate } from '$app/navigation'
|
import { goto } from '$app/navigation'
|
||||||
import EntryEditor from '$lib/components/EntryEditor.svelte'
|
import EntryEditor from '$lib/components/EntryEditor.svelte'
|
||||||
import Anilist from '$lib/components/icons/Anilist.svelte'
|
import Anilist from '$lib/components/icons/Anilist.svelte'
|
||||||
import MyAnimeList from '$lib/components/icons/MyAnimeList.svelte'
|
import MyAnimeList from '$lib/components/icons/MyAnimeList.svelte'
|
||||||
|
|
@ -66,17 +66,10 @@
|
||||||
|
|
||||||
let container: HTMLDivElement
|
let container: HTMLDivElement
|
||||||
|
|
||||||
onNavigate(() => {
|
|
||||||
container.scrollTo({
|
|
||||||
top: 0,
|
|
||||||
behavior: 'smooth'
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
$: ({ r, g, b } = colors(media.coverImage?.color ?? undefined))
|
$: ({ r, g, b } = colors(media.coverImage?.color ?? undefined))
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class='min-w-0 -ml-14 pl-14 grow items-center flex flex-col h-full overflow-y-auto z-10 pointer-events-none pb-10' use:dragScroll on:scroll={handleScroll} bind:this={container} style:--custom={media.coverImage?.color ?? '#fff'} style:--red={r} style:--green={g} style:--blue={b}>
|
<div class='min-w-0 -ml-14 pl-14 grow items-center flex flex-col h-full overflow-y-auto -z-1 pb-10' use:dragScroll on:scroll={handleScroll} bind:this={container} style:--custom={media.coverImage?.color ?? '#fff'} style:--red={r} style:--green={g} style:--blue={b}>
|
||||||
<div class='gap-6 w-full pt-4 md:pt-32 flex flex-col items-center justify-center max-w-[1600px] px-3 xl:px-14 pointer-events-auto'>
|
<div class='gap-6 w-full pt-4 md:pt-32 flex flex-col items-center justify-center max-w-[1600px] px-3 xl:px-14 pointer-events-auto'>
|
||||||
<div class='flex flex-col md:flex-row w-full items-center md:items-end gap-5 pt-12'>
|
<div class='flex flex-col md:flex-row w-full items-center md:items-end gap-5 pt-12'>
|
||||||
<Dialog.Root portal='#root'>
|
<Dialog.Root portal='#root'>
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,16 @@
|
||||||
<script lang='ts'>
|
<script lang='ts'>
|
||||||
|
import { SvelteFlowProvider } from '@xyflow/svelte'
|
||||||
|
|
||||||
import type { PageData } from './$types'
|
import type { PageData } from './$types'
|
||||||
|
|
||||||
import EpisodesList from '$lib/components/EpisodesList.svelte'
|
import EpisodesList from '$lib/components/EpisodesList.svelte'
|
||||||
import { Button } from '$lib/components/ui/button'
|
|
||||||
import { Threads } from '$lib/components/ui/forums'
|
import { Threads } from '$lib/components/ui/forums'
|
||||||
import { Load } from '$lib/components/ui/img'
|
import { Relations } from '$lib/components/ui/relations'
|
||||||
import * as Tabs from '$lib/components/ui/tabs'
|
import * as Tabs from '$lib/components/ui/tabs'
|
||||||
import { Themes } from '$lib/components/ui/themes'
|
import { Themes } from '$lib/components/ui/themes'
|
||||||
import { format, relation } from '$lib/modules/anilist'
|
|
||||||
import { authAggregator } from '$lib/modules/auth'
|
import { authAggregator } from '$lib/modules/auth'
|
||||||
import { dragScroll } from '$lib/modules/navigate'
|
import '@xyflow/svelte/dist/style.css'
|
||||||
|
import { cn } from '$lib/utils'
|
||||||
|
|
||||||
export let data: PageData
|
export let data: PageData
|
||||||
|
|
||||||
|
|
@ -17,13 +18,7 @@
|
||||||
|
|
||||||
$: media = $anime.Media!
|
$: media = $anime.Media!
|
||||||
|
|
||||||
$: relations = media.relations?.edges?.filter(edge => edge?.node?.type === 'ANIME')
|
let expanded = false
|
||||||
|
|
||||||
let showRelations = false
|
|
||||||
|
|
||||||
function showMore () {
|
|
||||||
showRelations = !showRelations
|
|
||||||
}
|
|
||||||
|
|
||||||
$: mediaId = media.id
|
$: mediaId = media.id
|
||||||
$: following = authAggregator.following(mediaId)
|
$: following = authAggregator.following(mediaId)
|
||||||
|
|
@ -33,37 +28,11 @@
|
||||||
let value: string
|
let value: string
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if relations?.length}
|
|
||||||
<div class='w-full'>
|
|
||||||
<div class='flex justify-between items-center'>
|
|
||||||
<div class='text-[20px] md:text-2xl font-bold'>Relations</div>
|
|
||||||
{#if relations.length > 3}
|
|
||||||
<Button variant='ghost' class='text-muted-foreground font-bold text-sm' on:click={showMore}>{showRelations ? 'Show Less' : 'Show More'}</Button>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
<div class='md:w-full flex gap-5 overflow-x-scroll md:overflow-x-visible md:grid md:grid-cols-3 justify-items-center py-3' use:dragScroll>
|
|
||||||
{#each showRelations ? relations : relations.slice(0, 3) as rel (rel?.node?.id)}
|
|
||||||
{@const media = rel?.node}
|
|
||||||
{#if media}
|
|
||||||
<a class='select:scale-[1.02] select:shadow-lg scale-100 transition-all duration-200 shrink-0 ease-out focus-visible:ring-ring focus-visible:ring-1 rounded-md w-96 md:w-full h-[126px] bg-neutral-950 text-secondary-foreground select:bg-neutral-900 flex' style:-webkit-user-drag='none' href='/app/anime/{media.id}'>
|
|
||||||
<div class='w-[90px] bg-image rounded-l-md shrink-0'>
|
|
||||||
<Load src={media.coverImage?.medium} class='object-cover h-full w-full shrink-0 rounded-l-md' />
|
|
||||||
</div>
|
|
||||||
<div class='h-full grid px-3 items-center'>
|
|
||||||
<div class='text-custom font-bold capitalize'>{relation(rel.relationType)}</div>
|
|
||||||
<div class='line-clamp-2'>{media.title?.userPreferred ?? 'N/A'}</div>
|
|
||||||
<div class='font-thin'>{format(media)}</div>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
{/if}
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
<Tabs.Root bind:value class='w-full' activateOnFocus={false}>
|
<Tabs.Root bind:value class='w-full' activateOnFocus={false}>
|
||||||
<div class='flex justify-between items-center gap-3 sm:flex-row flex-col'>
|
<div class='flex justify-between items-center gap-3 sm:flex-row flex-col'>
|
||||||
<Tabs.List class='flex'>
|
<Tabs.List class='flex'>
|
||||||
<Tabs.Trigger value='episodes' tabindex={0} class='px-8 data-[state=active]:bg-custom data-[state=active]:text-contrast data-[state=active]:font-bold'>Episodes</Tabs.Trigger>
|
<Tabs.Trigger value='episodes' tabindex={0} class='px-8 data-[state=active]:bg-custom data-[state=active]:text-contrast data-[state=active]:font-bold'>Episodes</Tabs.Trigger>
|
||||||
|
<Tabs.Trigger value='relations' tabindex={0} class='px-8 data-[state=active]:bg-custom data-[state=active]:text-contrast data-[state=active]:font-bold'>Relations</Tabs.Trigger>
|
||||||
<Tabs.Trigger value='threads' tabindex={0} class='px-8 data-[state=active]:bg-custom data-[state=active]:text-contrast data-[state=active]:font-bold'>Threads</Tabs.Trigger>
|
<Tabs.Trigger value='threads' tabindex={0} class='px-8 data-[state=active]:bg-custom data-[state=active]:text-contrast data-[state=active]:font-bold'>Threads</Tabs.Trigger>
|
||||||
<Tabs.Trigger value='themes' tabindex={0} class='px-8 data-[state=active]:bg-custom data-[state=active]:text-contrast data-[state=active]:font-bold'>Themes</Tabs.Trigger>
|
<Tabs.Trigger value='themes' tabindex={0} class='px-8 data-[state=active]:bg-custom data-[state=active]:text-contrast data-[state=active]:font-bold'>Themes</Tabs.Trigger>
|
||||||
</Tabs.List>
|
</Tabs.List>
|
||||||
|
|
@ -71,6 +40,15 @@
|
||||||
<Tabs.Content value='episodes' tabindex={-1}>
|
<Tabs.Content value='episodes' tabindex={-1}>
|
||||||
<EpisodesList {media} {eps} {following} />
|
<EpisodesList {media} {eps} {following} />
|
||||||
</Tabs.Content>
|
</Tabs.Content>
|
||||||
|
<Tabs.Content value='relations' tabindex={-1}>
|
||||||
|
{#if value === 'relations'}
|
||||||
|
<div class={cn('border border-border rounded overflow-clip mt-3 transition-[height]', expanded ? 'h-[80vh]' : 'h-72')}>
|
||||||
|
<SvelteFlowProvider>
|
||||||
|
<Relations {media} bind:expanded />
|
||||||
|
</SvelteFlowProvider>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</Tabs.Content>
|
||||||
<Tabs.Content value='threads' tabindex={-1}>
|
<Tabs.Content value='threads' tabindex={-1}>
|
||||||
{#key mediaId}
|
{#key mediaId}
|
||||||
{#if value === 'threads'}
|
{#if value === 'threads'}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue