mirror of
https://github.com/Dictionarry-Hub/profilarr.git
synced 2026-01-31 06:40:50 +01:00
style(mm): card views for all media management pages
This commit is contained in:
@@ -74,7 +74,7 @@
|
||||
<h3 class="truncate text-sm font-semibold text-neutral-900 dark:text-neutral-100">
|
||||
{instance.name}
|
||||
</h3>
|
||||
<div class="mt-1 flex flex-wrap items-center gap-1">
|
||||
<div class="mt-1 flex flex-col items-start gap-1">
|
||||
{#if instance.enabled}
|
||||
<Badge variant="success">Enabled</Badge>
|
||||
{:else}
|
||||
@@ -85,22 +85,22 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Action buttons - always visible, mobile-friendly -->
|
||||
<div class="flex flex-shrink-0 flex-col gap-1">
|
||||
<!-- Action buttons -->
|
||||
<div class="flex flex-shrink-0 items-center gap-1">
|
||||
<a
|
||||
href={instance.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
on:click={(e) => e.stopPropagation()}
|
||||
class="flex items-center justify-center rounded-lg border border-neutral-200 bg-neutral-50 p-2.5 text-neutral-600 transition-colors hover:bg-neutral-100 active:bg-neutral-200 dark:border-neutral-700 dark:bg-neutral-800 dark:text-neutral-400 dark:hover:bg-neutral-700 dark:active:bg-neutral-600"
|
||||
class="rounded-md p-1.5 text-neutral-400 transition-colors hover:text-neutral-600 dark:text-neutral-500 dark:hover:text-neutral-300"
|
||||
>
|
||||
<ExternalLink size={18} />
|
||||
<ExternalLink size={16} />
|
||||
</a>
|
||||
<button
|
||||
on:click={(e) => handleDeleteClick(e, instance)}
|
||||
class="flex items-center justify-center rounded-lg border border-neutral-200 bg-neutral-50 p-2.5 text-neutral-600 transition-colors hover:border-red-200 hover:bg-red-50 hover:text-red-600 active:border-red-300 active:bg-red-100 dark:border-neutral-700 dark:bg-neutral-800 dark:text-neutral-400 dark:hover:border-red-800 dark:hover:bg-red-900/30 dark:hover:text-red-400 dark:active:bg-red-900/50"
|
||||
class="rounded-md p-1.5 text-neutral-400 transition-colors hover:text-red-500 dark:text-neutral-500 dark:hover:text-red-400"
|
||||
>
|
||||
<Trash2 size={18} />
|
||||
<Trash2 size={16} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -113,21 +113,21 @@
|
||||
</div>
|
||||
|
||||
<!-- Action buttons -->
|
||||
<div class="flex flex-shrink-0 flex-col gap-1">
|
||||
<div class="flex flex-shrink-0 items-center gap-1">
|
||||
<a
|
||||
href={database.repository_url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
on:click={(e) => e.stopPropagation()}
|
||||
class="flex items-center justify-center rounded-lg border border-neutral-200 bg-neutral-50 p-2.5 text-neutral-600 transition-colors active:bg-neutral-200 dark:border-neutral-700 dark:bg-neutral-800 dark:text-neutral-300 dark:active:bg-neutral-700"
|
||||
class="rounded-md p-1.5 text-neutral-400 transition-colors hover:text-neutral-600 dark:text-neutral-500 dark:hover:text-neutral-300"
|
||||
>
|
||||
<ExternalLink size={18} />
|
||||
<ExternalLink size={16} />
|
||||
</a>
|
||||
<button
|
||||
on:click={(e) => handleUnlinkClick(e, database)}
|
||||
class="flex items-center justify-center rounded-lg border border-neutral-200 bg-neutral-50 p-2.5 text-neutral-600 transition-colors active:border-red-200 active:bg-red-100 active:text-red-600 dark:border-neutral-700 dark:bg-neutral-800 dark:text-neutral-300 dark:active:border-red-800 dark:active:bg-red-900 dark:active:text-red-400"
|
||||
class="rounded-md p-1.5 text-neutral-400 transition-colors hover:text-red-500 dark:text-neutral-500 dark:hover:text-red-400"
|
||||
>
|
||||
<Unlink size={18} />
|
||||
<Unlink size={16} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
import ActionsBar from '$ui/actions/ActionsBar.svelte';
|
||||
import ActionButton from '$ui/actions/ActionButton.svelte';
|
||||
import SearchAction from '$ui/actions/SearchAction.svelte';
|
||||
import ViewToggle from '$ui/actions/ViewToggle.svelte';
|
||||
import TableView from './views/TableView.svelte';
|
||||
import CardView from './views/CardView.svelte';
|
||||
import { createDataPageStore } from '$lib/client/stores/dataPage';
|
||||
import { goto } from '$app/navigation';
|
||||
import { Plus } from 'lucide-svelte';
|
||||
@@ -11,7 +13,7 @@
|
||||
export let data: PageData;
|
||||
|
||||
// Initialize data page store
|
||||
const { search, filtered, setItems } = createDataPageStore(data.mediaSettingsConfigs, {
|
||||
const { search, view, filtered, setItems } = createDataPageStore(data.mediaSettingsConfigs, {
|
||||
storageKey: 'mediaSettingsView',
|
||||
searchKeys: ['name']
|
||||
});
|
||||
@@ -27,6 +29,7 @@
|
||||
icon={Plus}
|
||||
on:click={() => goto(`/media-management/${data.currentDatabase.id}/media-settings/new`)}
|
||||
/>
|
||||
<ViewToggle bind:value={$view} />
|
||||
</ActionsBar>
|
||||
|
||||
<!-- Media Settings Content -->
|
||||
@@ -45,7 +48,9 @@
|
||||
>
|
||||
<p class="text-neutral-600 dark:text-neutral-400">No media settings configs match your search</p>
|
||||
</div>
|
||||
{:else}
|
||||
{:else if $view === 'table'}
|
||||
<TableView configs={$filtered} databaseId={data.currentDatabase.id} />
|
||||
{:else}
|
||||
<CardView configs={$filtered} databaseId={data.currentDatabase.id} />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import Badge from '$ui/badge/Badge.svelte';
|
||||
import type { MediaSettingsListItem } from '$shared/pcd/display.ts';
|
||||
import radarrLogo from '$lib/client/assets/Radarr.svg';
|
||||
import sonarrLogo from '$lib/client/assets/Sonarr.svg';
|
||||
|
||||
export let configs: MediaSettingsListItem[];
|
||||
export let databaseId: number;
|
||||
|
||||
const logos: Record<string, string> = {
|
||||
radarr: radarrLogo,
|
||||
sonarr: sonarrLogo
|
||||
};
|
||||
|
||||
const propersRepacksConfig: Record<
|
||||
string,
|
||||
{ variant: 'neutral' | 'success' | 'warning'; label: string }
|
||||
> = {
|
||||
doNotPrefer: { variant: 'neutral', label: 'Do Not Prefer' },
|
||||
preferAndUpgrade: { variant: 'success', label: 'Prefer & Upgrade' },
|
||||
doNotUpgradeAutomatically: { variant: 'warning', label: 'No Auto Upgrade' }
|
||||
};
|
||||
|
||||
let loadedImages: Set<string> = new Set();
|
||||
|
||||
function handleImageLoad(name: string) {
|
||||
loadedImages.add(name);
|
||||
loadedImages = loadedImages;
|
||||
}
|
||||
|
||||
function handleCardClick(config: MediaSettingsListItem) {
|
||||
goto(
|
||||
`/media-management/${databaseId}/media-settings/${config.arr_type}/${encodeURIComponent(config.name)}`
|
||||
);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="grid grid-cols-1 gap-3">
|
||||
{#each configs as config}
|
||||
{@const prConfig = propersRepacksConfig[config.propers_repacks] || {
|
||||
variant: 'neutral',
|
||||
label: config.propers_repacks
|
||||
}}
|
||||
<div
|
||||
on:click={() => handleCardClick(config)}
|
||||
on:keydown={(e) => e.key === 'Enter' && handleCardClick(config)}
|
||||
role="button"
|
||||
tabindex="0"
|
||||
class="group flex cursor-pointer items-center gap-4 rounded-lg border border-neutral-200 bg-white p-3 transition-all hover:border-neutral-300 hover:shadow-md active:bg-neutral-50 dark:border-neutral-800 dark:bg-neutral-900 dark:hover:border-neutral-700 dark:active:bg-neutral-800"
|
||||
>
|
||||
<!-- Left: Logo + Name -->
|
||||
<div class="flex min-w-0 flex-1 items-center gap-3">
|
||||
<div class="relative h-10 w-10 flex-shrink-0">
|
||||
{#if !loadedImages.has(config.name)}
|
||||
<div
|
||||
class="absolute inset-0 animate-pulse rounded-lg bg-neutral-200 dark:bg-neutral-700"
|
||||
></div>
|
||||
{/if}
|
||||
<img
|
||||
src={logos[config.arr_type]}
|
||||
alt="{config.arr_type} logo"
|
||||
class="h-10 w-10 rounded-lg {loadedImages.has(config.name)
|
||||
? 'opacity-100'
|
||||
: 'opacity-0'}"
|
||||
on:load={() => handleImageLoad(config.name)}
|
||||
/>
|
||||
</div>
|
||||
<div class="min-w-0">
|
||||
<h3 class="truncate text-sm font-semibold text-neutral-900 dark:text-neutral-100">
|
||||
{config.name}
|
||||
</h3>
|
||||
<div class="mt-1 flex flex-wrap items-center gap-1">
|
||||
<Badge variant={prConfig.variant}>{prConfig.label}</Badge>
|
||||
{#if config.enable_media_info}
|
||||
<Badge variant="success">Media Info</Badge>
|
||||
{:else}
|
||||
<Badge variant="neutral">No Media Info</Badge>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
@@ -2,7 +2,9 @@
|
||||
import ActionsBar from '$ui/actions/ActionsBar.svelte';
|
||||
import ActionButton from '$ui/actions/ActionButton.svelte';
|
||||
import SearchAction from '$ui/actions/SearchAction.svelte';
|
||||
import ViewToggle from '$ui/actions/ViewToggle.svelte';
|
||||
import TableView from './views/TableView.svelte';
|
||||
import CardView from './views/CardView.svelte';
|
||||
import { createDataPageStore } from '$lib/client/stores/dataPage';
|
||||
import { goto } from '$app/navigation';
|
||||
import { Plus } from 'lucide-svelte';
|
||||
@@ -11,7 +13,7 @@
|
||||
export let data: PageData;
|
||||
|
||||
// Initialize data page store
|
||||
const { search, filtered, setItems } = createDataPageStore(data.namingConfigs, {
|
||||
const { search, view, filtered, setItems } = createDataPageStore(data.namingConfigs, {
|
||||
storageKey: 'namingSettingsView',
|
||||
searchKeys: ['name']
|
||||
});
|
||||
@@ -27,6 +29,7 @@
|
||||
icon={Plus}
|
||||
on:click={() => goto(`/media-management/${data.currentDatabase.id}/naming/new`)}
|
||||
/>
|
||||
<ViewToggle bind:value={$view} />
|
||||
</ActionsBar>
|
||||
|
||||
<!-- Naming Configs Content -->
|
||||
@@ -45,7 +48,9 @@
|
||||
>
|
||||
<p class="text-neutral-600 dark:text-neutral-400">No naming configs match your search</p>
|
||||
</div>
|
||||
{:else}
|
||||
{:else if $view === 'table'}
|
||||
<TableView configs={$filtered} databaseId={data.currentDatabase.id} />
|
||||
{:else}
|
||||
<CardView configs={$filtered} databaseId={data.currentDatabase.id} />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import Badge from '$ui/badge/Badge.svelte';
|
||||
import type { NamingListItem } from '$shared/pcd/display.ts';
|
||||
import radarrLogo from '$lib/client/assets/Radarr.svg';
|
||||
import sonarrLogo from '$lib/client/assets/Sonarr.svg';
|
||||
|
||||
export let configs: NamingListItem[];
|
||||
export let databaseId: number;
|
||||
|
||||
const logos: Record<string, string> = {
|
||||
radarr: radarrLogo,
|
||||
sonarr: sonarrLogo
|
||||
};
|
||||
|
||||
let loadedImages: Set<string> = new Set();
|
||||
|
||||
function handleImageLoad(name: string) {
|
||||
loadedImages.add(name);
|
||||
loadedImages = loadedImages;
|
||||
}
|
||||
|
||||
function handleCardClick(config: NamingListItem) {
|
||||
goto(
|
||||
`/media-management/${databaseId}/naming/${config.arr_type}/${encodeURIComponent(config.name)}`
|
||||
);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="grid grid-cols-1 gap-3">
|
||||
{#each configs as config}
|
||||
<div
|
||||
on:click={() => handleCardClick(config)}
|
||||
on:keydown={(e) => e.key === 'Enter' && handleCardClick(config)}
|
||||
role="button"
|
||||
tabindex="0"
|
||||
class="group flex cursor-pointer items-center gap-4 rounded-lg border border-neutral-200 bg-white p-3 transition-all hover:border-neutral-300 hover:shadow-md active:bg-neutral-50 dark:border-neutral-800 dark:bg-neutral-900 dark:hover:border-neutral-700 dark:active:bg-neutral-800"
|
||||
>
|
||||
<!-- Left: Logo + Name -->
|
||||
<div class="flex min-w-0 flex-1 items-center gap-3">
|
||||
<div class="relative h-10 w-10 flex-shrink-0">
|
||||
{#if !loadedImages.has(config.name)}
|
||||
<div
|
||||
class="absolute inset-0 animate-pulse rounded-lg bg-neutral-200 dark:bg-neutral-700"
|
||||
></div>
|
||||
{/if}
|
||||
<img
|
||||
src={logos[config.arr_type]}
|
||||
alt="{config.arr_type} logo"
|
||||
class="h-10 w-10 rounded-lg {loadedImages.has(config.name)
|
||||
? 'opacity-100'
|
||||
: 'opacity-0'}"
|
||||
on:load={() => handleImageLoad(config.name)}
|
||||
/>
|
||||
</div>
|
||||
<div class="min-w-0">
|
||||
<h3 class="truncate text-sm font-semibold text-neutral-900 dark:text-neutral-100">
|
||||
{config.name}
|
||||
</h3>
|
||||
<div class="mt-1">
|
||||
{#if config.rename}
|
||||
<Badge variant="success">Rename Enabled</Badge>
|
||||
{:else}
|
||||
<Badge variant="neutral">Rename Disabled</Badge>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
@@ -2,7 +2,9 @@
|
||||
import ActionsBar from '$ui/actions/ActionsBar.svelte';
|
||||
import ActionButton from '$ui/actions/ActionButton.svelte';
|
||||
import SearchAction from '$ui/actions/SearchAction.svelte';
|
||||
import ViewToggle from '$ui/actions/ViewToggle.svelte';
|
||||
import TableView from './views/TableView.svelte';
|
||||
import CardView from './views/CardView.svelte';
|
||||
import { createDataPageStore } from '$lib/client/stores/dataPage';
|
||||
import { goto } from '$app/navigation';
|
||||
import { Plus } from 'lucide-svelte';
|
||||
@@ -11,7 +13,7 @@
|
||||
export let data: PageData;
|
||||
|
||||
// Initialize data page store
|
||||
const { search, filtered, setItems } = createDataPageStore(data.qualityDefinitionsConfigs, {
|
||||
const { search, view, filtered, setItems } = createDataPageStore(data.qualityDefinitionsConfigs, {
|
||||
storageKey: 'qualityDefinitionsView',
|
||||
searchKeys: ['name']
|
||||
});
|
||||
@@ -27,6 +29,7 @@
|
||||
icon={Plus}
|
||||
on:click={() => goto(`/media-management/${data.currentDatabase.id}/quality-definitions/new`)}
|
||||
/>
|
||||
<ViewToggle bind:value={$view} />
|
||||
</ActionsBar>
|
||||
|
||||
<!-- Quality Definitions Content -->
|
||||
@@ -45,7 +48,9 @@
|
||||
>
|
||||
<p class="text-neutral-600 dark:text-neutral-400">No quality definitions configs match your search</p>
|
||||
</div>
|
||||
{:else}
|
||||
{:else if $view === 'table'}
|
||||
<TableView configs={$filtered} databaseId={data.currentDatabase.id} />
|
||||
{:else}
|
||||
<CardView configs={$filtered} databaseId={data.currentDatabase.id} />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import Badge from '$ui/badge/Badge.svelte';
|
||||
import type { QualityDefinitionListItem } from '$shared/pcd/display.ts';
|
||||
import radarrLogo from '$lib/client/assets/Radarr.svg';
|
||||
import sonarrLogo from '$lib/client/assets/Sonarr.svg';
|
||||
|
||||
export let configs: QualityDefinitionListItem[];
|
||||
export let databaseId: number;
|
||||
|
||||
const logos: Record<string, string> = {
|
||||
radarr: radarrLogo,
|
||||
sonarr: sonarrLogo
|
||||
};
|
||||
|
||||
let loadedImages: Set<string> = new Set();
|
||||
|
||||
function handleImageLoad(name: string) {
|
||||
loadedImages.add(name);
|
||||
loadedImages = loadedImages;
|
||||
}
|
||||
|
||||
function handleCardClick(config: QualityDefinitionListItem) {
|
||||
goto(
|
||||
`/media-management/${databaseId}/quality-definitions/${config.arr_type}/${encodeURIComponent(config.name)}`
|
||||
);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="grid grid-cols-1 gap-3">
|
||||
{#each configs as config}
|
||||
<div
|
||||
on:click={() => handleCardClick(config)}
|
||||
on:keydown={(e) => e.key === 'Enter' && handleCardClick(config)}
|
||||
role="button"
|
||||
tabindex="0"
|
||||
class="group flex cursor-pointer items-center gap-4 rounded-lg border border-neutral-200 bg-white p-3 transition-all hover:border-neutral-300 hover:shadow-md active:bg-neutral-50 dark:border-neutral-800 dark:bg-neutral-900 dark:hover:border-neutral-700 dark:active:bg-neutral-800"
|
||||
>
|
||||
<!-- Left: Logo + Name -->
|
||||
<div class="flex min-w-0 flex-1 items-center gap-3">
|
||||
<div class="relative h-10 w-10 flex-shrink-0">
|
||||
{#if !loadedImages.has(config.name)}
|
||||
<div
|
||||
class="absolute inset-0 animate-pulse rounded-lg bg-neutral-200 dark:bg-neutral-700"
|
||||
></div>
|
||||
{/if}
|
||||
<img
|
||||
src={logos[config.arr_type]}
|
||||
alt="{config.arr_type} logo"
|
||||
class="h-10 w-10 rounded-lg {loadedImages.has(config.name)
|
||||
? 'opacity-100'
|
||||
: 'opacity-0'}"
|
||||
on:load={() => handleImageLoad(config.name)}
|
||||
/>
|
||||
</div>
|
||||
<div class="min-w-0">
|
||||
<h3 class="truncate text-sm font-semibold text-neutral-900 dark:text-neutral-100">
|
||||
{config.name}
|
||||
</h3>
|
||||
<div class="mt-1">
|
||||
<Badge variant="neutral">{config.quality_count} qualities</Badge>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
Reference in New Issue
Block a user