feat: hide hentai by default
Some checks failed
Check / check (push) Has been cancelled

fix: improve airing schedule results
This commit is contained in:
ThaUnknown 2025-09-29 17:30:21 +02:00
parent bccaac3c24
commit de3da8acf8
No known key found for this signature in database
12 changed files with 31 additions and 25 deletions

View file

@ -1,6 +1,6 @@
{ {
"name": "ui", "name": "ui",
"version": "6.4.149", "version": "6.4.150",
"license": "BUSL-1.1", "license": "BUSL-1.1",
"private": true, "private": true,
"packageManager": "pnpm@9.15.5", "packageManager": "pnpm@9.15.5",

View file

@ -75,8 +75,7 @@ export default class PictureInPicture {
const renderFrame = (noskip?: number) => { const renderFrame = (noskip?: number) => {
if (noskip) this.video!.paused ? video.pause() : video.play() if (noskip) this.video!.paused ? video.pause() : video.play()
context.drawImage(this.deband?.canvas ?? this.video!, 0, 0) context.drawImage(this.deband?.canvas ?? this.video!, 0, 0)
// @ts-expect-error internal call on canvas if (canvas.width && canvas.height && this.subtitles?.renderer?._canvas) context.drawImage(this.subtitles.renderer._canvas, 0, 0, canvas.width, canvas.height)
if (canvas.width && canvas.height && this.subtitles.renderer?._canvas) context.drawImage(this.subtitles.renderer._canvas, 0, 0, canvas.width, canvas.height)
loop = this.video!.requestVideoFrameCallback(renderFrame) loop = this.video!.requestVideoFrameCallback(renderFrame)
} }
ctrl.signal.addEventListener('abort', () => { ctrl.signal.addEventListener('abort', () => {

View file

@ -3,6 +3,8 @@ import Debug from 'debug'
import lavenshtein from 'js-levenshtein' import lavenshtein from 'js-levenshtein'
import { derived, get, readable, writable, type Writable } from 'svelte/store' import { derived, get, readable, writable, type Writable } from 'svelte/store'
import { nsfw } from '../settings/settings'
import { Comments, DeleteEntry, DeleteThreadComment, Entry, Following, type FullMedia, IDMedia, RecrusiveRelations, 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'
@ -105,7 +107,7 @@ class AnilistClient {
}) })
search (variables: VariablesOf<typeof Search>, pause?: boolean) { search (variables: VariablesOf<typeof Search>, pause?: boolean) {
return queryStore({ client: this.client, query: Search, variables, pause }) return queryStore({ client: this.client, query: Search, variables: { ...variables, nsfw: get(nsfw) }, pause })
} }
async searchCompound (flattenedTitles: Array<{key: string, title: string, year?: string, isAdult: boolean}>) { async searchCompound (flattenedTitles: Array<{key: string, title: string, year?: string, isAdult: boolean}>) {
@ -199,8 +201,8 @@ class AnilistClient {
return Object.fromEntries(Object.values(res.data ?? {}).flatMap(({ media }) => media).map(media => [media.idMal, media.id])) return Object.fromEntries(Object.values(res.data ?? {}).flatMap(({ media }) => media).map(media => [media.idMal, media.id]))
} }
schedule (ids?: number[], onList = true) { schedule (ids?: number[], onList: boolean | null = true) {
return queryStore({ client: this.client, query: Schedule, variables: { ids, onList, seasonCurrent: currentSeason, seasonYearCurrent: currentYear, seasonLast: lastSeason, seasonYearLast: lastYear, seasonNext: nextSeason, seasonYearNext: nextYear } }) return queryStore({ client: this.client, query: Schedule, variables: { ids, onList, seasonCurrent: currentSeason, seasonYearCurrent: currentYear, seasonLast: lastSeason, seasonYearLast: lastYear, seasonNext: nextSeason, seasonYearNext: nextYear, formatNot: onList ? null : 'TV_SHORT', nsfw: get(nsfw) } })
} }
async toggleFav (id: number) { async toggleFav (id: number) {

View file

@ -153,12 +153,12 @@ export const UserFrag = gql(`
`) `)
export const Search = gql(` export const Search = gql(`
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]) { 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], $nsfw: [String]) {
Page(page: $page, perPage: $perPage) { Page(page: $page, perPage: $perPage) {
pageInfo { pageInfo {
hasNextPage hasNextPage
}, },
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) { 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, genre_not_in: $nsfw) {
...FullMedia ...FullMedia
} }
} }
@ -254,34 +254,34 @@ export const ScheduleMedia = gql(`
`) `)
export const Schedule = gql(` export const Schedule = gql(`
query Schedule($seasonCurrent: MediaSeason, $seasonYearCurrent: Int, $seasonLast: MediaSeason, $seasonYearLast: Int, $seasonNext: MediaSeason, $seasonYearNext: Int, $onList: Boolean, $ids: [Int]) { query Schedule($seasonCurrent: MediaSeason, $seasonYearCurrent: Int, $seasonLast: MediaSeason, $seasonYearLast: Int, $seasonNext: MediaSeason, $seasonYearNext: Int, $onList: Boolean, $ids: [Int], $formatNot: MediaFormat, $nsfw: [String]) {
curr1: Page(page: 1) { curr1: Page(page: 1) {
media(type: ANIME, season: $seasonCurrent, seasonYear: $seasonYearCurrent, countryOfOrigin: JP, format_not: TV_SHORT, onList: $onList, id_in: $ids) { media(type: ANIME, season: $seasonCurrent, seasonYear: $seasonYearCurrent, format_not: $formatNot, onList: $onList, id_in: $ids, genre_not_in: $nsfw) {
...ScheduleMedia ...ScheduleMedia
} }
} }
curr2: Page(page: 2) { curr2: Page(page: 2) {
media(type: ANIME, season: $seasonCurrent, seasonYear: $seasonYearCurrent, countryOfOrigin: JP, format_not: TV_SHORT, onList: $onList, id_in: $ids) { media(type: ANIME, season: $seasonCurrent, seasonYear: $seasonYearCurrent, format_not: $formatNot, onList: $onList, id_in: $ids, genre_not_in: $nsfw) {
...ScheduleMedia ...ScheduleMedia
} }
} }
curr3: Page(page: 3) { curr3: Page(page: 3) {
media(type: ANIME, season: $seasonCurrent, seasonYear: $seasonYearCurrent, countryOfOrigin: JP, format_not: TV_SHORT, onList: $onList, id_in: $ids) { media(type: ANIME, season: $seasonCurrent, seasonYear: $seasonYearCurrent, format_not: $formatNot, onList: $onList, id_in: $ids, genre_not_in: $nsfw) {
...ScheduleMedia ...ScheduleMedia
} }
} }
residue: Page(page: 1) { residue: Page(page: 1) {
media(type: ANIME, season: $seasonLast, seasonYear: $seasonYearLast, episodes_greater: 16, countryOfOrigin: JP, format_not: TV_SHORT, onList: $onList, id_in: $ids) { media(type: ANIME, season: $seasonLast, seasonYear: $seasonYearLast, episodes_greater: 11, format_not: $formatNot, onList: $onList, id_in: $ids, genre_not_in: $nsfw) {
...ScheduleMedia ...ScheduleMedia
} }
}, },
next1: Page(page: 1) { next1: Page(page: 1) {
media(type: ANIME, season: $seasonNext, seasonYear: $seasonYearNext, sort: [START_DATE], countryOfOrigin: JP, format_not: TV_SHORT, onList: $onList, id_in: $ids) { media(type: ANIME, season: $seasonNext, seasonYear: $seasonYearNext, sort: [START_DATE], format_not: $formatNot, onList: $onList, id_in: $ids, genre_not_in: $nsfw) {
...ScheduleMedia ...ScheduleMedia
} }
}, },
next2: Page(page: 2) { next2: Page(page: 2) {
media(type: ANIME, season: $seasonNext, seasonYear: $seasonYearNext, sort: [START_DATE], countryOfOrigin: JP, format_not: TV_SHORT, onList: $onList, id_in: $ids) { media(type: ANIME, season: $seasonNext, seasonYear: $seasonYearNext, sort: [START_DATE], format_not: $formatNot, onList: $onList, id_in: $ids, genre_not_in: $nsfw) {
...ScheduleMedia ...ScheduleMedia
} }
} }

View file

@ -72,7 +72,7 @@ export default new class AuthAggregator {
// QUERIES/MUTATIONS // QUERIES/MUTATIONS
schedule (onList = true) { schedule (onList: boolean | null = true) {
if (this.anilist()) return client.schedule(undefined, onList) if (this.anilist()) return client.schedule(undefined, onList)
if (this.kitsu()) return kitsu.schedule(onList) if (this.kitsu()) return kitsu.schedule(onList)
if (this.mal()) return mal.schedule(onList) if (this.mal()) return mal.schedule(onList)

View file

@ -417,7 +417,7 @@ export default new class KitsuSync {
// QUERIES/MUTATIONS // QUERIES/MUTATIONS
schedule (onList = true) { schedule (onList: boolean | null = true) {
const ids = Object.keys(this.userlist.value).map(id => parseInt(id)) const ids = Object.keys(this.userlist.value).map(id => parseInt(id))
debug('Kitsu schedule called with onList:', onList, 'and ids:', ids) debug('Kitsu schedule called with onList:', onList, 'and ids:', ids)
return client.schedule(onList && ids.length ? ids : undefined) return client.schedule(onList && ids.length ? ids : undefined)

View file

@ -45,7 +45,7 @@ export default new class LocalSync {
} }
} }
schedule (onList = true): ReturnType<typeof client.schedule> { schedule (onList: boolean | null = true): ReturnType<typeof client.schedule> {
const ids = Object.values(this.entries.value).map(({ mediaListEntry }) => mediaListEntry?.id).filter(e => e != null) const ids = Object.values(this.entries.value).map(({ mediaListEntry }) => mediaListEntry?.id).filter(e => e != null)
return client.schedule(onList && ids.length ? ids : undefined) return client.schedule(onList && ids.length ? ids : undefined)
} }

View file

@ -430,7 +430,7 @@ export default new class MALSync {
// QUERIES/MUTATIONS // QUERIES/MUTATIONS
schedule (onList = true) { schedule (onList: boolean | null = true) {
const ids = Object.keys(this.userlist.value).map(id => parseInt(id)) const ids = Object.keys(this.userlist.value).map(id => parseInt(id))
debug('Fetching MAL schedule with IDs:', ids) debug('Fetching MAL schedule with IDs:', ids)
return client.schedule(onList && ids.length ? ids : undefined) return client.schedule(onList && ids.length ? ids : undefined)

View file

@ -42,5 +42,6 @@ export default {
playerSkip: false, playerSkip: false,
playerSkipFiller: false, playerSkipFiller: false,
minimalPlayerUI: false, minimalPlayerUI: false,
androidStorageType: 'cache' androidStorageType: 'cache',
showHentai: false
} }

View file

@ -14,6 +14,8 @@ export const settings = persisted('settings', defaults, { beforeRead: value => (
export const debug = persisted('debug-key', '') export const debug = persisted('debug-key', '')
export const nsfw = derived(settings, $settings => ($settings.showHentai ? null : ['Hentai']) as ['Hentai'] | null)
debug.subscribe((value) => { debug.subscribe((value) => {
native.debug(value) native.debug(value)
Debug.enable(value) Debug.enable(value)

View file

@ -21,7 +21,7 @@
const onList = persisted('schedule-on-list', true) const onList = persisted('schedule-on-list', true)
$: query = authAggregator.schedule($onList) $: query = authAggregator.schedule($onList || null)
let now = new Date() let now = new Date()
$: monthName = now.toLocaleString('en-US', { month: 'long' }) $: monthName = now.toLocaleString('en-US', { month: 'long' })
@ -200,7 +200,7 @@
<Tooltip.Content sameWidth={true} class='text-center gap-1.5'> <Tooltip.Content sameWidth={true} class='text-center gap-1.5'>
{#each episodes.slice(5) as episode, i (i)} {#each episodes.slice(5) as episode, i (i)}
{@const status = _list(episode)} {@const status = _list(episode)}
<ButtonPrimitive.Root class={cn('flex items-center h-4 w-full group', +episode.airTime < Date.now() && 'text-neutral-300')} href='/app/anime/{episode.id}'> <ButtonPrimitive.Root class={cn('flex items-center h-4 w-full group', +episode.airTime < Date.now() && 'text-neutral-400')} href='/app/anime/{episode.id}'>
<div class='font-medium text-nowrap text-ellipsis overflow-hidden pr-2' title={episode.title?.userPreferred}> <div class='font-medium text-nowrap text-ellipsis overflow-hidden pr-2' title={episode.title?.userPreferred}>
{#if status} {#if status}
<StatusDot variant={status} class='hidden xl:inline-flex' /> <StatusDot variant={status} class='hidden xl:inline-flex' />

View file

@ -52,8 +52,8 @@
<SettingCard let:id title='CSS Variables' description='Used for custom themes. Can change colors, sizes, spacing and more. Supports only variables.'> <SettingCard let:id title='CSS Variables' description='Used for custom themes. Can change colors, sizes, spacing and more. Supports only variables.'>
<Textarea class='form-control w-60 shrink-0 mw-full bg-dark' placeholder='--accent-color: #e5204c;' bind:value={$variables} {id} /> <Textarea class='form-control w-60 shrink-0 mw-full bg-dark' placeholder='--accent-color: #e5204c;' bind:value={$variables} {id} />
</SettingCard> </SettingCard>
<div class='font-weight-bold text-xl font-bold'>UI Settings</div>
{#if !SUPPORTS.isAndroid} {#if !SUPPORTS.isAndroid}
<div class='font-weight-bold text-xl font-bold'>Rendering Settings</div>
<SettingCard title='ANGLE Backend' description="What ANGLE backend to use for rendering. DON'T CHANGE WITHOUT REASON! On some Windows machines D3D9 might help with flicker. Changing this setting to something your device doesn't support might prevent Hayase from opening which will require a full reinstall. While Vulkan is an available option it might not be fully supported on Linux."> <SettingCard title='ANGLE Backend' description="What ANGLE backend to use for rendering. DON'T CHANGE WITHOUT REASON! On some Windows machines D3D9 might help with flicker. Changing this setting to something your device doesn't support might prevent Hayase from opening which will require a full reinstall. While Vulkan is an available option it might not be fully supported on Linux.">
<SingleCombo bind:value={$settings.angle} items={angle} class='w-40 shrink-0 border-input border' /> <SingleCombo bind:value={$settings.angle} items={angle} class='w-40 shrink-0 border-input border' />
</SettingCard> </SettingCard>
@ -62,11 +62,13 @@
<SettingCard title='Idle Animation' description='Enable/Disable the 3d idle animation. Changing this setting will restart the app.' let:id> <SettingCard title='Idle Animation' description='Enable/Disable the 3d idle animation. Changing this setting will restart the app.' let:id>
<Switch bind:checked={$settings.idleAnimation} on:click={native.restart} {id} /> <Switch bind:checked={$settings.idleAnimation} on:click={native.restart} {id} />
</SettingCard> --> </SettingCard> -->
{:else}
<div class='font-weight-bold text-xl font-bold'>UI Settings</div>
{/if} {/if}
<SettingCard title='UI Scale' description='Change the zoom level of the interface.' let:id> <SettingCard title='UI Scale' description='Change the zoom level of the interface.' let:id>
<Slider bind:value min={0.3} max={2.5} step={0.1} class='w-60 shrink-0' on:pointerup={saveScale} /> <Slider bind:value min={0.3} max={2.5} step={0.1} class='w-60 shrink-0' on:pointerup={saveScale} />
<div class='text-muted-foreground text-xs'>{Number(value[0]).toFixed(1)}</div> <div class='text-muted-foreground text-xs'>{Number(value[0]).toFixed(1)}</div>
</SettingCard> </SettingCard>
<div class='font-weight-bold text-xl font-bold'>Visibility Settings</div>
<SettingCard let:id title='Show Hentai' description='Shows hentai content throughout the app. If disabled all hentai content will be hidden and not shown in search results, but shown if present in your list.'>
<Switch {id} bind:checked={$settings.showHentai} />
</SettingCard>
</div> </div>