mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-05-24 02:02:36 -04:00
Convert news items to dapper implementation
This commit is contained in:
parent
948f8694f2
commit
ee4cc6706e
@ -16,180 +16,16 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Kyoo.Abstractions.Controllers;
|
||||
using Kyoo.Abstractions.Models.Attributes;
|
||||
|
||||
namespace Kyoo.Abstractions.Models
|
||||
namespace Kyoo.Abstractions.Models;
|
||||
|
||||
/// <summary>
|
||||
/// A show, a movie or a collection.
|
||||
/// </summary>
|
||||
[OneOf(Types = new[] { typeof(Episode), typeof(Movie) })]
|
||||
public interface INews : IResource, IThumbnails, IMetadata, IAddedDate, IQuery
|
||||
{
|
||||
/// <summary>
|
||||
/// The type of item, ether a show, a movie or a collection.
|
||||
/// </summary>
|
||||
public enum NewsKind
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="ILibraryItem"/> is an <see cref="Episode"/>.
|
||||
/// </summary>
|
||||
Episode,
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ILibraryItem"/> is a Movie.
|
||||
/// </summary>
|
||||
Movie,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A new item
|
||||
/// </summary>
|
||||
public class News : IResource, IMetadata, IThumbnails, IAddedDate, IQuery
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public int Id { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
[MaxLength(256)]
|
||||
public string Slug { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The title of this show.
|
||||
/// </summary>
|
||||
public string? Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A catchphrase for this movie.
|
||||
/// </summary>
|
||||
public string? Tagline { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The list of alternative titles of this show.
|
||||
/// </summary>
|
||||
public string[] Aliases { get; set; } = Array.Empty<string>();
|
||||
|
||||
/// <summary>
|
||||
/// The path of the movie video file.
|
||||
/// </summary>
|
||||
public string Path { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The summary of this show.
|
||||
/// </summary>
|
||||
public string? Overview { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A list of tags that match this movie.
|
||||
/// </summary>
|
||||
public string[] Tags { get; set; } = Array.Empty<string>();
|
||||
|
||||
/// <summary>
|
||||
/// The list of genres (themes) this show has.
|
||||
/// </summary>
|
||||
public Genre[] Genres { get; set; } = Array.Empty<Genre>();
|
||||
|
||||
/// <summary>
|
||||
/// Is this show airing, not aired yet or finished?
|
||||
/// </summary>
|
||||
public Status? Status { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// How well this item is rated? (from 0 to 100).
|
||||
/// </summary>
|
||||
public int? Rating { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// How long is this movie or episode? (in minutes)
|
||||
/// </summary>
|
||||
public int Runtime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The date this movie aired.
|
||||
/// </summary>
|
||||
public DateTime? AirDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The date this movie aired.
|
||||
/// </summary>
|
||||
public DateTime? ReleaseDate => AirDate;
|
||||
|
||||
/// <inheritdoc />
|
||||
public DateTime AddedDate { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Image? Poster { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Image? Thumbnail { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Image? Logo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A video of a few minutes that tease the content.
|
||||
/// </summary>
|
||||
public string? Trailer { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Dictionary<string, MetadataId> ExternalId { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// The season in witch this episode is in.
|
||||
/// </summary>
|
||||
public int? SeasonNumber { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The number of this episode in it's season.
|
||||
/// </summary>
|
||||
public int? EpisodeNumber { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The absolute number of this episode. It's an episode number that is not reset to 1 after a new season.
|
||||
/// </summary>
|
||||
public int? AbsoluteNumber { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A simple summary of informations about the show of this episode
|
||||
/// (this is specially useful since news can't have includes).
|
||||
/// </summary>
|
||||
public ShowInfo? Show { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Is the item a a movie or an episode?
|
||||
/// </summary>
|
||||
public NewsKind Kind { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Links to watch this movie.
|
||||
/// </summary>
|
||||
public VideoLinks Links => new()
|
||||
{
|
||||
Direct = $"/video/{Kind.ToString().ToLower()}/{Slug}/direct",
|
||||
Hls = $"/video/{Kind.ToString().ToLower()}/{Slug}/master.m3u8",
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// A simple summary of informations about the show of this episode
|
||||
/// (this is specially useful since news can't have includes).
|
||||
/// </summary>
|
||||
public class ShowInfo : IResource, IThumbnails
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public int Id { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string Slug { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The title of this show.
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Image? Poster { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Image? Thumbnail { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Image? Logo { get; set; }
|
||||
}
|
||||
}
|
||||
static Sort IQuery.DefaultSort => new Sort<INews>.By(nameof(AddedDate));
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ namespace Kyoo.Abstractions.Models
|
||||
/// <summary>
|
||||
/// A class to represent a single show's episode.
|
||||
/// </summary>
|
||||
public class Episode : IQuery, IResource, IMetadata, IThumbnails, IAddedDate
|
||||
public class Episode : IQuery, IResource, IMetadata, IThumbnails, IAddedDate, INews
|
||||
{
|
||||
// Use absolute numbers by default and fallback to season/episodes if it does not exists.
|
||||
public static Sort DefaultSort => new Sort<Episode>.Conglomerate(
|
||||
|
@ -29,7 +29,7 @@ namespace Kyoo.Abstractions.Models
|
||||
/// <summary>
|
||||
/// A series or a movie.
|
||||
/// </summary>
|
||||
public class Movie : IQuery, IResource, IMetadata, IOnMerge, IThumbnails, IAddedDate, ILibraryItem
|
||||
public class Movie : IQuery, IResource, IMetadata, IOnMerge, IThumbnails, IAddedDate, ILibraryItem, INews
|
||||
{
|
||||
public static Sort DefaultSort => new Sort<Movie>.By(x => x.Name);
|
||||
|
||||
|
@ -39,12 +39,12 @@ public static class DapperHelper
|
||||
{
|
||||
private static string _Property(string key, Dictionary<string, Type> config)
|
||||
{
|
||||
if (config.Count == 1)
|
||||
return $"{config.First()}.{key.ToSnakeCase()}";
|
||||
|
||||
IEnumerable<string> keys = config
|
||||
string[] keys = config
|
||||
.Where(x => key == "id" || x.Value.GetProperty(key) != null)
|
||||
.Select(x => $"{x.Key}.{x.Value.GetProperty(key)?.GetCustomAttribute<ColumnAttribute>()?.Name ?? key.ToSnakeCase()}");
|
||||
.Select(x => $"{x.Key}.{x.Value.GetProperty(key)?.GetCustomAttribute<ColumnAttribute>()?.Name ?? key.ToSnakeCase()}")
|
||||
.ToArray();
|
||||
if (keys.Length == 1)
|
||||
return keys.First();
|
||||
return $"coalesce({string.Join(", ", keys)})";
|
||||
}
|
||||
|
||||
@ -66,14 +66,16 @@ public static class DapperHelper
|
||||
}
|
||||
|
||||
public static (
|
||||
Dictionary<string, Type> config,
|
||||
string projection,
|
||||
string join,
|
||||
List<Type> types,
|
||||
Func<T, IEnumerable<object?>, T> map
|
||||
) ProcessInclude<T>(Include<T> include, Dictionary<string, Type> config)
|
||||
where T : class
|
||||
{
|
||||
int relation = 0;
|
||||
Dictionary<string, Type> retConfig = new();
|
||||
List<Type> types = new();
|
||||
StringBuilder projection = new();
|
||||
StringBuilder join = new();
|
||||
|
||||
foreach (Include.Metadata metadata in include.Metadatas)
|
||||
@ -83,7 +85,8 @@ public static class DapperHelper
|
||||
{
|
||||
case Include.SingleRelation(var name, var type, var rid):
|
||||
string tableName = type.GetCustomAttribute<TableAttribute>()?.Name ?? $"{type.Name.ToSnakeCase()}s";
|
||||
retConfig.Add($"r{relation}", type);
|
||||
types.Add(type);
|
||||
projection.AppendLine($", r{relation}.* -- {type.Name} as r{relation}");
|
||||
join.Append($"\nleft join {tableName} as r{relation} on r{relation}.id = {_Property(rid, config)}");
|
||||
break;
|
||||
case Include.CustomRelation(var name, var type, var sql, var on, var declaring):
|
||||
@ -91,7 +94,8 @@ public static class DapperHelper
|
||||
string lateral = sql.Contains("\"this\"") ? " lateral" : string.Empty;
|
||||
sql = sql.Replace("\"this\"", owner);
|
||||
on = on?.Replace("\"this\"", owner);
|
||||
retConfig.Add($"r{relation}", type);
|
||||
types.Add(type);
|
||||
projection.AppendLine($", r{relation}.*");
|
||||
join.Append($"\nleft join{lateral} ({sql}) as r{relation} on r{relation}.{on}");
|
||||
break;
|
||||
case Include.ProjectedRelation:
|
||||
@ -114,7 +118,7 @@ public static class DapperHelper
|
||||
return item;
|
||||
}
|
||||
|
||||
return (retConfig, join.ToString(), Map);
|
||||
return (projection.ToString(), join.ToString(), types, Map);
|
||||
}
|
||||
|
||||
public static FormattableString ProcessFilter<T>(Filter<T> filter, Dictionary<string, Type> config)
|
||||
@ -187,9 +191,8 @@ public static class DapperHelper
|
||||
|
||||
// Include handling
|
||||
include ??= new();
|
||||
var (includeConfig, includeJoin, mapIncludes) = ProcessInclude(include, config);
|
||||
var (includeProjection, includeJoin, includeTypes, mapIncludes) = ProcessInclude(include, config);
|
||||
query.AppendLiteral(includeJoin);
|
||||
string includeProjection = string.Join(string.Empty, includeConfig.Select(x => $", {x.Key}.*"));
|
||||
query.Replace("/* includes */", $"{includeProjection:raw}", out bool replaced);
|
||||
if (!replaced)
|
||||
throw new ArgumentException("Missing '/* includes */' placeholder in top level sql select to support includes.");
|
||||
@ -210,9 +213,7 @@ public static class DapperHelper
|
||||
// Build query and prepare to do the query/projections
|
||||
IDapperSqlCommand cmd = query.Build();
|
||||
string sql = cmd.Sql;
|
||||
List<Type> types = config.Select(x => x.Value)
|
||||
.Concat(includeConfig.Select(x => x.Value))
|
||||
.ToList();
|
||||
List<Type> types = config.Select(x => x.Value).Concat(includeTypes).ToList();
|
||||
|
||||
// Expand projections on every types received.
|
||||
sql = Regex.Replace(sql, @"(,?) -- (\w+)( as (\w+))?", (match) =>
|
||||
@ -296,6 +297,7 @@ public static class DapperHelper
|
||||
query += ProcessFilter(filter, config);
|
||||
|
||||
IDapperSqlCommand cmd = query.Build();
|
||||
|
||||
// language=postgreSQL
|
||||
string sql = $"select count(*) from ({cmd.Sql}) as query";
|
||||
|
||||
|
@ -18,53 +18,53 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Kyoo.Abstractions.Controllers;
|
||||
using System.Data.Common;
|
||||
using System.IO;
|
||||
using Kyoo.Abstractions.Models;
|
||||
using Kyoo.Abstractions.Models.Utils;
|
||||
using Kyoo.Postgresql;
|
||||
|
||||
namespace Kyoo.Core.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// A local repository to handle shows
|
||||
/// </summary>
|
||||
public class NewsRepository : LocalRepository<News>
|
||||
public class NewsRepository : DapperRepository<INews>
|
||||
{
|
||||
public NewsRepository(DatabaseContext database, IThumbnailsManager thumbs)
|
||||
: base(database, thumbs)
|
||||
// language=PostgreSQL
|
||||
protected override FormattableString Sql => $"""
|
||||
select
|
||||
e.*, -- Episode as e
|
||||
m.*
|
||||
/* includes */
|
||||
from
|
||||
episodes as e
|
||||
full outer join (
|
||||
select
|
||||
* -- Movie
|
||||
from
|
||||
movies
|
||||
) as m on false
|
||||
""";
|
||||
|
||||
protected override Dictionary<string, Type> Config => new()
|
||||
{
|
||||
{ "e", typeof(Episode) },
|
||||
{ "m", typeof(Movie) },
|
||||
};
|
||||
|
||||
protected override INews Mapper(List<object?> items)
|
||||
{
|
||||
if (items[0] is Episode episode && episode.Id != 0)
|
||||
return episode;
|
||||
if (items[1] is Movie movie && movie.Id != 0)
|
||||
{
|
||||
movie.Id = -movie.Id;
|
||||
return movie;
|
||||
}
|
||||
throw new InvalidDataException();
|
||||
}
|
||||
|
||||
public NewsRepository(DbConnection database)
|
||||
: base(database)
|
||||
{ }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task<ICollection<News>> Search(string query, Include<News>? include = default)
|
||||
=> throw new InvalidOperationException();
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task<News> Create(News obj)
|
||||
=> throw new InvalidOperationException();
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task<News> CreateIfNotExists(News obj)
|
||||
=> throw new InvalidOperationException();
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task<News> Edit(News edited)
|
||||
=> throw new InvalidOperationException();
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task<News> Patch(int id, Func<News, Task<bool>> patch)
|
||||
=> throw new InvalidOperationException();
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task Delete(int id)
|
||||
=> throw new InvalidOperationException();
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task Delete(string slug)
|
||||
=> throw new InvalidOperationException();
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task Delete(News obj)
|
||||
=> throw new InvalidOperationException();
|
||||
}
|
||||
}
|
||||
|
@ -33,9 +33,9 @@ namespace Kyoo.Core.Api
|
||||
[ApiController]
|
||||
[PartialPermission("LibraryItem")]
|
||||
[ApiDefinition("News", Group = ResourcesGroup)]
|
||||
public class NewsApi : CrudThumbsApi<News>
|
||||
public class NewsApi : CrudThumbsApi<INews>
|
||||
{
|
||||
public NewsApi(IRepository<News> news, IThumbnailsManager thumbs)
|
||||
public NewsApi(IRepository<INews> news, IThumbnailsManager thumbs)
|
||||
: base(news, thumbs)
|
||||
{ }
|
||||
}
|
||||
|
@ -92,14 +92,6 @@ namespace Kyoo.Postgresql
|
||||
/// </summary>
|
||||
public DbSet<PeopleRole> PeopleRoles { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The list of new items (episodes and movies).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This set is ready only, on most database this will be a view.
|
||||
/// </remarks>
|
||||
public DbSet<News> News { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Add a many to many link between two resources.
|
||||
/// </summary>
|
||||
@ -284,7 +276,6 @@ namespace Kyoo.Postgresql
|
||||
.WithMany("Users")
|
||||
.UsingEntity(x => x.ToTable(LinkName<User, Show>()));
|
||||
|
||||
_HasMetadata<News>(modelBuilder);
|
||||
_HasMetadata<Collection>(modelBuilder);
|
||||
_HasMetadata<Movie>(modelBuilder);
|
||||
_HasMetadata<Show>(modelBuilder);
|
||||
@ -293,7 +284,6 @@ namespace Kyoo.Postgresql
|
||||
_HasMetadata<People>(modelBuilder);
|
||||
_HasMetadata<Studio>(modelBuilder);
|
||||
|
||||
_HasImages<News>(modelBuilder);
|
||||
_HasImages<Collection>(modelBuilder);
|
||||
_HasImages<Movie>(modelBuilder);
|
||||
_HasImages<Show>(modelBuilder);
|
||||
@ -301,7 +291,6 @@ namespace Kyoo.Postgresql
|
||||
_HasImages<Episode>(modelBuilder);
|
||||
_HasImages<People>(modelBuilder);
|
||||
|
||||
_HasAddedDate<News>(modelBuilder);
|
||||
_HasAddedDate<Collection>(modelBuilder);
|
||||
_HasAddedDate<Movie>(modelBuilder);
|
||||
_HasAddedDate<Show>(modelBuilder);
|
||||
@ -347,14 +336,6 @@ namespace Kyoo.Postgresql
|
||||
|
||||
modelBuilder.Entity<Movie>()
|
||||
.Ignore(x => x.Links);
|
||||
modelBuilder.Entity<News>()
|
||||
.Ignore(x => x.Links);
|
||||
|
||||
var builder = modelBuilder.Entity<News>()
|
||||
.OwnsOne(x => x.Show);
|
||||
builder.OwnsOne(x => x.Poster);
|
||||
builder.OwnsOne(x => x.Thumbnail);
|
||||
builder.OwnsOne(x => x.Logo);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -370,10 +370,6 @@ namespace Kyoo.Postgresql.Migrations
|
||||
.HasColumnType("genre[]")
|
||||
.HasColumnName("genres");
|
||||
|
||||
b.Property<NewsKind>("Kind")
|
||||
.HasColumnType("news_kind")
|
||||
.HasColumnName("kind");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasColumnType("text")
|
||||
.HasColumnName("name");
|
||||
|
@ -390,10 +390,6 @@ namespace Kyoo.Postgresql.Migrations
|
||||
.HasColumnType("genre[]")
|
||||
.HasColumnName("genres");
|
||||
|
||||
b.Property<NewsKind>("Kind")
|
||||
.HasColumnType("news_kind")
|
||||
.HasColumnName("kind");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasColumnType("text")
|
||||
.HasColumnName("name");
|
||||
|
@ -387,10 +387,6 @@ namespace Kyoo.Postgresql.Migrations
|
||||
.HasColumnType("genre[]")
|
||||
.HasColumnName("genres");
|
||||
|
||||
b.Property<NewsKind>("Kind")
|
||||
.HasColumnType("news_kind")
|
||||
.HasColumnName("kind");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasColumnType("text")
|
||||
.HasColumnName("name");
|
||||
|
@ -18,11 +18,8 @@
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using EFCore.NamingConventions.Internal;
|
||||
using Kyoo.Abstractions.Models;
|
||||
using Kyoo.Utils;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
|
||||
using Npgsql;
|
||||
@ -50,7 +47,6 @@ namespace Kyoo.Postgresql
|
||||
{
|
||||
NpgsqlConnection.GlobalTypeMapper.MapEnum<Status>();
|
||||
NpgsqlConnection.GlobalTypeMapper.MapEnum<Genre>();
|
||||
NpgsqlConnection.GlobalTypeMapper.MapEnum<NewsKind>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -103,7 +99,6 @@ namespace Kyoo.Postgresql
|
||||
{
|
||||
modelBuilder.HasPostgresEnum<Status>();
|
||||
modelBuilder.HasPostgresEnum<Genre>();
|
||||
modelBuilder.HasPostgresEnum<NewsKind>();
|
||||
|
||||
modelBuilder.HasDbFunction(typeof(DatabaseContext).GetMethod(nameof(MD5))!)
|
||||
.HasTranslation(args =>
|
||||
|
@ -37,7 +37,10 @@ export const EpisodeP = BaseEpisodeP.and(
|
||||
|
||||
show: ShowP.optional(),
|
||||
}),
|
||||
);
|
||||
).transform((x) => {
|
||||
if (x.show && !x.thumbnail && x.show.thumbnail) x.thumbnail = x.show.thumbnail;
|
||||
return x;
|
||||
});
|
||||
|
||||
/**
|
||||
* A class to represent a single show's episode.
|
||||
|
@ -20,9 +20,7 @@
|
||||
|
||||
import { z } from "zod";
|
||||
import { MovieP } from "./movie";
|
||||
import { BaseEpisodeP } from "./episode.base";
|
||||
import { ResourceP } from "../traits/resource";
|
||||
import { withImages } from "../traits/images";
|
||||
import { EpisodeP } from "./episode";
|
||||
|
||||
/**
|
||||
* The type of item, ether a a movie or an episode.
|
||||
@ -36,29 +34,7 @@ export const NewsP = z.union([
|
||||
/*
|
||||
* Either an episode
|
||||
*/
|
||||
BaseEpisodeP.and(
|
||||
z.object({
|
||||
kind: z.literal(NewsKind.Episode),
|
||||
show: withImages(
|
||||
ResourceP.extend({
|
||||
name: z.string(),
|
||||
}),
|
||||
"shows",
|
||||
).transform((x) => {
|
||||
if (!x.thumbnail && x.poster) {
|
||||
x.thumbnail = { ...x.poster };
|
||||
if (x.thumbnail) {
|
||||
x.thumbnail.low = x.thumbnail.high;
|
||||
x.thumbnail.medium = x.thumbnail.high;
|
||||
}
|
||||
}
|
||||
return x;
|
||||
}),
|
||||
}),
|
||||
).transform((x) => {
|
||||
if (!x.thumbnail && x.show.thumbnail) x.thumbnail = x.show.thumbnail;
|
||||
return x;
|
||||
}),
|
||||
EpisodeP.and(z.object({ kind: z.literal(NewsKind.Episode) })),
|
||||
/*
|
||||
* Or a Movie
|
||||
*/
|
||||
|
@ -92,5 +92,6 @@ NewsList.query = (): QueryIdentifier<News> => ({
|
||||
params: {
|
||||
// Limit the inital numbers of items
|
||||
limit: 10,
|
||||
fields: ["show"],
|
||||
},
|
||||
});
|
||||
|
@ -149,13 +149,15 @@ export const ItemDetails = ({
|
||||
minHeight: px(50),
|
||||
})}
|
||||
>
|
||||
<ScrollView horizontal {...css({ alignItems: "center" })}>
|
||||
{(genres || [...Array(3)])?.map((x, i) => (
|
||||
<Chip key={x ?? i} size="small" {...css({ mX: ts(0.5) })}>
|
||||
{x ?? <Skeleton {...css({ width: rem(3), height: rem(0.8) })} />}
|
||||
</Chip>
|
||||
))}
|
||||
</ScrollView>
|
||||
{(isLoading || genres) && (
|
||||
<ScrollView horizontal {...css({ alignItems: "center" })}>
|
||||
{(genres || [...Array(3)])?.map((x, i) => (
|
||||
<Chip key={x ?? i} size="small" {...css({ mX: ts(0.5) })}>
|
||||
{x ?? <Skeleton {...css({ width: rem(3), height: rem(0.8) })} />}
|
||||
</Chip>
|
||||
))}
|
||||
</ScrollView>
|
||||
)}
|
||||
{playHref !== null && (
|
||||
<IconFab
|
||||
icon={PlayArrow}
|
||||
|
Loading…
x
Reference in New Issue
Block a user