mirror of
https://github.com/kmc7468/arkvault.git
synced 2026-02-04 16:16:55 +00:00
Compare commits
7 Commits
174305ca1b
...
v0.6.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
90ac5ba4c3 | ||
|
|
dfffa004ac | ||
|
|
0cd55a413d | ||
|
|
361d966a59 | ||
|
|
aef43b8bfa | ||
|
|
7f128cccf6 | ||
|
|
a198e5f6dc |
@@ -1,46 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import { createWindowVirtualizer } from "@tanstack/svelte-virtual";
|
|
||||||
import { untrack, type Snippet } from "svelte";
|
|
||||||
import type { ClassValue } from "svelte/elements";
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
class?: ClassValue;
|
|
||||||
count: number;
|
|
||||||
item: Snippet<[index: number]>;
|
|
||||||
itemHeight: (index: number) => number;
|
|
||||||
placeholder?: Snippet;
|
|
||||||
}
|
|
||||||
|
|
||||||
let { class: className, count, item, itemHeight, placeholder }: Props = $props();
|
|
||||||
|
|
||||||
const virtualizer = $derived(
|
|
||||||
createWindowVirtualizer({
|
|
||||||
count: untrack(() => count),
|
|
||||||
estimateSize: itemHeight,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
const measureItem = (node: HTMLElement) => {
|
|
||||||
$effect(() => $virtualizer.measureElement(node));
|
|
||||||
};
|
|
||||||
|
|
||||||
$effect(() => $virtualizer.setOptions({ count }));
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class={["relative", className]}>
|
|
||||||
<div style:height="{$virtualizer.getTotalSize()}px">
|
|
||||||
{#each $virtualizer.getVirtualItems() as virtualItem (virtualItem.key)}
|
|
||||||
<div
|
|
||||||
class="absolute left-0 top-0 w-full"
|
|
||||||
style:transform="translateY({virtualItem.start}px)"
|
|
||||||
data-index={virtualItem.index}
|
|
||||||
use:measureItem
|
|
||||||
>
|
|
||||||
{@render item(virtualItem.index)}
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
{#if placeholder && $virtualizer.getVirtualItems().length === 0}
|
|
||||||
{@render placeholder()}
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
@@ -3,4 +3,3 @@ export * from "./buttons";
|
|||||||
export * from "./divs";
|
export * from "./divs";
|
||||||
export * from "./inputs";
|
export * from "./inputs";
|
||||||
export { default as Modal } from "./Modal.svelte";
|
export { default as Modal } from "./Modal.svelte";
|
||||||
export { default as RowVirtualizer } from "./RowVirtualizer.svelte";
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { untrack } from "svelte";
|
import { untrack } from "svelte";
|
||||||
import { get, type Writable } from "svelte/store";
|
import { get, type Writable } from "svelte/store";
|
||||||
import { CheckBox, RowVirtualizer } from "$lib/components/atoms";
|
import { CheckBox } from "$lib/components/atoms";
|
||||||
import { SubCategories, type SelectedCategory } from "$lib/components/molecules";
|
import { SubCategories, type SelectedCategory } from "$lib/components/molecules";
|
||||||
import { getFileInfo, type FileInfo, type CategoryInfo } from "$lib/modules/filesystem";
|
import { getFileInfo, type FileInfo, type CategoryInfo } from "$lib/modules/filesystem";
|
||||||
import { masterKeyStore } from "$lib/stores";
|
import { masterKeyStore } from "$lib/stores";
|
||||||
@@ -89,26 +89,19 @@
|
|||||||
<p class="font-medium">하위 카테고리의 파일</p>
|
<p class="font-medium">하위 카테고리의 파일</p>
|
||||||
</CheckBox>
|
</CheckBox>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="space-y-1">
|
||||||
{#key info}
|
{#key info}
|
||||||
<RowVirtualizer
|
{#each files as { info, isRecursive }}
|
||||||
count={files.length}
|
|
||||||
itemHeight={(index) => 56 + (index + 1 < files.length ? 4 : 0)}
|
|
||||||
>
|
|
||||||
{#snippet item(index)}
|
|
||||||
{@const { info, isRecursive } = files[index]!}
|
|
||||||
<div class={[index + 1 < files.length && "pb-1"]}>
|
|
||||||
<File
|
<File
|
||||||
{info}
|
{info}
|
||||||
onclick={onFileClick}
|
onclick={onFileClick}
|
||||||
onRemoveClick={!isRecursive ? onFileRemoveClick : undefined}
|
onRemoveClick={!isRecursive ? onFileRemoveClick : undefined}
|
||||||
/>
|
/>
|
||||||
</div>
|
{:else}
|
||||||
{/snippet}
|
<p class="text-gray-500 text-center">이 카테고리에 추가된 파일이 없어요.</p>
|
||||||
{#snippet placeholder()}
|
{/each}
|
||||||
<p class="text-center text-gray-500">이 카테고리에 추가된 파일이 없어요.</p>
|
|
||||||
{/snippet}
|
|
||||||
</RowVirtualizer>
|
|
||||||
{/key}
|
{/key}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { createWindowVirtualizer } from "@tanstack/svelte-virtual";
|
||||||
import { untrack } from "svelte";
|
import { untrack } from "svelte";
|
||||||
import { get, type Writable } from "svelte/store";
|
import { get, type Writable } from "svelte/store";
|
||||||
import { FileThumbnailButton, RowVirtualizer } from "$lib/components/atoms";
|
import { FileThumbnailButton } from "$lib/components/atoms";
|
||||||
import type { FileInfo } from "$lib/modules/filesystem";
|
import type { FileInfo } from "$lib/modules/filesystem";
|
||||||
import { formatDate, formatDateSortable, SortBy, sortEntries } from "$lib/utils";
|
import { formatDate, formatDateSortable, SortBy, sortEntries } from "$lib/utils";
|
||||||
|
|
||||||
@@ -16,11 +17,25 @@
|
|||||||
| { date?: undefined; contentType?: undefined; info: Writable<FileInfo | null> }
|
| { date?: undefined; contentType?: undefined; info: Writable<FileInfo | null> }
|
||||||
| { date: Date; contentType: string; info: Writable<FileInfo | null> };
|
| { date: Date; contentType: string; info: Writable<FileInfo | null> };
|
||||||
type Row =
|
type Row =
|
||||||
| { type: "header"; label: string }
|
| { type: "header"; key: string; label: string }
|
||||||
| { type: "items"; items: FileEntry[]; isLast: boolean };
|
| { type: "items"; key: string; items: FileEntry[] };
|
||||||
|
|
||||||
let filesWithDate: FileEntry[] = $state([]);
|
let filesWithDate: FileEntry[] = $state([]);
|
||||||
let rows: Row[] = $state([]);
|
let rows: Row[] = $state([]);
|
||||||
|
let listElement: HTMLDivElement | undefined = $state();
|
||||||
|
|
||||||
|
const virtualizer = createWindowVirtualizer({
|
||||||
|
count: 0,
|
||||||
|
getItemKey: (index) => rows[index]!.key,
|
||||||
|
estimateSize: () => 1000, // TODO
|
||||||
|
});
|
||||||
|
|
||||||
|
const measureRow = (node: HTMLElement) => {
|
||||||
|
$virtualizer.measureElement(node);
|
||||||
|
return {
|
||||||
|
update: () => $virtualizer.measureElement(node),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
filesWithDate = files.map((file) => {
|
filesWithDate = files.map((file) => {
|
||||||
@@ -61,19 +76,18 @@
|
|||||||
|
|
||||||
newRows.push({
|
newRows.push({
|
||||||
type: "header",
|
type: "header",
|
||||||
|
key: `header-${date}`,
|
||||||
label: formatDate(entries[0]!.date!),
|
label: formatDate(entries[0]!.date!),
|
||||||
});
|
});
|
||||||
|
|
||||||
for (let i = 0; i < entries.length; i += 4) {
|
|
||||||
newRows.push({
|
newRows.push({
|
||||||
type: "items",
|
type: "items",
|
||||||
items: entries.slice(i, i + 4),
|
key: `items-${date}`,
|
||||||
isLast: i + 4 >= entries.length,
|
items: entries,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
rows = newRows;
|
rows = newRows;
|
||||||
|
$virtualizer.setOptions({ count: rows.length });
|
||||||
};
|
};
|
||||||
return untrack(() => {
|
return untrack(() => {
|
||||||
buildRows();
|
buildRows();
|
||||||
@@ -96,29 +110,29 @@
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<RowVirtualizer
|
<div bind:this={listElement} class="relative flex flex-grow flex-col">
|
||||||
count={rows.length}
|
<div style="height: {$virtualizer.getTotalSize()}px;">
|
||||||
itemHeight={(index) =>
|
{#each $virtualizer.getVirtualItems() as virtualRow (virtualRow.key)}
|
||||||
rows[index]!.type === "header"
|
{@const row = rows[virtualRow.index]!}
|
||||||
? 32
|
<div
|
||||||
: Math.ceil(rows[index]!.items.length / 4) * 181 +
|
use:measureRow
|
||||||
(Math.ceil(rows[index]!.items.length / 4) - 1) * 4 +
|
data-index={virtualRow.index}
|
||||||
16}
|
class="absolute left-0 top-0 w-full"
|
||||||
class="flex flex-grow flex-col"
|
style="transform: translateY({virtualRow.start}px);"
|
||||||
>
|
>
|
||||||
{#snippet item(index)}
|
|
||||||
{@const row = rows[index]!}
|
|
||||||
{#if row.type === "header"}
|
{#if row.type === "header"}
|
||||||
<p class="pb-2 font-medium">{row.label}</p>
|
<p class="pb-2 font-medium">{row.label}</p>
|
||||||
{:else}
|
{:else}
|
||||||
<div class={["grid grid-cols-4 gap-x-1", row.isLast ? "pb-4" : "pb-1"]}>
|
<div class="grid grid-cols-4 gap-1 pb-4">
|
||||||
{#each row.items as { info }}
|
{#each row.items as { info }}
|
||||||
<FileThumbnailButton {info} onclick={onFileClick} />
|
<FileThumbnailButton {info} onclick={onFileClick} />
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{/snippet}
|
</div>
|
||||||
{#snippet placeholder()}
|
{/each}
|
||||||
|
</div>
|
||||||
|
{#if $virtualizer.getVirtualItems().length === 0}
|
||||||
<div class="flex h-full flex-grow items-center justify-center">
|
<div class="flex h-full flex-grow items-center justify-center">
|
||||||
<p class="text-gray-500">
|
<p class="text-gray-500">
|
||||||
{#if files.length === 0}
|
{#if files.length === 0}
|
||||||
@@ -130,5 +144,5 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{/snippet}
|
{/if}
|
||||||
</RowVirtualizer>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { untrack } from "svelte";
|
import { untrack } from "svelte";
|
||||||
import { get, type Writable } from "svelte/store";
|
import { get, type Writable } from "svelte/store";
|
||||||
import { ActionEntryButton, RowVirtualizer } from "$lib/components/atoms";
|
import { ActionEntryButton } from "$lib/components/atoms";
|
||||||
import { DirectoryEntryLabel } from "$lib/components/molecules";
|
import { DirectoryEntryLabel } from "$lib/components/molecules";
|
||||||
import {
|
import {
|
||||||
getDirectoryInfo,
|
getDirectoryInfo,
|
||||||
@@ -127,23 +127,13 @@
|
|||||||
{#each subDirectories as { info }}
|
{#each subDirectories as { info }}
|
||||||
<SubDirectory {info} onclick={onEntryClick} onOpenMenuClick={onEntryMenuClick} />
|
<SubDirectory {info} onclick={onEntryClick} onOpenMenuClick={onEntryMenuClick} />
|
||||||
{/each}
|
{/each}
|
||||||
{#if files.length > 0}
|
{#each files as file}
|
||||||
<RowVirtualizer
|
|
||||||
count={files.length}
|
|
||||||
itemHeight={(index) => 48 + (index + 1 < files.length ? 4 : 0)}
|
|
||||||
>
|
|
||||||
{#snippet item(index)}
|
|
||||||
{@const file = files[index]!}
|
|
||||||
<div class={index + 1 < files.length ? "pb-1" : ""}>
|
|
||||||
{#if file.type === "file"}
|
{#if file.type === "file"}
|
||||||
<File info={file.info} onclick={onEntryClick} onOpenMenuClick={onEntryMenuClick} />
|
<File info={file.info} onclick={onEntryClick} onOpenMenuClick={onEntryMenuClick} />
|
||||||
{:else}
|
{:else}
|
||||||
<UploadingFile status={file.info} />
|
<UploadingFile status={file.info} />
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
{/each}
|
||||||
{/snippet}
|
|
||||||
</RowVirtualizer>
|
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="flex flex-grow items-center justify-center">
|
<div class="flex flex-grow items-center justify-center">
|
||||||
|
|||||||
Reference in New Issue
Block a user