mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-25 18:34:38 +02:00
feat(storage): add SVG empty state illustrations for all pages
Replace plain text empty states with themed SVG illustrations: - files/folder: cloud folder with upload arrows - trash: empty bin with checkmark - favorites: star outline - search: magnifying glass - shared: connected nodes Reusable EmptyState component with snippet-based action slots. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
9f66800945
commit
56307a3dbb
7 changed files with 371 additions and 39 deletions
324
apps/storage/apps/web/src/lib/components/files/EmptyState.svelte
Normal file
324
apps/storage/apps/web/src/lib/components/files/EmptyState.svelte
Normal file
|
|
@ -0,0 +1,324 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import type { Snippet } from 'svelte';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
/** Which illustration to show */
|
||||||
|
type: 'files' | 'trash' | 'favorites' | 'search' | 'folder' | 'shared';
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
actions?: Snippet;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { type, title, description, actions }: Props = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="empty-state">
|
||||||
|
<div class="illustration">
|
||||||
|
{#if type === 'files' || type === 'folder'}
|
||||||
|
<!-- Cloud folder illustration -->
|
||||||
|
<svg
|
||||||
|
width="120"
|
||||||
|
height="120"
|
||||||
|
viewBox="0 0 120 120"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<circle cx="60" cy="60" r="50" fill="rgb(var(--color-primary) / 0.06)" />
|
||||||
|
<rect
|
||||||
|
x="30"
|
||||||
|
y="42"
|
||||||
|
width="60"
|
||||||
|
height="40"
|
||||||
|
rx="4"
|
||||||
|
fill="rgb(var(--color-primary) / 0.15)"
|
||||||
|
stroke="rgb(var(--color-primary) / 0.3)"
|
||||||
|
stroke-width="1.5"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M30 46C30 43.7909 31.7909 42 34 42H48L53 36H86C88.2091 36 90 37.7909 90 40V42H30Z"
|
||||||
|
fill="rgb(var(--color-primary) / 0.25)"
|
||||||
|
stroke="rgb(var(--color-primary) / 0.3)"
|
||||||
|
stroke-width="1.5"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M52 60L56 56L60 60"
|
||||||
|
stroke="rgb(var(--color-primary) / 0.5)"
|
||||||
|
stroke-width="2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
/>
|
||||||
|
<line
|
||||||
|
x1="56"
|
||||||
|
y1="56"
|
||||||
|
x2="56"
|
||||||
|
y2="72"
|
||||||
|
stroke="rgb(var(--color-primary) / 0.5)"
|
||||||
|
stroke-width="2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M64 60L68 56L72 60"
|
||||||
|
stroke="rgb(var(--color-primary) / 0.3)"
|
||||||
|
stroke-width="2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
/>
|
||||||
|
<line
|
||||||
|
x1="68"
|
||||||
|
y1="56"
|
||||||
|
x2="68"
|
||||||
|
y2="68"
|
||||||
|
stroke="rgb(var(--color-primary) / 0.3)"
|
||||||
|
stroke-width="2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
{:else if type === 'trash'}
|
||||||
|
<!-- Empty trash illustration -->
|
||||||
|
<svg
|
||||||
|
width="120"
|
||||||
|
height="120"
|
||||||
|
viewBox="0 0 120 120"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<circle cx="60" cy="60" r="50" fill="rgb(var(--color-text-secondary) / 0.05)" />
|
||||||
|
<rect
|
||||||
|
x="40"
|
||||||
|
y="44"
|
||||||
|
width="40"
|
||||||
|
height="42"
|
||||||
|
rx="3"
|
||||||
|
fill="rgb(var(--color-text-secondary) / 0.08)"
|
||||||
|
stroke="rgb(var(--color-text-secondary) / 0.2)"
|
||||||
|
stroke-width="1.5"
|
||||||
|
/>
|
||||||
|
<rect
|
||||||
|
x="36"
|
||||||
|
y="38"
|
||||||
|
width="48"
|
||||||
|
height="6"
|
||||||
|
rx="2"
|
||||||
|
fill="rgb(var(--color-text-secondary) / 0.12)"
|
||||||
|
stroke="rgb(var(--color-text-secondary) / 0.2)"
|
||||||
|
stroke-width="1.5"
|
||||||
|
/>
|
||||||
|
<line
|
||||||
|
x1="50"
|
||||||
|
y1="52"
|
||||||
|
x2="50"
|
||||||
|
y2="78"
|
||||||
|
stroke="rgb(var(--color-text-secondary) / 0.15)"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke-linecap="round"
|
||||||
|
/>
|
||||||
|
<line
|
||||||
|
x1="60"
|
||||||
|
y1="52"
|
||||||
|
x2="60"
|
||||||
|
y2="78"
|
||||||
|
stroke="rgb(var(--color-text-secondary) / 0.15)"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke-linecap="round"
|
||||||
|
/>
|
||||||
|
<line
|
||||||
|
x1="70"
|
||||||
|
y1="52"
|
||||||
|
x2="70"
|
||||||
|
y2="78"
|
||||||
|
stroke="rgb(var(--color-text-secondary) / 0.15)"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke-linecap="round"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M52 38V34C52 32.8954 52.8954 32 54 32H66C67.1046 32 68 32.8954 68 34V38"
|
||||||
|
stroke="rgb(var(--color-text-secondary) / 0.2)"
|
||||||
|
stroke-width="1.5"
|
||||||
|
/>
|
||||||
|
<circle
|
||||||
|
cx="60"
|
||||||
|
cy="62"
|
||||||
|
r="8"
|
||||||
|
fill="rgb(var(--color-success) / 0.1)"
|
||||||
|
stroke="rgb(var(--color-success) / 0.3)"
|
||||||
|
stroke-width="1.5"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M56 62L59 65L65 59"
|
||||||
|
stroke="rgb(var(--color-success) / 0.5)"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
{:else if type === 'favorites'}
|
||||||
|
<!-- No favorites illustration -->
|
||||||
|
<svg
|
||||||
|
width="120"
|
||||||
|
height="120"
|
||||||
|
viewBox="0 0 120 120"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<circle cx="60" cy="60" r="50" fill="rgb(var(--color-warning) / 0.05)" />
|
||||||
|
<path
|
||||||
|
d="M60 40L65.5 51.2L78 53L69 61.8L71 74L60 68.2L49 74L51 61.8L42 53L54.5 51.2L60 40Z"
|
||||||
|
fill="rgb(var(--color-warning) / 0.1)"
|
||||||
|
stroke="rgb(var(--color-warning) / 0.3)"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
/>
|
||||||
|
<circle cx="60" cy="57" r="4" fill="rgb(var(--color-warning) / 0.15)" />
|
||||||
|
<path
|
||||||
|
d="M60 45L63 51"
|
||||||
|
stroke="rgb(var(--color-warning) / 0.2)"
|
||||||
|
stroke-width="1"
|
||||||
|
stroke-linecap="round"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M60 45L57 51"
|
||||||
|
stroke="rgb(var(--color-warning) / 0.2)"
|
||||||
|
stroke-width="1"
|
||||||
|
stroke-linecap="round"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
{:else if type === 'search'}
|
||||||
|
<!-- No results illustration -->
|
||||||
|
<svg
|
||||||
|
width="120"
|
||||||
|
height="120"
|
||||||
|
viewBox="0 0 120 120"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<circle cx="60" cy="60" r="50" fill="rgb(var(--color-primary) / 0.05)" />
|
||||||
|
<circle
|
||||||
|
cx="54"
|
||||||
|
cy="54"
|
||||||
|
r="16"
|
||||||
|
fill="rgb(var(--color-primary) / 0.06)"
|
||||||
|
stroke="rgb(var(--color-primary) / 0.25)"
|
||||||
|
stroke-width="2"
|
||||||
|
/>
|
||||||
|
<line
|
||||||
|
x1="65"
|
||||||
|
y1="65"
|
||||||
|
x2="78"
|
||||||
|
y2="78"
|
||||||
|
stroke="rgb(var(--color-primary) / 0.25)"
|
||||||
|
stroke-width="3"
|
||||||
|
stroke-linecap="round"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M48 54H60"
|
||||||
|
stroke="rgb(var(--color-primary) / 0.15)"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke-linecap="round"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M54 48V60"
|
||||||
|
stroke="rgb(var(--color-primary) / 0.15)"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke-linecap="round"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
{:else if type === 'shared'}
|
||||||
|
<!-- No shares illustration -->
|
||||||
|
<svg
|
||||||
|
width="120"
|
||||||
|
height="120"
|
||||||
|
viewBox="0 0 120 120"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<circle cx="60" cy="60" r="50" fill="rgb(var(--color-primary) / 0.05)" />
|
||||||
|
<circle
|
||||||
|
cx="44"
|
||||||
|
cy="52"
|
||||||
|
r="6"
|
||||||
|
fill="rgb(var(--color-primary) / 0.1)"
|
||||||
|
stroke="rgb(var(--color-primary) / 0.25)"
|
||||||
|
stroke-width="1.5"
|
||||||
|
/>
|
||||||
|
<circle
|
||||||
|
cx="76"
|
||||||
|
cy="42"
|
||||||
|
r="6"
|
||||||
|
fill="rgb(var(--color-primary) / 0.1)"
|
||||||
|
stroke="rgb(var(--color-primary) / 0.25)"
|
||||||
|
stroke-width="1.5"
|
||||||
|
/>
|
||||||
|
<circle
|
||||||
|
cx="76"
|
||||||
|
cy="68"
|
||||||
|
r="6"
|
||||||
|
fill="rgb(var(--color-primary) / 0.1)"
|
||||||
|
stroke="rgb(var(--color-primary) / 0.25)"
|
||||||
|
stroke-width="1.5"
|
||||||
|
/>
|
||||||
|
<line
|
||||||
|
x1="50"
|
||||||
|
y1="50"
|
||||||
|
x2="70"
|
||||||
|
y2="44"
|
||||||
|
stroke="rgb(var(--color-primary) / 0.2)"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke-dasharray="3 3"
|
||||||
|
/>
|
||||||
|
<line
|
||||||
|
x1="50"
|
||||||
|
y1="55"
|
||||||
|
x2="70"
|
||||||
|
y2="65"
|
||||||
|
stroke="rgb(var(--color-primary) / 0.2)"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke-dasharray="3 3"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2 class="empty-title">{title}</h2>
|
||||||
|
<p class="empty-description">{description}</p>
|
||||||
|
|
||||||
|
{#if actions}
|
||||||
|
<div class="empty-actions">
|
||||||
|
{@render actions()}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.empty-state {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 3rem 2rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.illustration {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-title {
|
||||||
|
margin: 0 0 0.5rem;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: rgb(var(--color-text-primary));
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-description {
|
||||||
|
margin: 0 0 1.5rem;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: rgb(var(--color-text-secondary));
|
||||||
|
max-width: 320px;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -11,6 +11,7 @@
|
||||||
import FilePreviewModal from '$lib/components/files/FilePreviewModal.svelte';
|
import FilePreviewModal from '$lib/components/files/FilePreviewModal.svelte';
|
||||||
import FileSkeletonGrid from '$lib/components/files/FileSkeletonGrid.svelte';
|
import FileSkeletonGrid from '$lib/components/files/FileSkeletonGrid.svelte';
|
||||||
import FileSkeletonList from '$lib/components/files/FileSkeletonList.svelte';
|
import FileSkeletonList from '$lib/components/files/FileSkeletonList.svelte';
|
||||||
|
import EmptyState from '$lib/components/files/EmptyState.svelte';
|
||||||
|
|
||||||
let previewFile = $state<StorageFile | null>(null);
|
let previewFile = $state<StorageFile | null>(null);
|
||||||
let files = $state<StorageFile[]>([]);
|
let files = $state<StorageFile[]>([]);
|
||||||
|
|
@ -111,11 +112,11 @@
|
||||||
<button onclick={loadFavorites}>Erneut versuchen</button>
|
<button onclick={loadFavorites}>Erneut versuchen</button>
|
||||||
</div>
|
</div>
|
||||||
{:else if files.length === 0 && folders.length === 0}
|
{:else if files.length === 0 && folders.length === 0}
|
||||||
<div class="empty-state">
|
<EmptyState
|
||||||
<Heart size={48} />
|
type="favorites"
|
||||||
<h2>Keine Favoriten</h2>
|
title="Keine Favoriten"
|
||||||
<p>Markiere Dateien und Ordner als Favoriten, um sie hier schnell zu finden.</p>
|
description="Markiere Dateien und Ordner als Favoriten, um sie hier schnell zu finden."
|
||||||
</div>
|
/>
|
||||||
{:else if filesStore.viewMode === 'grid'}
|
{:else if filesStore.viewMode === 'grid'}
|
||||||
<FileGrid
|
<FileGrid
|
||||||
{files}
|
{files}
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@
|
||||||
import FilePreviewModal from '$lib/components/files/FilePreviewModal.svelte';
|
import FilePreviewModal from '$lib/components/files/FilePreviewModal.svelte';
|
||||||
import FileSkeletonGrid from '$lib/components/files/FileSkeletonGrid.svelte';
|
import FileSkeletonGrid from '$lib/components/files/FileSkeletonGrid.svelte';
|
||||||
import FileSkeletonList from '$lib/components/files/FileSkeletonList.svelte';
|
import FileSkeletonList from '$lib/components/files/FileSkeletonList.svelte';
|
||||||
|
import EmptyState from '$lib/components/files/EmptyState.svelte';
|
||||||
|
|
||||||
let previewFile = $state<StorageFile | null>(null);
|
let previewFile = $state<StorageFile | null>(null);
|
||||||
let showUploadZone = $state(false);
|
let showUploadZone = $state(false);
|
||||||
|
|
@ -247,11 +248,12 @@
|
||||||
<button onclick={() => filesStore.loadFolder()}>Erneut versuchen</button>
|
<button onclick={() => filesStore.loadFolder()}>Erneut versuchen</button>
|
||||||
</div>
|
</div>
|
||||||
{:else if filesStore.files.length === 0 && filesStore.folders.length === 0}
|
{:else if filesStore.files.length === 0 && filesStore.folders.length === 0}
|
||||||
<div class="empty-state">
|
<EmptyState
|
||||||
<UploadSimple size={48} />
|
type="files"
|
||||||
<h2>Noch keine Dateien</h2>
|
title="Noch keine Dateien"
|
||||||
<p>Lade deine ersten Dateien hoch oder erstelle einen Ordner.</p>
|
description="Lade deine ersten Dateien hoch oder erstelle einen Ordner."
|
||||||
<div class="empty-actions">
|
>
|
||||||
|
{#snippet actions()}
|
||||||
<button class="action-btn" onclick={() => (showNewFolderModal = true)}>
|
<button class="action-btn" onclick={() => (showNewFolderModal = true)}>
|
||||||
<FolderPlus size={18} />
|
<FolderPlus size={18} />
|
||||||
<span>Neuer Ordner</span>
|
<span>Neuer Ordner</span>
|
||||||
|
|
@ -260,8 +262,8 @@
|
||||||
<UploadSimple size={18} />
|
<UploadSimple size={18} />
|
||||||
<span>Hochladen</span>
|
<span>Hochladen</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
{/snippet}
|
||||||
</div>
|
</EmptyState>
|
||||||
{:else if filesStore.viewMode === 'grid'}
|
{:else if filesStore.viewMode === 'grid'}
|
||||||
<FileGrid
|
<FileGrid
|
||||||
files={filesStore.files}
|
files={filesStore.files}
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@
|
||||||
import FilePreviewModal from '$lib/components/files/FilePreviewModal.svelte';
|
import FilePreviewModal from '$lib/components/files/FilePreviewModal.svelte';
|
||||||
import FileSkeletonGrid from '$lib/components/files/FileSkeletonGrid.svelte';
|
import FileSkeletonGrid from '$lib/components/files/FileSkeletonGrid.svelte';
|
||||||
import FileSkeletonList from '$lib/components/files/FileSkeletonList.svelte';
|
import FileSkeletonList from '$lib/components/files/FileSkeletonList.svelte';
|
||||||
|
import EmptyState from '$lib/components/files/EmptyState.svelte';
|
||||||
|
|
||||||
let previewFile = $state<StorageFile | null>(null);
|
let previewFile = $state<StorageFile | null>(null);
|
||||||
let showUploadZone = $state(false);
|
let showUploadZone = $state(false);
|
||||||
|
|
@ -263,11 +264,12 @@
|
||||||
<button onclick={() => filesStore.loadFolder(folderId)}>Erneut versuchen</button>
|
<button onclick={() => filesStore.loadFolder(folderId)}>Erneut versuchen</button>
|
||||||
</div>
|
</div>
|
||||||
{:else if filesStore.files.length === 0 && filesStore.folders.length === 0}
|
{:else if filesStore.files.length === 0 && filesStore.folders.length === 0}
|
||||||
<div class="empty-state">
|
<EmptyState
|
||||||
<UploadSimple size={48} />
|
type="folder"
|
||||||
<h2>Leerer Ordner</h2>
|
title="Leerer Ordner"
|
||||||
<p>Dieser Ordner ist leer. Lade Dateien hoch oder erstelle Unterordner.</p>
|
description="Dieser Ordner ist leer. Lade Dateien hoch oder erstelle Unterordner."
|
||||||
<div class="empty-actions">
|
>
|
||||||
|
{#snippet actions()}
|
||||||
<button class="action-btn" onclick={() => (showNewFolderModal = true)}>
|
<button class="action-btn" onclick={() => (showNewFolderModal = true)}>
|
||||||
<FolderPlus size={18} />
|
<FolderPlus size={18} />
|
||||||
<span>Neuer Ordner</span>
|
<span>Neuer Ordner</span>
|
||||||
|
|
@ -276,8 +278,8 @@
|
||||||
<UploadSimple size={18} />
|
<UploadSimple size={18} />
|
||||||
<span>Hochladen</span>
|
<span>Hochladen</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
{/snippet}
|
||||||
</div>
|
</EmptyState>
|
||||||
{:else if filesStore.viewMode === 'grid'}
|
{:else if filesStore.viewMode === 'grid'}
|
||||||
<FileGrid
|
<FileGrid
|
||||||
files={filesStore.files}
|
files={filesStore.files}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
import { StorageEvents } from '@manacore/shared-utils/analytics';
|
import { StorageEvents } from '@manacore/shared-utils/analytics';
|
||||||
import type { StorageFile, StorageFolder } from '$lib/api/client';
|
import type { StorageFile, StorageFolder } from '$lib/api/client';
|
||||||
import { filesStore } from '$lib/stores/files.svelte';
|
import { filesStore } from '$lib/stores/files.svelte';
|
||||||
|
import EmptyState from '$lib/components/files/EmptyState.svelte';
|
||||||
import FileGrid from '$lib/components/files/FileGrid.svelte';
|
import FileGrid from '$lib/components/files/FileGrid.svelte';
|
||||||
import FileList from '$lib/components/files/FileList.svelte';
|
import FileList from '$lib/components/files/FileList.svelte';
|
||||||
import FilePreviewModal from '$lib/components/files/FilePreviewModal.svelte';
|
import FilePreviewModal from '$lib/components/files/FilePreviewModal.svelte';
|
||||||
|
|
@ -118,11 +119,11 @@
|
||||||
<p>Suche läuft...</p>
|
<p>Suche läuft...</p>
|
||||||
</div>
|
</div>
|
||||||
{:else if searched && files.length === 0 && folders.length === 0}
|
{:else if searched && files.length === 0 && folders.length === 0}
|
||||||
<div class="empty-state">
|
<EmptyState
|
||||||
<MagnifyingGlass size={48} />
|
type="search"
|
||||||
<h2>Keine Ergebnisse</h2>
|
title="Keine Ergebnisse"
|
||||||
<p>Keine Dateien oder Ordner für "{query}" gefunden.</p>
|
description={'Keine Dateien oder Ordner für "' + query + '" gefunden.'}
|
||||||
</div>
|
/>
|
||||||
{:else if searched}
|
{:else if searched}
|
||||||
<div class="results-header">
|
<div class="results-header">
|
||||||
<span>{files.length + folders.length} Ergebnis(se) für "{query}"</span>
|
<span>{files.length + folders.length} Ergebnis(se) für "{query}"</span>
|
||||||
|
|
@ -134,11 +135,11 @@
|
||||||
<FileList {files} {folders} onFileClick={handleFileClick} onFolderClick={handleFolderClick} />
|
<FileList {files} {folders} onFileClick={handleFileClick} onFolderClick={handleFolderClick} />
|
||||||
{/if}
|
{/if}
|
||||||
{:else}
|
{:else}
|
||||||
<div class="empty-state">
|
<EmptyState
|
||||||
<MagnifyingGlass size={48} />
|
type="search"
|
||||||
<h2>Dateien durchsuchen</h2>
|
title="Dateien durchsuchen"
|
||||||
<p>Gib einen Suchbegriff ein, um Dateien und Ordner zu finden.</p>
|
description="Gib einen Suchbegriff ein, um Dateien und Ordner zu finden."
|
||||||
</div>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
import { StorageEvents } from '@manacore/shared-utils/analytics';
|
import { StorageEvents } from '@manacore/shared-utils/analytics';
|
||||||
import type { Share } from '$lib/api/client';
|
import type { Share } from '$lib/api/client';
|
||||||
import { toastStore } from '@manacore/shared-ui';
|
import { toastStore } from '@manacore/shared-ui';
|
||||||
|
import EmptyState from '$lib/components/files/EmptyState.svelte';
|
||||||
|
|
||||||
let shares = $state<Share[]>([]);
|
let shares = $state<Share[]>([]);
|
||||||
let loading = $state(true);
|
let loading = $state(true);
|
||||||
|
|
@ -95,11 +96,11 @@
|
||||||
<button onclick={loadShares}>Erneut versuchen</button>
|
<button onclick={loadShares}>Erneut versuchen</button>
|
||||||
</div>
|
</div>
|
||||||
{:else if shares.length === 0}
|
{:else if shares.length === 0}
|
||||||
<div class="empty-state">
|
<EmptyState
|
||||||
<ShareNetwork size={48} />
|
type="shared"
|
||||||
<h2>Keine geteilten Links</h2>
|
title="Keine geteilten Links"
|
||||||
<p>Teile Dateien oder Ordner, um Links hier zu verwalten.</p>
|
description="Teile Dateien oder Ordner, um Links hier zu verwalten."
|
||||||
</div>
|
/>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="shares-list">
|
<div class="shares-list">
|
||||||
{#each shares as share (share.id)}
|
{#each shares as share (share.id)}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
import type { StorageFile, StorageFolder } from '$lib/api/client';
|
import type { StorageFile, StorageFolder } from '$lib/api/client';
|
||||||
import { toastStore } from '@manacore/shared-ui';
|
import { toastStore } from '@manacore/shared-ui';
|
||||||
import FileSkeletonList from '$lib/components/files/FileSkeletonList.svelte';
|
import FileSkeletonList from '$lib/components/files/FileSkeletonList.svelte';
|
||||||
|
import EmptyState from '$lib/components/files/EmptyState.svelte';
|
||||||
|
|
||||||
let files = $state<StorageFile[]>([]);
|
let files = $state<StorageFile[]>([]);
|
||||||
let folders = $state<StorageFolder[]>([]);
|
let folders = $state<StorageFolder[]>([]);
|
||||||
|
|
@ -117,11 +118,11 @@
|
||||||
<button onclick={loadTrash}>Erneut versuchen</button>
|
<button onclick={loadTrash}>Erneut versuchen</button>
|
||||||
</div>
|
</div>
|
||||||
{:else if files.length === 0 && folders.length === 0}
|
{:else if files.length === 0 && folders.length === 0}
|
||||||
<div class="empty-state">
|
<EmptyState
|
||||||
<Trash size={48} />
|
type="trash"
|
||||||
<h2>Papierkorb ist leer</h2>
|
title="Papierkorb ist leer"
|
||||||
<p>Gelöschte Dateien und Ordner erscheinen hier.</p>
|
description="Gelöschte Dateien und Ordner erscheinen hier."
|
||||||
</div>
|
/>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="trash-list">
|
<div class="trash-list">
|
||||||
{#each folders as folder (folder.id)}
|
{#each folders as folder (folder.id)}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue