Files
twitch-panels/src/components/PanelList.svelte
T

261 lines
6.2 KiB
Svelte

<script lang="ts">
import { onMount } from "svelte";
import { panelStore } from "../stores/panelStore";
import { panelStorage } from "../lib/utils/panelStorage";
import type { Panel } from "../lib/types/panel";
import { IconButton } from "../lib/components/ui";
let { onPanelSelect, onPanelDelete }: {
onPanelSelect: (panel: Panel) => void;
onPanelDelete: (panelId: string) => void;
} = $props();
let panels = $state<Panel[]>([]);
let selectedPanelId = $state<string | null>(null);
let errorMessage = $state<string | null>(null);
onMount(() => {
loadPanels();
});
function loadPanels() {
try {
errorMessage = null;
panels = panelStorage.getAllPanels();
// Если есть текущая панель в store, отмечаем её как выбранную
const currentPanel = $panelStore;
if (currentPanel) {
selectedPanelId = currentPanel.id;
}
} catch (error) {
errorMessage = error instanceof Error ? error.message : "Ошибка загрузки панелей";
}
}
function handlePanelSelect(panel: Panel) {
selectedPanelId = panel.id;
onPanelSelect(panel);
}
function handlePanelDelete(panelId: string) {
try {
errorMessage = null;
const result = panelStorage.deletePanel(panelId);
if (result.success) {
panels = panels.filter((p) => p.id !== panelId);
// Если удалили выбранную панель, сбрасываем выбор
if (selectedPanelId === panelId) {
selectedPanelId = null;
}
// Уведомляем родительский компонент
onPanelDelete(panelId);
} else {
errorMessage = result.error || "Ошибка удаления панели";
}
} catch (error) {
errorMessage = error instanceof Error ? error.message : "Ошибка удаления панели";
}
}
function formatDate(date: Date): string {
return new Intl.DateTimeFormat("ru-RU", {
day: "2-digit",
month: "2-digit",
year: "numeric",
hour: "2-digit",
minute: "2-digit",
}).format(date);
}
</script>
<div class="panel-list">
<div class="panel-list-header">
<h2>Сохраненные панели</h2>
<IconButton variant="secondary" onclick={loadPanels} ariaLabel="Обновить список">
</IconButton>
</div>
{#if errorMessage}
<div class="error-message">
{errorMessage}
</div>
{/if}
{#if panels.length === 0}
<div class="empty-state">
<p>Нет сохраненных панелей</p>
</div>
{:else}
<div class="panels-grid">
{#each panels as panel (panel.id)}
<div
class="panel-card {selectedPanelId === panel.id ? 'selected' : ''}"
onclick={() => handlePanelSelect(panel)}
role="button"
tabindex="0"
>
<div class="panel-preview">
{#if panel.backgroundImage}
<img
src={panel.backgroundImage}
alt="Panel preview"
class="panel-image"
/>
{:else}
<div class="no-image">Нет изображения</div>
{/if}
</div>
<div class="panel-info">
<div class="panel-meta">
<span class="panel-id">ID: {panel.id.slice(0, 8)}...</span>
<span class="panel-date">{formatDate(panel.updatedAt)}</span>
</div>
<div class="panel-actions">
<IconButton
variant="danger"
onclick={(e) => { e.stopPropagation(); handlePanelDelete(panel.id); }}
ariaLabel="Удалить панель"
>
🗑️
</IconButton>
</div>
</div>
</div>
{/each}
</div>
{/if}
</div>
<style>
.panel-list {
display: flex;
flex-direction: column;
gap: 1.5rem;
width: 100%;
max-width: 1200px;
margin: 0 auto;
}
.panel-list-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem;
background: #f9f9f9;
border-radius: 8px;
}
.panel-list-header h2 {
margin: 0;
color: #333;
font-size: 1.5rem;
}
.error-message {
background: #ffebee;
color: #c62828;
padding: 0.75rem;
border-radius: 4px;
border: 1px solid #ffcdd2;
}
.empty-state {
text-align: center;
padding: 2rem;
color: #666;
background: #f9f9f9;
border-radius: 8px;
}
.empty-state p {
margin: 0;
}
.panels-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 1rem;
}
.panel-card {
border: 2px solid #ddd;
border-radius: 8px;
overflow: hidden;
cursor: pointer;
transition: all 0.3s ease;
background: white;
}
.panel-card:hover {
border-color: #007bff;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.panel-card.selected {
border-color: #007bff;
background: #f0f8ff;
}
.panel-preview {
position: relative;
width: 100%;
padding-top: 31.25%; /* 320:100 aspect ratio */
background: #f5f5f5;
}
.panel-image {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
}
.no-image {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
color: #999;
font-size: 0.9rem;
}
.panel-info {
padding: 0.75rem;
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.panel-meta {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 0.8rem;
color: #666;
}
.panel-id {
font-family: monospace;
}
.panel-actions {
display: flex;
justify-content: flex-end;
gap: 0.5rem;
}
</style>