Design Prot Merge #1
@@ -2,5 +2,5 @@ namespace Musify.Api.DataTransferObjects.PlayLists;
|
||||
|
||||
public record CreatePlayListRequest(
|
||||
string Name,
|
||||
string Description,
|
||||
string? Description,
|
||||
Guid? PictureIntentId);
|
||||
|
||||
@@ -181,12 +181,8 @@ public static class PlayListEndpoints
|
||||
CreatePlayListRequest request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var description = string.IsNullOrWhiteSpace(request.Description)
|
||||
? "No description was provided."
|
||||
: request.Description;
|
||||
|
||||
var result = await mediator.Send(
|
||||
new CreatePlayListCommand(currentUser.RequiredId, request.Name, description, request.PictureIntentId),
|
||||
new CreatePlayListCommand(currentUser.RequiredId, request.Name, request.Description, request.PictureIntentId),
|
||||
cancellationToken);
|
||||
|
||||
return result.ToCreatedResult(playList => $"/playlists/{playList.Id}");
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace Musify.Application.PlayLists
|
||||
public record CreatePlayListCommand(
|
||||
long UserId,
|
||||
string Name,
|
||||
string Description,
|
||||
string? Description,
|
||||
Guid? PictureIntentId)
|
||||
: ICommand<ErrorOr<PlayListApplicationResponse>>;
|
||||
|
||||
@@ -58,7 +58,7 @@ namespace Musify.Application.PlayLists
|
||||
UserId = request.UserId,
|
||||
Name = request.Name,
|
||||
NormalizedName = request.Name.Trim().ToUpperInvariant(),
|
||||
Description = request.Description,
|
||||
Description = string.IsNullOrWhiteSpace(request.Description) ? null : request.Description.Trim(),
|
||||
Pictures = pictureIntent == null ? null
|
||||
: new PlayListPictures { OriginalName = pictureIntent.ObjectName }
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
using Musify.Domain.Abstractions;
|
||||
@@ -24,7 +24,6 @@ namespace Musify.Domain.Entities
|
||||
[MaxLength(50)]
|
||||
public required string NormalizedName { get; set; }
|
||||
|
||||
[Required]
|
||||
[MaxLength(256)]
|
||||
public string? Description { get; set; }
|
||||
|
||||
|
||||
+1174
File diff suppressed because it is too large
Load Diff
+46
@@ -0,0 +1,46 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Musify.Infrastructure.Persistence.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class MakePlayListDescriptionOptional : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Description",
|
||||
table: "PlayLists",
|
||||
type: "character varying(256)",
|
||||
maxLength: 256,
|
||||
nullable: true,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(256)",
|
||||
oldMaxLength: 256);
|
||||
|
||||
migrationBuilder.Sql(
|
||||
"""
|
||||
UPDATE "PlayLists" SET "Description" = NULL
|
||||
WHERE "Description" = 'No description was provided.';
|
||||
""");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Description",
|
||||
table: "PlayLists",
|
||||
type: "character varying(256)",
|
||||
maxLength: 256,
|
||||
nullable: false,
|
||||
defaultValue: "",
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(256)",
|
||||
oldMaxLength: 256,
|
||||
oldNullable: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -409,7 +409,6 @@ namespace Musify.Infrastructure.Persistence.Migrations
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
<header
|
||||
class="sticky top-0 z-40 flex h-14 items-center justify-between border-b border-line bg-bg/85 page-x backdrop-blur-md md:hidden"
|
||||
>
|
||||
<a href="/" class="text-lg font-extrabold tracking-wide">Musify</a>
|
||||
<a href="/" class="text-lg font-semibold tracking-wide">Musify</a>
|
||||
|
||||
<div class="relative" bind:this={menuRef}>
|
||||
<button
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script lang="ts">
|
||||
import MediaCard from './MediaCard.svelte';
|
||||
import PlaylistArt from './PlaylistArt.svelte';
|
||||
import Cover from './Cover.svelte';
|
||||
import { hueFor } from '$lib/theme/color';
|
||||
@@ -45,30 +46,25 @@
|
||||
);
|
||||
</script>
|
||||
|
||||
<a
|
||||
<MediaCard
|
||||
href={href ?? `/albums/${id}`}
|
||||
class="group animate-enter min-w-0 text-left"
|
||||
style="animation-delay:{index * 45}ms"
|
||||
{title}
|
||||
subtitle={meta}
|
||||
{index}
|
||||
oncontextmenu={onContextMenu}
|
||||
>
|
||||
{#if coverSrc}
|
||||
<Cover
|
||||
trackId={id}
|
||||
src={coverSrc}
|
||||
hue={hueFor(id)}
|
||||
size="large"
|
||||
alt={title}
|
||||
class="aspect-square w-full rounded-art shadow-art transition group-hover:shadow-art-lg"
|
||||
/>
|
||||
{:else}
|
||||
<PlaylistArt
|
||||
{trackIds}
|
||||
hue={hueFor(id)}
|
||||
class="aspect-square w-full rounded-art shadow-art transition group-hover:shadow-art-lg"
|
||||
/>
|
||||
{/if}
|
||||
<div class="mt-2.5 truncate text-fg">{title}</div>
|
||||
{#if meta}
|
||||
<div class="mt-0.5 truncate text-sm text-fg-3">{meta}</div>
|
||||
{/if}
|
||||
</a>
|
||||
{#snippet art(artClass)}
|
||||
{#if coverSrc}
|
||||
<Cover
|
||||
trackId={id}
|
||||
src={coverSrc}
|
||||
hue={hueFor(id)}
|
||||
size="large"
|
||||
alt={title}
|
||||
class={artClass}
|
||||
/>
|
||||
{:else}
|
||||
<PlaylistArt {trackIds} hue={hueFor(id)} class={artClass} />
|
||||
{/if}
|
||||
{/snippet}
|
||||
</MediaCard>
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
</span>
|
||||
{/if}
|
||||
{#if title}
|
||||
<h3 class="mt-5 text-2xl font-bold tracking-tight text-fg">{title}</h3>
|
||||
<h3 class="mt-5 text-2xl font-semibold tracking-tight text-fg">{title}</h3>
|
||||
{/if}
|
||||
{#if description}
|
||||
<p class="mt-2 text-fg-2">{description}</p>
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
<script lang="ts" module>
|
||||
export const mediaCardArt =
|
||||
'aspect-square w-full rounded-art shadow-art transition duration-300 ease-out group-hover:-translate-y-1 group-hover:shadow-art-lg';
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import type { Snippet } from 'svelte';
|
||||
|
||||
interface Props {
|
||||
href: string;
|
||||
title: string;
|
||||
subtitle?: string | null;
|
||||
index?: number;
|
||||
oncontextmenu?: (event: MouseEvent) => void;
|
||||
art: Snippet<[string]>;
|
||||
}
|
||||
|
||||
let { href, title, subtitle, index = 0, oncontextmenu, art }: Props = $props();
|
||||
</script>
|
||||
|
||||
<a
|
||||
{href}
|
||||
class="group animate-enter min-w-0 text-left"
|
||||
style="animation-delay:{index * 45}ms"
|
||||
{oncontextmenu}
|
||||
>
|
||||
{@render art(mediaCardArt)}
|
||||
<div class="mt-2.5 truncate text-fg transition-colors group-hover:text-accent-soft">{title}</div>
|
||||
{#if subtitle}
|
||||
<div class="mt-0.5 truncate text-sm text-fg-3">{subtitle}</div>
|
||||
{/if}
|
||||
</a>
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
let {
|
||||
as = 'div',
|
||||
min = '200px',
|
||||
min = '165px',
|
||||
minMobile = '150px',
|
||||
class: klass = '',
|
||||
children
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script lang="ts">
|
||||
import MediaCard from './MediaCard.svelte';
|
||||
import MixArt from './MixArt.svelte';
|
||||
import { hueFor } from '$lib/theme/color';
|
||||
import type { Mix } from '$lib/mixes';
|
||||
@@ -13,19 +14,13 @@
|
||||
const count = $derived(Number(mix.itemCount));
|
||||
</script>
|
||||
|
||||
<a
|
||||
<MediaCard
|
||||
href="/mixes/{mix.id}"
|
||||
class="group animate-enter min-w-0 text-left"
|
||||
style="animation-delay:{index * 45}ms"
|
||||
title={mix.title}
|
||||
subtitle="{count} {count === 1 ? 'canción' : 'canciones'}"
|
||||
{index}
|
||||
>
|
||||
<MixArt
|
||||
items={mix.items}
|
||||
hue={hueFor(mix.id)}
|
||||
class="aspect-square w-full rounded-art shadow-art transition group-hover:shadow-art-lg"
|
||||
/>
|
||||
<div class="mt-2.5 truncate text-fg">{mix.title}</div>
|
||||
<div class="mt-0.5 truncate text-sm text-fg-3">
|
||||
{count}
|
||||
{count === 1 ? 'canción' : 'canciones'}
|
||||
</div>
|
||||
</a>
|
||||
{#snippet art(artClass)}
|
||||
<MixArt items={mix.items} hue={hueFor(mix.id)} class={artClass} />
|
||||
{/snippet}
|
||||
</MediaCard>
|
||||
|
||||
@@ -82,7 +82,7 @@
|
||||
>
|
||||
{#if title}
|
||||
<div class="flex items-center justify-between">
|
||||
<h2 class="text-lg font-bold tracking-tight text-fg">{title}</h2>
|
||||
<h2 class="text-lg font-semibold tracking-tight text-fg">{title}</h2>
|
||||
<IconButton label="Cerrar" onclick={onClose}>
|
||||
<X class="h-4 w-4" />
|
||||
</IconButton>
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
<div class="flex flex-col gap-4 sm:flex-row sm:items-end sm:justify-between {klass}">
|
||||
<div class="min-w-0">
|
||||
<h1 class="text-2xl font-bold tracking-tight text-fg sm:text-4xl">{title}</h1>
|
||||
<h1 class="text-2xl font-semibold tracking-tight text-fg sm:text-4xl">{title}</h1>
|
||||
{#if subtitle}
|
||||
<p class="mt-2 text-sm text-fg-2 sm:text-base">{subtitle}</p>
|
||||
{/if}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script lang="ts">
|
||||
import MediaCard from './MediaCard.svelte';
|
||||
import PlaylistArt from './PlaylistArt.svelte';
|
||||
import { hueFor } from '$lib/theme/color';
|
||||
|
||||
@@ -14,20 +15,8 @@
|
||||
let { id, name, description, trackIds, updatedAt, index = 0 }: Props = $props();
|
||||
</script>
|
||||
|
||||
<a
|
||||
href="/playlists/{id}"
|
||||
class="group animate-enter min-w-0 text-left"
|
||||
style="animation-delay:{index * 45}ms"
|
||||
>
|
||||
<PlaylistArt
|
||||
playlistId={id}
|
||||
{trackIds}
|
||||
hue={hueFor(id)}
|
||||
version={updatedAt}
|
||||
class="aspect-square w-full rounded-art shadow-art transition group-hover:shadow-art-lg"
|
||||
/>
|
||||
<div class="mt-2.5 truncate text-fg">{name}</div>
|
||||
{#if description}
|
||||
<div class="mt-0.5 truncate text-sm text-fg-3">{description}</div>
|
||||
{/if}
|
||||
</a>
|
||||
<MediaCard href="/playlists/{id}" title={name} subtitle={description} {index}>
|
||||
{#snippet art(artClass)}
|
||||
<PlaylistArt playlistId={id} {trackIds} hue={hueFor(id)} version={updatedAt} class={artClass} />
|
||||
{/snippet}
|
||||
</MediaCard>
|
||||
|
||||
@@ -11,10 +11,12 @@
|
||||
</script>
|
||||
|
||||
{#if actions}
|
||||
<div class="mb-4 flex items-center justify-between gap-4 sm:mb-6 {klass}">
|
||||
<h2 class="text-xl font-bold text-fg sm:text-2xl">{title}</h2>
|
||||
<div class="mb-3 flex items-baseline justify-between gap-4 sm:mb-4 {klass}">
|
||||
<h2 class="text-lg font-semibold tracking-tight text-fg sm:text-xl">{title}</h2>
|
||||
{@render actions()}
|
||||
</div>
|
||||
{:else}
|
||||
<h2 class="mb-4 text-xl font-bold text-fg sm:mb-6 sm:text-2xl {klass}">{title}</h2>
|
||||
<h2 class="mb-3 text-lg font-semibold tracking-tight text-fg sm:mb-4 sm:text-xl {klass}">
|
||||
{title}
|
||||
</h2>
|
||||
{/if}
|
||||
|
||||
@@ -77,7 +77,7 @@
|
||||
</Modal>
|
||||
|
||||
<Modal open={confirmingDelete} onClose={() => (confirmingDelete = false)} maxWidth="max-w-sm">
|
||||
<h2 class="text-lg font-bold tracking-tight text-fg">
|
||||
<h2 class="text-lg font-semibold tracking-tight text-fg">
|
||||
¿Eliminar «{album.title}»?
|
||||
</h2>
|
||||
<p class="mt-2 text-base text-fg-3">
|
||||
|
||||
@@ -64,7 +64,7 @@
|
||||
<div class="min-w-0 flex-1">
|
||||
<p class="text-sm tracking-[0.14em] text-fg-3 uppercase">Álbum</p>
|
||||
<h1
|
||||
class="mt-1.5 text-3xl font-bold tracking-tight break-words text-fg sm:text-5xl md:text-7xl"
|
||||
class="mt-1.5 text-3xl font-semibold tracking-tight break-words text-fg sm:text-5xl md:text-7xl"
|
||||
>
|
||||
{title}
|
||||
</h1>
|
||||
|
||||
@@ -103,7 +103,7 @@
|
||||
{album.isSingle ? 'Single' : album.isEp ? 'EP' : 'Álbum'} · YouTube Music
|
||||
</p>
|
||||
<h1
|
||||
class="mt-1.5 text-3xl font-bold tracking-tight break-words text-fg sm:text-5xl md:text-7xl"
|
||||
class="mt-1.5 text-3xl font-semibold tracking-tight break-words text-fg sm:text-5xl md:text-7xl"
|
||||
>
|
||||
{album.title}
|
||||
</h1>
|
||||
|
||||
@@ -85,7 +85,7 @@
|
||||
<div class="min-w-0 flex-1">
|
||||
<p class="text-sm tracking-[0.14em] text-fg-3 uppercase">Mezcla</p>
|
||||
<h1
|
||||
class="mt-1.5 text-3xl font-bold tracking-tight break-words text-fg sm:text-5xl md:text-7xl"
|
||||
class="mt-1.5 text-3xl font-semibold tracking-tight break-words text-fg sm:text-5xl md:text-7xl"
|
||||
>
|
||||
{mix.title}
|
||||
</h1>
|
||||
|
||||
@@ -79,7 +79,7 @@
|
||||
</Modal>
|
||||
|
||||
<Modal open={confirmingDelete} onClose={() => (confirmingDelete = false)} maxWidth="max-w-sm">
|
||||
<h2 class="text-lg font-bold tracking-tight text-fg">
|
||||
<h2 class="text-lg font-semibold tracking-tight text-fg">
|
||||
¿Eliminar «{playlist.name}»?
|
||||
</h2>
|
||||
<p class="mt-2 text-base text-fg-3">
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
<div class="min-w-0 flex-1">
|
||||
<p class="text-sm tracking-[0.14em] text-fg-3 uppercase">Lista</p>
|
||||
<h1
|
||||
class="mt-1.5 text-3xl font-bold tracking-tight break-words text-fg sm:text-5xl md:text-7xl"
|
||||
class="mt-1.5 text-3xl font-semibold tracking-tight break-words text-fg sm:text-5xl md:text-7xl"
|
||||
>
|
||||
{name}
|
||||
</h1>
|
||||
|
||||
@@ -141,7 +141,7 @@
|
||||
>
|
||||
<Check class="h-8 w-8" strokeWidth={2.5} />
|
||||
</span>
|
||||
<h2 class="mt-6 text-2xl font-bold tracking-tight text-fg">¡Subida!</h2>
|
||||
<h2 class="mt-6 text-2xl font-semibold tracking-tight text-fg">¡Subida!</h2>
|
||||
<p class="mt-3 text-fg-2">
|
||||
«{publishedTitle}» se está procesando. Aparecerá lista para reproducir en tu biblioteca en
|
||||
unos momentos.
|
||||
|
||||
@@ -178,14 +178,14 @@
|
||||
></div>
|
||||
<div class="animate-enter relative flex flex-col gap-2 sm:gap-2.5">
|
||||
<p class="text-sm tracking-[0.14em] text-fg-2 uppercase sm:text-base">{data.greeting}</p>
|
||||
<h1 class="max-w-160 text-3xl leading-[1.05] font-extrabold text-fg sm:text-5xl">
|
||||
<h1 class="max-w-160 text-3xl leading-[1.05] font-semibold text-fg sm:text-5xl">
|
||||
Tu música. Sin límites.
|
||||
</h1>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{#if recentlyPlayed.length > 0}
|
||||
<section class="page-x pt-5 pb-3 sm:pt-6">
|
||||
<section class="page-x pt-10 sm:pt-14">
|
||||
<SectionHeading title="Volver a escuchar" />
|
||||
<MediaGrid min="280px" minMobile="220px">
|
||||
{#each recentlyPlayed as track, i (track.id)}
|
||||
@@ -228,7 +228,7 @@
|
||||
{/if}
|
||||
|
||||
{#if mixes.length > 0 || playlists.length > 0}
|
||||
<section class="grid gap-x-8 gap-y-5 page-x pt-5 pb-3 sm:pt-6 lg:grid-cols-2">
|
||||
<section class="grid gap-x-8 gap-y-10 page-x pt-10 sm:pt-14 lg:grid-cols-2">
|
||||
{#if mixes.length > 0}
|
||||
<div class="min-w-0">
|
||||
<SectionHeading title="Hechas para ti" />
|
||||
@@ -269,7 +269,7 @@
|
||||
{/if}
|
||||
|
||||
{#if albums.length > 0}
|
||||
<section class="page-x pt-5 pb-3 sm:pt-6">
|
||||
<section class="page-x pt-10 sm:pt-14">
|
||||
<SectionHeading title="Álbumes escuchados recientemente">
|
||||
{#snippet actions()}
|
||||
<a href="/albums" class="text-sm text-fg-3 transition hover:text-accent-soft">Ver todos</a>
|
||||
@@ -290,7 +290,7 @@
|
||||
</section>
|
||||
{/if}
|
||||
|
||||
<section class="page-x pt-5 pb-10 sm:pt-6">
|
||||
<section class="page-x pt-10 pb-16 sm:pt-14">
|
||||
<SectionHeading title="Descubrir" />
|
||||
{#if latest.length > 0}
|
||||
<MediaGrid as="ul">
|
||||
@@ -299,6 +299,7 @@
|
||||
id={targetId(item)}
|
||||
title={targetTitle(item)}
|
||||
artist={targetArtist(item)}
|
||||
coverShape="round"
|
||||
coverSrc={targetCoverSrc(item)}
|
||||
hue={hueFor(targetId(item))}
|
||||
explicit={targetExplicit(item)}
|
||||
|
||||
@@ -13,10 +13,10 @@
|
||||
|
||||
<div class="grid min-h-screen place-items-center bg-bg px-6 text-fg">
|
||||
<div class="flex w-full max-w-110 flex-col items-center gap-9 text-center">
|
||||
<div class="text-3xl font-extrabold tracking-tight">Musify</div>
|
||||
<div class="text-3xl font-semibold tracking-tight">Musify</div>
|
||||
|
||||
<div>
|
||||
<h1 class="text-4xl font-extrabold tracking-tight">Tu música. Sin límites.</h1>
|
||||
<h1 class="text-4xl font-semibold tracking-tight">Tu música. Sin límites.</h1>
|
||||
<p class="mt-3 text-base text-fg-2">Inicia sesión para continuar.</p>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user