mirror of
https://github.com/kmc7468/arkvault.git
synced 2026-02-04 08:06:56 +00:00
@@ -34,7 +34,7 @@ export class FilesystemCache<K, V extends object> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
(state.value
|
(state.value
|
||||||
? Promise.resolve(state.value)
|
? Promise.resolve($state.snapshot(state.value) as V)
|
||||||
: this.options.fetchFromIndexedDB(key).then((loadedInfo) => {
|
: this.options.fetchFromIndexedDB(key).then((loadedInfo) => {
|
||||||
if (loadedInfo) {
|
if (loadedInfo) {
|
||||||
state.value = loadedInfo;
|
state.value = loadedInfo;
|
||||||
|
|||||||
@@ -74,28 +74,6 @@ export const getAllDirectoriesByParent = async (userId: number, parentId: Direct
|
|||||||
return directories.map(toDirectory);
|
return directories.map(toDirectory);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getAllRecursiveDirectoriesByParent = async (userId: number, parentId: DirectoryId) => {
|
|
||||||
const directories = await db
|
|
||||||
.withRecursive("directory_tree", (db) =>
|
|
||||||
db
|
|
||||||
.selectFrom("directory")
|
|
||||||
.selectAll()
|
|
||||||
.$if(parentId === "root", (qb) => qb.where("parent_id", "is", null))
|
|
||||||
.$if(parentId !== "root", (qb) => qb.where("parent_id", "=", parentId as number))
|
|
||||||
.where("user_id", "=", userId)
|
|
||||||
.unionAll((db) =>
|
|
||||||
db
|
|
||||||
.selectFrom("directory")
|
|
||||||
.innerJoin("directory_tree", "directory.parent_id", "directory_tree.id")
|
|
||||||
.selectAll("directory"),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.selectFrom("directory_tree")
|
|
||||||
.selectAll()
|
|
||||||
.execute();
|
|
||||||
return directories.map(toDirectory);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getAllFavoriteDirectories = async (userId: number) => {
|
export const getAllFavoriteDirectories = async (userId: number) => {
|
||||||
const directories = await db
|
const directories = await db
|
||||||
.selectFrom("directory")
|
.selectFrom("directory")
|
||||||
@@ -117,6 +95,61 @@ export const getDirectory = async (userId: number, directoryId: number) => {
|
|||||||
return directory ? toDirectory(directory) : null;
|
return directory ? toDirectory(directory) : null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const searchDirectories = async (
|
||||||
|
userId: number,
|
||||||
|
filters: {
|
||||||
|
parentId: DirectoryId;
|
||||||
|
inFavorites: boolean;
|
||||||
|
},
|
||||||
|
) => {
|
||||||
|
const directories = await db
|
||||||
|
.withRecursive("directory_tree", (db) =>
|
||||||
|
db
|
||||||
|
.selectFrom("directory")
|
||||||
|
.select("id")
|
||||||
|
.where("user_id", "=", userId)
|
||||||
|
.$if(filters.parentId === "root", (qb) => qb.where((eb) => eb.lit(false))) // directory_tree will be empty if parentId is "root"
|
||||||
|
.$if(filters.parentId !== "root", (qb) => qb.where("id", "=", filters.parentId as number))
|
||||||
|
.unionAll(
|
||||||
|
db
|
||||||
|
.selectFrom("directory as d")
|
||||||
|
.innerJoin("directory_tree as dt", "d.parent_id", "dt.id")
|
||||||
|
.select("d.id"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.withRecursive("favorite_directory_tree", (db) =>
|
||||||
|
db
|
||||||
|
.selectFrom("directory")
|
||||||
|
.select("id")
|
||||||
|
.where("user_id", "=", userId)
|
||||||
|
.$if(!filters.inFavorites, (qb) => qb.where((eb) => eb.lit(false))) // favorite_directory_tree will be empty if inFavorites is false
|
||||||
|
.$if(filters.inFavorites, (qb) => qb.where("is_favorite", "=", true))
|
||||||
|
.unionAll((db) =>
|
||||||
|
db
|
||||||
|
.selectFrom("directory as d")
|
||||||
|
.innerJoin("favorite_directory_tree as dt", "d.parent_id", "dt.id")
|
||||||
|
.select("d.id"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.selectFrom("directory")
|
||||||
|
.selectAll()
|
||||||
|
.where("user_id", "=", userId)
|
||||||
|
.$if(filters.parentId !== "root", (qb) =>
|
||||||
|
qb.where((eb) =>
|
||||||
|
eb.exists(eb.selectFrom("directory_tree as dt").whereRef("dt.id", "=", "parent_id")),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.$if(filters.inFavorites, (qb) =>
|
||||||
|
qb.where((eb) =>
|
||||||
|
eb.exists(
|
||||||
|
eb.selectFrom("favorite_directory_tree as dt").whereRef("dt.id", "=", "directory.id"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.execute();
|
||||||
|
return directories.map(toDirectory);
|
||||||
|
};
|
||||||
|
|
||||||
export const setDirectoryEncName = async (
|
export const setDirectoryEncName = async (
|
||||||
userId: number,
|
userId: number,
|
||||||
directoryId: number,
|
directoryId: number,
|
||||||
|
|||||||
@@ -248,6 +248,7 @@ export const searchFiles = async (
|
|||||||
userId: number,
|
userId: number,
|
||||||
filters: {
|
filters: {
|
||||||
parentId: DirectoryId;
|
parentId: DirectoryId;
|
||||||
|
inFavorites: boolean;
|
||||||
includeCategoryIds: number[];
|
includeCategoryIds: number[];
|
||||||
excludeCategoryIds: number[];
|
excludeCategoryIds: number[];
|
||||||
},
|
},
|
||||||
@@ -258,7 +259,7 @@ export const searchFiles = async (
|
|||||||
.selectFrom("directory")
|
.selectFrom("directory")
|
||||||
.select("id")
|
.select("id")
|
||||||
.where("user_id", "=", userId)
|
.where("user_id", "=", userId)
|
||||||
.where((eb) => eb.val(filters.parentId !== "root")) // directory_tree will be empty if parentId is "root"
|
.$if(filters.parentId === "root", (qb) => qb.where((eb) => eb.lit(false))) // directory_tree will be empty if parentId is "root"
|
||||||
.$if(filters.parentId !== "root", (qb) => qb.where("id", "=", filters.parentId as number))
|
.$if(filters.parentId !== "root", (qb) => qb.where("id", "=", filters.parentId as number))
|
||||||
.unionAll(
|
.unionAll(
|
||||||
db
|
db
|
||||||
@@ -267,6 +268,20 @@ export const searchFiles = async (
|
|||||||
.select("d.id"),
|
.select("d.id"),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
.withRecursive("favorite_directory_tree", (db) =>
|
||||||
|
db
|
||||||
|
.selectFrom("directory")
|
||||||
|
.select("id")
|
||||||
|
.where("user_id", "=", userId)
|
||||||
|
.$if(!filters.inFavorites, (qb) => qb.where((eb) => eb.lit(false))) // favorite_directory_tree will be empty if inFavorites is false
|
||||||
|
.$if(filters.inFavorites, (qb) => qb.where("is_favorite", "=", true))
|
||||||
|
.unionAll((db) =>
|
||||||
|
db
|
||||||
|
.selectFrom("directory as d")
|
||||||
|
.innerJoin("favorite_directory_tree as dt", "d.parent_id", "dt.id")
|
||||||
|
.select("d.id"),
|
||||||
|
),
|
||||||
|
)
|
||||||
.withRecursive("include_category_tree", (db) =>
|
.withRecursive("include_category_tree", (db) =>
|
||||||
db
|
db
|
||||||
.selectFrom("category")
|
.selectFrom("category")
|
||||||
@@ -295,19 +310,31 @@ export const searchFiles = async (
|
|||||||
)
|
)
|
||||||
.selectFrom("file")
|
.selectFrom("file")
|
||||||
.selectAll("file")
|
.selectAll("file")
|
||||||
.$if(filters.parentId === "root", (qb) => qb.where("user_id", "=", userId)) // directory_tree isn't used if parentId is "root"
|
.where("user_id", "=", userId)
|
||||||
.$if(filters.parentId !== "root", (qb) =>
|
.$if(filters.parentId !== "root", (qb) =>
|
||||||
qb.where("parent_id", "in", (eb) => eb.selectFrom("directory_tree").select("id")),
|
qb.where((eb) =>
|
||||||
|
eb.exists(eb.selectFrom("directory_tree as dt").whereRef("dt.id", "=", "file.parent_id")),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
.where((eb) =>
|
.$if(filters.inFavorites, (qb) =>
|
||||||
eb.not(
|
qb.where((eb) =>
|
||||||
eb.exists(
|
eb.or([
|
||||||
eb
|
eb("is_favorite", "=", true),
|
||||||
.selectFrom("file_category")
|
eb.exists(
|
||||||
.whereRef("file_id", "=", "file.id")
|
eb.selectFrom("favorite_directory_tree as dt").whereRef("dt.id", "=", "file.parent_id"),
|
||||||
.where("category_id", "in", (eb) =>
|
),
|
||||||
eb.selectFrom("exclude_category_tree").select("id"),
|
]),
|
||||||
),
|
),
|
||||||
|
)
|
||||||
|
.$if(filters.excludeCategoryIds.length > 0, (qb) =>
|
||||||
|
qb.where((eb) =>
|
||||||
|
eb.not(
|
||||||
|
eb.exists(
|
||||||
|
eb
|
||||||
|
.selectFrom("file_category")
|
||||||
|
.innerJoin("exclude_category_tree", "category_id", "exclude_category_tree.id")
|
||||||
|
.whereRef("file_id", "=", "file.id"),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -46,7 +46,7 @@
|
|||||||
|
|
||||||
{#if isOpen && (directoryId || downloadUrl || fileBlob)}
|
{#if isOpen && (directoryId || downloadUrl || fileBlob)}
|
||||||
<div
|
<div
|
||||||
class="absolute right-2 top-full z-20 min-w-40 space-y-1 rounded-lg bg-white px-1 py-2 shadow-2xl"
|
class="absolute right-2 top-full z-20 min-w-44 space-y-1 rounded-lg bg-white px-1 py-2 shadow-2xl"
|
||||||
transition:fly={{ y: -8, duration: 200 }}
|
transition:fly={{ y: -8, duration: 200 }}
|
||||||
>
|
>
|
||||||
<p class="px-3 pt-2 text-sm font-semibold text-gray-600">더보기</p>
|
<p class="px-3 pt-2 text-sm font-semibold text-gray-600">더보기</p>
|
||||||
@@ -57,7 +57,9 @@
|
|||||||
onclick: () => void,
|
onclick: () => void,
|
||||||
)}
|
)}
|
||||||
<button {onclick} class="rounded-xl active:bg-gray-100">
|
<button {onclick} class="rounded-xl active:bg-gray-100">
|
||||||
<div class="flex items-center gap-x-3 px-3 py-2 text-gray-700 transition active:scale-95">
|
<div
|
||||||
|
class="flex items-center gap-x-3 px-3 py-2 text-lg text-gray-700 transition active:scale-95"
|
||||||
|
>
|
||||||
<Icon />
|
<Icon />
|
||||||
<p class="font-medium">{text}</p>
|
<p class="font-medium">{text}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -37,8 +37,8 @@
|
|||||||
includeImages: false,
|
includeImages: false,
|
||||||
includeVideos: false,
|
includeVideos: false,
|
||||||
includeDirectories: false,
|
includeDirectories: false,
|
||||||
searchInFavorites: false,
|
|
||||||
searchInDirectory: false,
|
searchInDirectory: false,
|
||||||
|
searchInFavorites: false,
|
||||||
categories: [],
|
categories: [],
|
||||||
});
|
});
|
||||||
let hasCategoryFilter = $derived(filters.categories.length > 0);
|
let hasCategoryFilter = $derived(filters.categories.length > 0);
|
||||||
@@ -47,7 +47,6 @@
|
|||||||
filters.includeImages ||
|
filters.includeImages ||
|
||||||
filters.includeVideos ||
|
filters.includeVideos ||
|
||||||
filters.includeDirectories ||
|
filters.includeDirectories ||
|
||||||
filters.searchInFavorites ||
|
|
||||||
filters.name.trim().length > 0,
|
filters.name.trim().length > 0,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -84,9 +83,7 @@
|
|||||||
|
|
||||||
return sortEntries(
|
return sortEntries(
|
||||||
[...directories, ...files].filter(
|
[...directories, ...files].filter(
|
||||||
(entry) =>
|
(entry) => !nameFilter || searchString(entry.name, nameFilter),
|
||||||
(!nameFilter || searchString(entry.name, nameFilter)) &&
|
|
||||||
(!filters.searchInFavorites || entry.isFavorite),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -145,6 +142,7 @@
|
|||||||
// Svelte sucks
|
// Svelte sucks
|
||||||
hasAnyFilter;
|
hasAnyFilter;
|
||||||
filters.searchInDirectory;
|
filters.searchInDirectory;
|
||||||
|
filters.searchInFavorites;
|
||||||
filters.categories.length;
|
filters.categories.length;
|
||||||
|
|
||||||
if (untrack(() => isRestoredFromSnapshot)) {
|
if (untrack(() => isRestoredFromSnapshot)) {
|
||||||
@@ -156,6 +154,7 @@
|
|||||||
requestSearch(
|
requestSearch(
|
||||||
{
|
{
|
||||||
ancestorId: filters.searchInDirectory ? data.directoryId! : "root",
|
ancestorId: filters.searchInDirectory ? data.directoryId! : "root",
|
||||||
|
inFavorites: filters.searchInFavorites,
|
||||||
categories: filters.categories,
|
categories: filters.categories,
|
||||||
},
|
},
|
||||||
$masterKeyStore?.get(1)?.key!,
|
$masterKeyStore?.get(1)?.key!,
|
||||||
@@ -216,7 +215,12 @@
|
|||||||
</div>
|
</div>
|
||||||
{#if hasAnyFilter}
|
{#if hasAnyFilter}
|
||||||
<div class="flex flex-grow flex-col space-y-2 bg-white p-4">
|
<div class="flex flex-grow flex-col space-y-2 bg-white p-4">
|
||||||
<p class="text-lg font-bold text-gray-800">검색 결과</p>
|
<p class="text-lg font-bold text-gray-800">
|
||||||
|
검색 결과
|
||||||
|
{#if result.length > 0}
|
||||||
|
{" "}({result.length}개)
|
||||||
|
{/if}
|
||||||
|
</p>
|
||||||
{#if result.length > 0}
|
{#if result.length > 0}
|
||||||
<RowVirtualizer
|
<RowVirtualizer
|
||||||
count={result.length}
|
count={result.length}
|
||||||
|
|||||||
@@ -1,17 +1,14 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { ClassValue } from "svelte/elements";
|
|
||||||
|
|
||||||
import IconArrowBack from "~icons/material-symbols/arrow-back";
|
import IconArrowBack from "~icons/material-symbols/arrow-back";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
class?: ClassValue;
|
|
||||||
value: string;
|
value: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
let { class: className, value = $bindable() }: Props = $props();
|
let { value = $bindable() }: Props = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class={["sticky top-0 z-10 flex items-center gap-x-2 px-2 py-3 backdrop-blur-2xl", className]}>
|
<div class={"sticky top-0 z-10 flex items-center gap-x-2 px-2 py-3 backdrop-blur-2xl"}>
|
||||||
<button
|
<button
|
||||||
onclick={() => history.back()}
|
onclick={() => history.back()}
|
||||||
class="w-[2.3rem] flex-shrink-0 rounded-full p-1 active:bg-black active:bg-opacity-[0.04]"
|
class="w-[2.3rem] flex-shrink-0 rounded-full p-1 active:bg-black active:bg-opacity-[0.04]"
|
||||||
@@ -22,6 +19,6 @@
|
|||||||
bind:value
|
bind:value
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="검색"
|
placeholder="검색"
|
||||||
class="mr-1 h-[2.3rem] flex-grow rounded-lg bg-gray-100 px-3 py-1.5 placeholder-gray-600 focus:outline-none"
|
class="mr-1 h-[2.3rem] min-w-0 flex-grow rounded-lg bg-gray-100 px-3 py-1.5 placeholder-gray-600 focus:outline-none"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { trpc } from "$trpc/client";
|
|||||||
|
|
||||||
export interface SearchFilter {
|
export interface SearchFilter {
|
||||||
ancestorId: DirectoryId;
|
ancestorId: DirectoryId;
|
||||||
|
inFavorites: boolean;
|
||||||
categories: { info: LocalCategoryInfo; type: "include" | "exclude" }[];
|
categories: { info: LocalCategoryInfo; type: "include" | "exclude" }[];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -21,6 +22,7 @@ export interface SearchResult {
|
|||||||
export const requestSearch = async (filter: SearchFilter, masterKey: CryptoKey) => {
|
export const requestSearch = async (filter: SearchFilter, masterKey: CryptoKey) => {
|
||||||
const { directories: directoriesRaw, files: filesRaw } = await trpc().search.search.query({
|
const { directories: directoriesRaw, files: filesRaw } = await trpc().search.search.query({
|
||||||
ancestor: filter.ancestorId,
|
ancestor: filter.ancestorId,
|
||||||
|
inFavorites: filter.inFavorites,
|
||||||
includeCategories: filter.categories
|
includeCategories: filter.categories
|
||||||
.filter(({ type }) => type === "include")
|
.filter(({ type }) => type === "include")
|
||||||
.map(({ info }) => info.id),
|
.map(({ info }) => info.id),
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
import { DirectoryEntryLabel } from "$lib/components/molecules";
|
import { DirectoryEntryLabel } from "$lib/components/molecules";
|
||||||
import { getFileThumbnail } from "$lib/modules/file";
|
import { getFileThumbnail } from "$lib/modules/file";
|
||||||
import type { CategoryFileInfo } from "$lib/modules/filesystem";
|
import type { CategoryFileInfo } from "$lib/modules/filesystem";
|
||||||
|
import { formatDateTime } from "$lib/utils";
|
||||||
import type { SelectedFile } from "./service.svelte";
|
import type { SelectedFile } from "./service.svelte";
|
||||||
|
|
||||||
import IconClose from "~icons/material-symbols/close";
|
import IconClose from "~icons/material-symbols/close";
|
||||||
@@ -19,7 +20,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ActionEntryButton
|
<ActionEntryButton
|
||||||
class="h-12"
|
class="h-14"
|
||||||
onclick={() => onclick(info)}
|
onclick={() => onclick(info)}
|
||||||
actionButtonIcon={onRemoveClick && IconClose}
|
actionButtonIcon={onRemoveClick && IconClose}
|
||||||
onActionButtonClick={() => onRemoveClick?.(info)}
|
onActionButtonClick={() => onRemoveClick?.(info)}
|
||||||
@@ -28,6 +29,7 @@
|
|||||||
type="file"
|
type="file"
|
||||||
thumbnail={$thumbnail}
|
thumbnail={$thumbnail}
|
||||||
name={info.name}
|
name={info.name}
|
||||||
|
subtext={formatDateTime(info.createdAt ?? info.lastModifiedAt)}
|
||||||
isFavorite={info.isFavorite}
|
isFavorite={info.isFavorite}
|
||||||
/>
|
/>
|
||||||
</ActionEntryButton>
|
</ActionEntryButton>
|
||||||
|
|||||||
@@ -103,7 +103,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
<title>파일</title>
|
<title>내 파일</title>
|
||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
<input bind:this={fileInput} onchange={uploadFile} type="file" multiple class="hidden" />
|
<input bind:this={fileInput} onchange={uploadFile} type="file" multiple class="hidden" />
|
||||||
|
|||||||
@@ -44,52 +44,50 @@
|
|||||||
<title>즐겨찾기</title>
|
<title>즐겨찾기</title>
|
||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
<TopBar title="즐겨찾기" showBackButton={false}>
|
<div class="flex h-full flex-col">
|
||||||
<button
|
<TopBar title="즐겨찾기" showBackButton={false}>
|
||||||
onclick={() => goto("/search?from=favorites")}
|
<button
|
||||||
class="w-full rounded-full p-1 text-2xl active:bg-black active:bg-opacity-[0.04]"
|
onclick={() => goto("/search?from=favorites")}
|
||||||
>
|
class="w-full rounded-full p-1 text-2xl active:bg-black active:bg-opacity-[0.04]"
|
||||||
<IconSearch />
|
|
||||||
</button>
|
|
||||||
</TopBar>
|
|
||||||
<div class="flex h-full flex-col p-4 !pt-0">
|
|
||||||
{#if isLoading}
|
|
||||||
<div class="flex flex-grow items-center justify-center">
|
|
||||||
<p class="text-gray-500">
|
|
||||||
{#if data.favorites.files.length === 0 && data.favorites.directories.length === 0}
|
|
||||||
즐겨찾기한 항목이 없어요.
|
|
||||||
{:else}
|
|
||||||
즐겨찾기 목록을 불러오고 있어요.
|
|
||||||
{/if}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
{:else if entries.length === 0}
|
|
||||||
<div class="flex flex-grow items-center justify-center">
|
|
||||||
<p class="text-gray-500">즐겨찾기한 항목이 없어요.</p>
|
|
||||||
</div>
|
|
||||||
{:else}
|
|
||||||
<RowVirtualizer
|
|
||||||
count={entries.length}
|
|
||||||
getItemKey={(index) => `${entries[index]!.type}-${entries[index]!.details.id}`}
|
|
||||||
estimateItemHeight={() => 56}
|
|
||||||
itemGap={4}
|
|
||||||
>
|
>
|
||||||
{#snippet item(index)}
|
<IconSearch />
|
||||||
{@const entry = entries[index]!}
|
</button>
|
||||||
{#if entry.type === "directory"}
|
</TopBar>
|
||||||
<Directory
|
<div class="flex flex-grow flex-col px-4 pb-4">
|
||||||
info={entry.details}
|
{#if entries.length > 0}
|
||||||
onclick={() => handleClick(entry)}
|
<RowVirtualizer
|
||||||
onRemoveClick={() => handleRemove(entry)}
|
count={entries.length}
|
||||||
/>
|
getItemKey={(index) => `${entries[index]!.type}-${entries[index]!.details.id}`}
|
||||||
{:else}
|
estimateItemHeight={() => 56}
|
||||||
<File
|
itemGap={4}
|
||||||
info={entry.details}
|
>
|
||||||
onclick={() => handleClick(entry)}
|
{#snippet item(index)}
|
||||||
onRemoveClick={() => handleRemove(entry)}
|
{@const entry = entries[index]!}
|
||||||
/>
|
{#if entry.type === "directory"}
|
||||||
{/if}
|
<Directory
|
||||||
{/snippet}
|
info={entry.details}
|
||||||
</RowVirtualizer>
|
onclick={() => handleClick(entry)}
|
||||||
{/if}
|
onRemoveClick={() => handleRemove(entry)}
|
||||||
|
/>
|
||||||
|
{:else}
|
||||||
|
<File
|
||||||
|
info={entry.details}
|
||||||
|
onclick={() => handleClick(entry)}
|
||||||
|
onRemoveClick={() => handleRemove(entry)}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
{/snippet}
|
||||||
|
</RowVirtualizer>
|
||||||
|
{:else}
|
||||||
|
<div class="flex flex-grow items-center justify-center">
|
||||||
|
<p class="text-gray-500">
|
||||||
|
{#if isLoading}
|
||||||
|
즐겨찾기 목록을 불러오고 있어요.
|
||||||
|
{:else}
|
||||||
|
즐겨찾기한 항목이 없어요.
|
||||||
|
{/if}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
import { DirectoryEntryLabel } from "$lib/components/molecules";
|
import { DirectoryEntryLabel } from "$lib/components/molecules";
|
||||||
import { getFileThumbnail } from "$lib/modules/file";
|
import { getFileThumbnail } from "$lib/modules/file";
|
||||||
import type { SummarizedFileInfo } from "$lib/modules/filesystem";
|
import type { SummarizedFileInfo } from "$lib/modules/filesystem";
|
||||||
|
import { formatDateTime } from "$lib/utils";
|
||||||
|
|
||||||
import IconClose from "~icons/material-symbols/close";
|
import IconClose from "~icons/material-symbols/close";
|
||||||
|
|
||||||
@@ -23,5 +24,11 @@
|
|||||||
actionButtonIcon={IconClose}
|
actionButtonIcon={IconClose}
|
||||||
onActionButtonClick={onRemoveClick}
|
onActionButtonClick={onRemoveClick}
|
||||||
>
|
>
|
||||||
<DirectoryEntryLabel type="file" thumbnail={$thumbnail} name={info.name} isFavorite />
|
<DirectoryEntryLabel
|
||||||
|
type="file"
|
||||||
|
thumbnail={$thumbnail}
|
||||||
|
name={info.name}
|
||||||
|
subtext={formatDateTime(info.createdAt ?? info.lastModifiedAt)}
|
||||||
|
isFavorite
|
||||||
|
/>
|
||||||
</ActionEntryButton>
|
</ActionEntryButton>
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ const searchRouter = router({
|
|||||||
.input(
|
.input(
|
||||||
z.object({
|
z.object({
|
||||||
ancestor: DirectoryIdSchema.default("root"),
|
ancestor: DirectoryIdSchema.default("root"),
|
||||||
|
inFavorites: z.boolean().default(false),
|
||||||
includeCategories: z.number().positive().array().default([]),
|
includeCategories: z.number().positive().array().default([]),
|
||||||
excludeCategories: z.number().positive().array().default([]),
|
excludeCategories: z.number().positive().array().default([]),
|
||||||
}),
|
}),
|
||||||
@@ -15,10 +16,14 @@ const searchRouter = router({
|
|||||||
.query(async ({ ctx, input }) => {
|
.query(async ({ ctx, input }) => {
|
||||||
const [directories, files] = await Promise.all([
|
const [directories, files] = await Promise.all([
|
||||||
input.includeCategories.length === 0 && input.excludeCategories.length === 0
|
input.includeCategories.length === 0 && input.excludeCategories.length === 0
|
||||||
? DirectoryRepo.getAllRecursiveDirectoriesByParent(ctx.session.userId, input.ancestor)
|
? DirectoryRepo.searchDirectories(ctx.session.userId, {
|
||||||
|
parentId: input.ancestor,
|
||||||
|
inFavorites: input.inFavorites,
|
||||||
|
})
|
||||||
: [],
|
: [],
|
||||||
FileRepo.searchFiles(ctx.session.userId, {
|
FileRepo.searchFiles(ctx.session.userId, {
|
||||||
parentId: input.ancestor,
|
parentId: input.ancestor,
|
||||||
|
inFavorites: input.inFavorites,
|
||||||
includeCategoryIds: input.includeCategories,
|
includeCategoryIds: input.includeCategories,
|
||||||
excludeCategoryIds: input.excludeCategories,
|
excludeCategoryIds: input.excludeCategories,
|
||||||
}),
|
}),
|
||||||
|
|||||||
Reference in New Issue
Block a user