mirror of
https://github.com/kmc7468/arkvault.git
synced 2026-11-04 00:00:28 +09:00
컴포넌트를 도메인과 무관한 UI 컴포넌트와 도메인 컴포넌트로 재구성하고 중복된 파일 목록 컴포넌트 통합 및 모듈 간 의존성 방향을 ESLint로 강제
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
4e82c5e556
commit
877cf9c42e
94 files changed
+398
-518
No files matched your search
@@ -13,6 +13,20 @@ const svelteConfig = (await loadConfig("./", { traverse: false }))?.config;
|
||||
|
||||
const gitignorePath = fileURLToPath(new URL("./.gitignore", import.meta.url));
|
||||
|
||||
// Restricts the direction of dependencies between modules
|
||||
const defineImportBoundaries = (boundaries) =>
|
||||
Object.entries(boundaries).map(([files, regex]) => ({
|
||||
files: [files],
|
||||
rules: {
|
||||
"no-restricted-imports": [
|
||||
"error",
|
||||
{
|
||||
patterns: [{ regex, message: "Violates the dependency direction between modules." }],
|
||||
},
|
||||
],
|
||||
},
|
||||
}));
|
||||
|
||||
export default defineConfig(
|
||||
includeIgnoreFile(gitignorePath),
|
||||
js.configs.recommended,
|
||||
@@ -37,6 +51,17 @@ export default defineConfig(
|
||||
"no-undef": "off",
|
||||
},
|
||||
},
|
||||
...defineImportBoundaries({
|
||||
// Pure code shared by the server, the client, and the service worker
|
||||
"src/lib/shared/**": "^(#lib/(?!shared/)|#trpc/|#workers/|\\$app/|svelte($|/))",
|
||||
"src/lib/server/**": "^(#lib/(client|features|ui)/|#trpc/)",
|
||||
// Infrastructure of the browser
|
||||
"src/lib/client/**": "^#lib/(server|features|ui)/",
|
||||
// Domain-agnostic components
|
||||
"src/lib/ui/**": "^(#lib/(server|client|features)/|#trpc/)",
|
||||
"src/lib/features/**": "^#lib/server/",
|
||||
"src/service-worker/**": "^(#lib/(?!shared/|client/storage/opfs\\.js$)|#trpc/|#workers/)",
|
||||
}),
|
||||
{
|
||||
files: ["**/*.svelte", "**/*.svelte.ts", "**/*.svelte.js"],
|
||||
languageOptions: {
|
||||
|
||||
+5
-2
@@ -1,7 +1,6 @@
|
||||
/* eslint-disable svelte/prefer-svelte-reactivity -- Lookup tables of reactive entries */
|
||||
import { LRUCache } from "lru-cache";
|
||||
import { deleteDirectory, deleteFile, readFile, writeFile } from "#lib/client/storage/opfs.js";
|
||||
import type { SummarizedFileInfo } from "#lib/features/filesystem/index.js";
|
||||
import { decryptChunk, encodeToBase64 } from "#lib/shared/crypto/index.js";
|
||||
|
||||
export interface Thumbnail {
|
||||
@@ -33,7 +32,11 @@ const fetchFromServer = async (fileId: number, dataKey: CryptoKey) => {
|
||||
return getThumbnailUrl(thumbnailBuffer);
|
||||
};
|
||||
|
||||
export const getFileThumbnail = (file: SummarizedFileInfo) => {
|
||||
export const getFileThumbnail = (file: {
|
||||
id: number;
|
||||
contentType: string;
|
||||
dataKey?: { key: CryptoKey };
|
||||
}) => {
|
||||
if (!(file.contentType.startsWith("image/") || file.contentType.startsWith("video/"))) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
export { default as ActionEntryButton } from "./ActionEntryButton.svelte";
|
||||
export { default as Button } from "./Button.svelte";
|
||||
export { default as EntryButton } from "./EntryButton.svelte";
|
||||
export { default as FileThumbnailButton } from "./FileThumbnailButton.svelte";
|
||||
export { default as FloatingButton } from "./FloatingButton.svelte";
|
||||
export { default as TextButton } from "./TextButton.svelte";
|
||||
@@ -1,3 +0,0 @@
|
||||
export { default as AdaptiveDiv } from "./AdaptiveDiv.svelte";
|
||||
export { default as BottomDiv } from "./BottomDiv.svelte";
|
||||
export { default as FullscreenDiv } from "./FullscreenDiv.svelte";
|
||||
@@ -1,8 +0,0 @@
|
||||
export { default as BottomSheet } from "./BottomSheet.svelte";
|
||||
export * from "./buttons";
|
||||
export { default as Chip } from "./Chip.svelte";
|
||||
export * from "./divs";
|
||||
export * from "./inputs";
|
||||
export { default as Modal } from "./Modal.svelte";
|
||||
export { default as RowVirtualizer } from "./RowVirtualizer.svelte";
|
||||
export { default as Toasts } from "./Toasts.svelte";
|
||||
@@ -1,2 +0,0 @@
|
||||
export { default as CheckBox } from "./CheckBox.svelte";
|
||||
export { default as TextInput } from "./TextInput.svelte";
|
||||
@@ -1,9 +0,0 @@
|
||||
export * from "./ActionModal.svelte";
|
||||
export { default as ActionModal } from "./ActionModal.svelte";
|
||||
export * from "./Categories.svelte";
|
||||
export { default as Categories } from "./Categories.svelte";
|
||||
export { default as IconEntryButton } from "./IconEntryButton.svelte";
|
||||
export * from "./labels";
|
||||
export { default as SubCategories } from "./SubCategories.svelte";
|
||||
export { default as TitledDiv } from "./TitledDiv.svelte";
|
||||
export { default as TopBar } from "./TopBar.svelte";
|
||||
@@ -1,4 +0,0 @@
|
||||
export { default as CategoryLabel } from "./CategoryLabel.svelte";
|
||||
export { default as DirectoryEntryLabel } from "./DirectoryEntryLabel.svelte";
|
||||
export { default as IconLabel } from "./IconLabel.svelte";
|
||||
export { default as TitleLabel } from "./TitleLabel.svelte";
|
||||
@@ -1 +0,0 @@
|
||||
export * from "./modals";
|
||||
@@ -1,4 +0,0 @@
|
||||
export { default as CategoryCreateModal } from "./CategoryCreateModal.svelte";
|
||||
export { default as ForceLoginModal } from "./ForceLoginModal.svelte";
|
||||
export { default as RenameModal } from "./RenameModal.svelte";
|
||||
export { default as TextInputModal } from "./TextInputModal.svelte";
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { ActionModal } from "#lib/components/molecules/index.js";
|
||||
import { ActionModal } from "#lib/ui/index.js";
|
||||
|
||||
interface Props {
|
||||
isOpen: boolean;
|
||||
@@ -0,0 +1 @@
|
||||
export { default as ForceLoginModal } from "./ForceLoginModal.svelte";
|
||||
@@ -1,25 +1,6 @@
|
||||
import { showToast } from "#lib/ui/index.js";
|
||||
import { isTRPCClientError } from "#trpc/client.js";
|
||||
|
||||
export interface Toast {
|
||||
id: number;
|
||||
message: string;
|
||||
}
|
||||
|
||||
const TOAST_DURATION = 3000;
|
||||
|
||||
let toasts: Toast[] = $state([]);
|
||||
let nextId = 0;
|
||||
|
||||
export const getToasts = () => toasts;
|
||||
|
||||
export const showToast = (message: string) => {
|
||||
const toast = { id: nextId++, message };
|
||||
toasts.push(toast);
|
||||
setTimeout(() => {
|
||||
toasts = toasts.filter(({ id }) => id !== toast.id);
|
||||
}, TOAST_DURATION);
|
||||
};
|
||||
|
||||
const getErrorMessage = (e: unknown, fallbackMessage: string) => {
|
||||
if (isTRPCClientError(e)) {
|
||||
switch (e.data?.code) {
|
||||
+2
-4
@@ -1,5 +1,5 @@
|
||||
<script module lang="ts">
|
||||
import type { DataKey } from "#lib/features/filesystem/index.js";
|
||||
import type { DataKey, SubCategoryInfo } from "#lib/features/filesystem/index.js";
|
||||
|
||||
export interface SelectedCategory {
|
||||
id: number;
|
||||
@@ -11,9 +11,7 @@
|
||||
<script lang="ts">
|
||||
import type { Component } from "svelte";
|
||||
import type { SvelteHTMLElements } from "svelte/elements";
|
||||
import { ActionEntryButton } from "#lib/components/atoms/index.js";
|
||||
import { CategoryLabel } from "#lib/components/molecules/index.js";
|
||||
import type { SubCategoryInfo } from "#lib/features/filesystem/index.js";
|
||||
import { ActionEntryButton, CategoryLabel } from "#lib/ui/index.js";
|
||||
import { sortEntries } from "#lib/utils/index.js";
|
||||
|
||||
interface Props {
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { TextInputModal } from "#lib/components/organisms/index.js";
|
||||
import { TextInputModal } from "#lib/ui/index.js";
|
||||
|
||||
interface Props {
|
||||
isOpen: boolean;
|
||||
@@ -0,0 +1,25 @@
|
||||
<script lang="ts">
|
||||
import type { Component } from "svelte";
|
||||
import type { SvelteHTMLElements } from "svelte/elements";
|
||||
import { ActionEntryButton, DirectoryEntryLabel } from "#lib/ui/index.js";
|
||||
|
||||
interface Props {
|
||||
action?: {
|
||||
icon: Component<SvelteHTMLElements["svg"]>;
|
||||
onclick: () => void;
|
||||
};
|
||||
info: { name: string; isFavorite?: boolean };
|
||||
onclick?: () => void;
|
||||
}
|
||||
|
||||
let { action, info, onclick }: Props = $props();
|
||||
</script>
|
||||
|
||||
<ActionEntryButton
|
||||
class="h-14"
|
||||
{onclick}
|
||||
actionButtonIcon={action?.icon}
|
||||
onActionButtonClick={action?.onclick}
|
||||
>
|
||||
<DirectoryEntryLabel type="directory" name={info.name} isFavorite={info.isFavorite} />
|
||||
</ActionEntryButton>
|
||||
@@ -0,0 +1,40 @@
|
||||
<script lang="ts">
|
||||
import type { Component } from "svelte";
|
||||
import type { ClassValue, SvelteHTMLElements } from "svelte/elements";
|
||||
import { getFileThumbnail } from "#lib/client/cache/index.js";
|
||||
import { ActionEntryButton, DirectoryEntryLabel } from "#lib/ui/index.js";
|
||||
import { formatDateTime } from "#lib/utils/index.js";
|
||||
import type { SummarizedFileInfo } from "../types";
|
||||
|
||||
interface Props {
|
||||
action?: {
|
||||
icon: Component<SvelteHTMLElements["svg"]>;
|
||||
onclick: () => void;
|
||||
class?: ClassValue;
|
||||
};
|
||||
info: SummarizedFileInfo;
|
||||
onclick?: () => void;
|
||||
showThumbnail?: boolean;
|
||||
subtext?: string;
|
||||
}
|
||||
|
||||
let { action, info, onclick, showThumbnail = true, subtext }: Props = $props();
|
||||
|
||||
let thumbnail = $derived(showThumbnail ? getFileThumbnail(info) : undefined);
|
||||
</script>
|
||||
|
||||
<ActionEntryButton
|
||||
class="h-14"
|
||||
{onclick}
|
||||
actionButtonIcon={action?.icon}
|
||||
actionButtonClass={action?.class}
|
||||
onActionButtonClick={action?.onclick}
|
||||
>
|
||||
<DirectoryEntryLabel
|
||||
type="file"
|
||||
thumbnail={thumbnail?.url}
|
||||
name={info.name}
|
||||
subtext={subtext ?? formatDateTime(info.createdAt ?? info.lastModifiedAt)}
|
||||
isFavorite={info.isFavorite}
|
||||
/>
|
||||
</ActionEntryButton>
|
||||
File renamed without changes.
+2
-5
@@ -1,12 +1,9 @@
|
||||
<script lang="ts">
|
||||
import type { Component } from "svelte";
|
||||
import type { ClassValue, SvelteHTMLElements } from "svelte/elements";
|
||||
import {
|
||||
Categories,
|
||||
IconEntryButton,
|
||||
type SelectedCategory,
|
||||
} from "#lib/components/molecules/index.js";
|
||||
import { Categories, type SelectedCategory } from "#lib/features/filesystem/components/index.js";
|
||||
import type { CategoryInfo } from "#lib/features/filesystem/index.js";
|
||||
import { IconEntryButton } from "#lib/ui/index.js";
|
||||
|
||||
import IconAddCircle from "~icons/material-symbols/add-circle";
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
export * from "./Categories.svelte";
|
||||
export { default as Categories } from "./Categories.svelte";
|
||||
export { default as CategoryCreateModal } from "./CategoryCreateModal.svelte";
|
||||
export { default as DirectoryEntry } from "./DirectoryEntry.svelte";
|
||||
export { default as FileEntry } from "./FileEntry.svelte";
|
||||
export { default as FileThumbnailButton } from "./FileThumbnailButton.svelte";
|
||||
export { default as SubCategories } from "./SubCategories.svelte";
|
||||
File renamed without changes.
+1
-1
@@ -50,7 +50,7 @@
|
||||
</div>
|
||||
|
||||
<style lang="postcss">
|
||||
@reference "../../../../app.css";
|
||||
@reference "../../../app.css";
|
||||
|
||||
#container:active:not(:has(#action-button:active)) {
|
||||
@apply bg-gray-100;
|
||||
File renamed without changes.
File renamed without changes.
+1
-1
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import type { Component } from "svelte";
|
||||
import type { ClassValue, SvelteHTMLElements } from "svelte/elements";
|
||||
import { AdaptiveDiv } from "#lib/components/atoms/index.js";
|
||||
import { AdaptiveDiv } from "#lib/ui/index.js";
|
||||
|
||||
interface Props {
|
||||
class: ClassValue;
|
||||
+1
-2
@@ -1,8 +1,7 @@
|
||||
<script lang="ts">
|
||||
import type { Component, Snippet } from "svelte";
|
||||
import type { ClassValue, SvelteHTMLElements } from "svelte/elements";
|
||||
import { EntryButton } from "#lib/components/atoms/index.js";
|
||||
import { IconLabel } from "#lib/components/molecules/index.js";
|
||||
import { EntryButton, IconLabel } from "#lib/ui/index.js";
|
||||
|
||||
interface Props {
|
||||
children: Snippet;
|
||||
File renamed without changes.
@@ -0,0 +1,28 @@
|
||||
export { default as ActionEntryButton } from "./buttons/ActionEntryButton.svelte";
|
||||
export { default as Button } from "./buttons/Button.svelte";
|
||||
export { default as EntryButton } from "./buttons/EntryButton.svelte";
|
||||
export { default as FloatingButton } from "./buttons/FloatingButton.svelte";
|
||||
export { default as IconEntryButton } from "./buttons/IconEntryButton.svelte";
|
||||
export { default as TextButton } from "./buttons/TextButton.svelte";
|
||||
export { default as CheckBox } from "./inputs/CheckBox.svelte";
|
||||
export { default as Chip } from "./inputs/Chip.svelte";
|
||||
export { default as TextInput } from "./inputs/TextInput.svelte";
|
||||
export { default as CategoryLabel } from "./labels/CategoryLabel.svelte";
|
||||
export { default as DirectoryEntryLabel } from "./labels/DirectoryEntryLabel.svelte";
|
||||
export { default as IconLabel } from "./labels/IconLabel.svelte";
|
||||
export { default as TitleLabel } from "./labels/TitleLabel.svelte";
|
||||
export { default as AdaptiveDiv } from "./layout/AdaptiveDiv.svelte";
|
||||
export { default as BottomDiv } from "./layout/BottomDiv.svelte";
|
||||
export { default as FullscreenDiv } from "./layout/FullscreenDiv.svelte";
|
||||
export { default as TitledDiv } from "./layout/TitledDiv.svelte";
|
||||
export { default as TopBar } from "./layout/TopBar.svelte";
|
||||
export * from "./overlays/ActionModal.svelte";
|
||||
export { default as ActionModal } from "./overlays/ActionModal.svelte";
|
||||
export { default as BottomSheet } from "./overlays/BottomSheet.svelte";
|
||||
export { default as Modal } from "./overlays/Modal.svelte";
|
||||
export * from "./overlays/overlayGroup.svelte";
|
||||
export { default as RenameModal } from "./overlays/RenameModal.svelte";
|
||||
export { default as TextInputModal } from "./overlays/TextInputModal.svelte";
|
||||
export * from "./overlays/toast.svelte";
|
||||
export { default as Toasts } from "./overlays/Toasts.svelte";
|
||||
export { default as RowVirtualizer } from "./RowVirtualizer.svelte";
|
||||
File renamed without changes.
File renamed without changes.
+1
-1
@@ -29,7 +29,7 @@
|
||||
</div>
|
||||
|
||||
<style lang="postcss">
|
||||
@reference "../../../../app.css";
|
||||
@reference "../../../app.css";
|
||||
|
||||
input:focus,
|
||||
input:not(:placeholder-shown) {
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import type { ClassValue } from "svelte/elements";
|
||||
import { IconLabel } from "#lib/components/molecules/index.js";
|
||||
import { IconLabel } from "#lib/ui/index.js";
|
||||
|
||||
import IconCategory from "~icons/material-symbols/category";
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import type { ClassValue } from "svelte/elements";
|
||||
import { IconLabel } from "#lib/components/molecules/index.js";
|
||||
import { IconLabel } from "#lib/ui/index.js";
|
||||
|
||||
import IconFolder from "~icons/material-symbols/folder";
|
||||
import IconDriveFolderUpload from "~icons/material-symbols/drive-folder-upload";
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import type { Component, Snippet } from "svelte";
|
||||
import type { ClassValue, SvelteHTMLElements } from "svelte/elements";
|
||||
import { TitleLabel } from "#lib/components/molecules/index.js";
|
||||
import { TitleLabel } from "#lib/ui/index.js";
|
||||
|
||||
interface Props {
|
||||
children?: Snippet;
|
||||
File renamed without changes.
+1
-1
@@ -4,7 +4,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
import type { Snippet } from "svelte";
|
||||
import { Button, Modal } from "#lib/components/atoms/index.js";
|
||||
import { Button, Modal } from "#lib/ui/index.js";
|
||||
|
||||
interface Props {
|
||||
cancelText?: string;
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
import type { Snippet } from "svelte";
|
||||
import type { ClassValue } from "svelte/elements";
|
||||
import { fade, fly } from "svelte/transition";
|
||||
import { AdaptiveDiv } from "#lib/components/atoms/index.js";
|
||||
import { AdaptiveDiv } from "#lib/ui/index.js";
|
||||
|
||||
interface Props {
|
||||
children: Snippet;
|
||||
@@ -2,7 +2,7 @@
|
||||
import type { Snippet } from "svelte";
|
||||
import type { ClassValue } from "svelte/elements";
|
||||
import { fade } from "svelte/transition";
|
||||
import { AdaptiveDiv } from "#lib/components/atoms/index.js";
|
||||
import { AdaptiveDiv } from "#lib/ui/index.js";
|
||||
|
||||
interface Props {
|
||||
children: Snippet;
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { TextInputModal } from "#lib/components/organisms/index.js";
|
||||
import { TextInputModal } from "#lib/ui/index.js";
|
||||
|
||||
interface Props {
|
||||
isOpen: boolean;
|
||||
+1
-2
@@ -1,6 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { TextInput } from "#lib/components/atoms/index.js";
|
||||
import { ActionModal, type ConfirmHandler } from "#lib/components/molecules/index.js";
|
||||
import { TextInput, ActionModal, type ConfirmHandler } from "#lib/ui/index.js";
|
||||
|
||||
interface Props {
|
||||
defaultValue?: string;
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { fly } from "svelte/transition";
|
||||
import { getToasts } from "#lib/client/toast.svelte.js";
|
||||
import { getToasts } from "./toast.svelte";
|
||||
</script>
|
||||
|
||||
<div class="pointer-events-none fixed inset-x-0 top-4 z-50 flex flex-col items-center gap-y-2 px-4">
|
||||
@@ -0,0 +1,27 @@
|
||||
// Manages overlays (modals and bottom sheets) of which at most one is open at a time
|
||||
export class OverlayGroup<T extends string> {
|
||||
current: T | undefined = $state();
|
||||
|
||||
open(name: T) {
|
||||
this.current = name;
|
||||
}
|
||||
|
||||
close() {
|
||||
this.current = undefined;
|
||||
}
|
||||
|
||||
// Getter and setter for `bind:isOpen={getter, setter}`
|
||||
getter(name: T) {
|
||||
return () => this.current === name;
|
||||
}
|
||||
|
||||
setter(name: T) {
|
||||
return (isOpen: boolean) => {
|
||||
if (isOpen) {
|
||||
this.current = name;
|
||||
} else if (this.current === name) {
|
||||
this.current = undefined;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
export interface Toast {
|
||||
id: number;
|
||||
message: string;
|
||||
}
|
||||
|
||||
const TOAST_DURATION = 3000;
|
||||
|
||||
let toasts: Toast[] = $state([]);
|
||||
let nextId = 0;
|
||||
|
||||
export const getToasts = () => toasts;
|
||||
|
||||
export const showToast = (message: string) => {
|
||||
const toast = { id: nextId++, message };
|
||||
toasts.push(toast);
|
||||
setTimeout(() => {
|
||||
toasts = toasts.filter(({ id }) => id !== toast.id);
|
||||
}, TOAST_DURATION);
|
||||
};
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { AdaptiveDiv } from "#lib/components/atoms/index.js";
|
||||
import { AdaptiveDiv } from "#lib/ui/index.js";
|
||||
|
||||
let { children } = $props();
|
||||
</script>
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
<script lang="ts">
|
||||
import { goto } from "$app/navigation";
|
||||
import { BottomDiv, Button, FullscreenDiv, TextInput } from "#lib/components/atoms/index.js";
|
||||
import { TitledDiv, TopBar } from "#lib/components/molecules/index.js";
|
||||
import { showToast, tryAction } from "#lib/client/toast.svelte.js";
|
||||
import { changePassword as requestPasswordChange } from "#lib/features/auth/index.js";
|
||||
import { tryAction } from "#lib/features/errors.js";
|
||||
import {
|
||||
BottomDiv,
|
||||
Button,
|
||||
FullscreenDiv,
|
||||
TextInput,
|
||||
TitledDiv,
|
||||
TopBar,
|
||||
showToast,
|
||||
} from "#lib/ui/index.js";
|
||||
|
||||
let oldPassword = $state("");
|
||||
let newPassword = $state("");
|
||||
|
||||
@@ -1,22 +1,23 @@
|
||||
<script lang="ts">
|
||||
import { goto } from "$app/navigation";
|
||||
import {
|
||||
downloadMasterKeys,
|
||||
keyring,
|
||||
requestClientRegistrationAndSessionUpgrade,
|
||||
} from "#lib/client/keys/index.js";
|
||||
import { ForceLoginModal } from "#lib/features/auth/components/index.js";
|
||||
import { login as requestLogin, logout as requestLogout } from "#lib/features/auth/index.js";
|
||||
import { showErrorToast } from "#lib/features/errors.js";
|
||||
import { cleanupDeletedFileCaches } from "#lib/features/filesystem/index.js";
|
||||
import {
|
||||
BottomDiv,
|
||||
Button,
|
||||
FullscreenDiv,
|
||||
TextButton,
|
||||
TextInput,
|
||||
} from "#lib/components/atoms/index.js";
|
||||
import { TitledDiv } from "#lib/components/molecules/index.js";
|
||||
import { ForceLoginModal } from "#lib/components/organisms/index.js";
|
||||
import {
|
||||
downloadMasterKeys,
|
||||
keyring,
|
||||
requestClientRegistrationAndSessionUpgrade,
|
||||
} from "#lib/client/keys/index.js";
|
||||
import { showErrorToast, showToast } from "#lib/client/toast.svelte.js";
|
||||
import { login as requestLogin, logout as requestLogout } from "#lib/features/auth/index.js";
|
||||
import { cleanupDeletedFileCaches } from "#lib/features/filesystem/index.js";
|
||||
TitledDiv,
|
||||
showToast,
|
||||
} from "#lib/ui/index.js";
|
||||
import { isTRPCClientError } from "#trpc/client.js";
|
||||
|
||||
let { data } = $props();
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
import { goto } from "$app/navigation";
|
||||
import { FullscreenDiv } from "#lib/components/atoms/index.js";
|
||||
import { TitledDiv } from "#lib/components/molecules/index.js";
|
||||
import {
|
||||
downloadMasterKeys,
|
||||
generatePublicKeyFingerprint,
|
||||
keyring,
|
||||
} from "#lib/client/keys/index.js";
|
||||
import { FullscreenDiv, TitledDiv } from "#lib/ui/index.js";
|
||||
|
||||
import IconFingerprint from "~icons/material-symbols/fingerprint";
|
||||
|
||||
|
||||
@@ -3,11 +3,10 @@
|
||||
import { untrack } from "svelte";
|
||||
import { goto } from "$app/navigation";
|
||||
import { page } from "$app/state";
|
||||
import { FullscreenDiv } from "#lib/components/atoms/index.js";
|
||||
import { Categories, IconEntryButton, TopBar } from "#lib/components/molecules/index.js";
|
||||
import { captureVideoThumbnail } from "#lib/client/media/thumbnail.js";
|
||||
import { getFileDownloadState } from "#lib/client/transfer/index.js";
|
||||
import { showErrorToast, tryAction } from "#lib/client/toast.svelte.js";
|
||||
import { showErrorToast, tryAction } from "#lib/features/errors.js";
|
||||
import { Categories } from "#lib/features/filesystem/components/index.js";
|
||||
import {
|
||||
addFileToCategory,
|
||||
getFileContent,
|
||||
@@ -19,6 +18,7 @@
|
||||
type DataKey,
|
||||
type FileInfo,
|
||||
} from "#lib/features/filesystem/index.js";
|
||||
import { FullscreenDiv, IconEntryButton, TopBar } from "#lib/ui/index.js";
|
||||
import AddToCategoryBottomSheet from "./AddToCategoryBottomSheet.svelte";
|
||||
import DownloadStatus from "./DownloadStatus.svelte";
|
||||
import TopBarMenu from "./TopBarMenu.svelte";
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
<script lang="ts">
|
||||
import { BottomDiv, BottomSheet, Button, FullscreenDiv } from "#lib/components/atoms/index.js";
|
||||
import { SubCategories } from "#lib/components/molecules/index.js";
|
||||
import { CategoryCreateModal } from "#lib/components/organisms/index.js";
|
||||
import { tryAction } from "#lib/client/toast.svelte.js";
|
||||
import { tryAction } from "#lib/features/errors.js";
|
||||
import { SubCategories, CategoryCreateModal } from "#lib/features/filesystem/components/index.js";
|
||||
import { createCategory, useCategory } from "#lib/features/filesystem/index.js";
|
||||
import { BottomDiv, BottomSheet, Button, FullscreenDiv } from "#lib/ui/index.js";
|
||||
|
||||
interface Props {
|
||||
onAddToCategoryClick: (categoryId: number) => void;
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
import { FullscreenDiv } from "#lib/components/atoms/index.js";
|
||||
import { TopBar } from "#lib/components/molecules/index.js";
|
||||
import {
|
||||
getDownloadingFiles,
|
||||
clearDownloadedFiles,
|
||||
type FileDownloadState,
|
||||
} from "#lib/client/transfer/index.js";
|
||||
import { bulkGetFileInfo, type MaybeFileInfo } from "#lib/features/filesystem/index.js";
|
||||
import { FullscreenDiv, TopBar } from "#lib/ui/index.js";
|
||||
import File from "./File.svelte";
|
||||
|
||||
let downloadingFiles: { info: MaybeFileInfo; state: FileDownloadState }[] = $state([]);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { FullscreenDiv } from "#lib/components/atoms/index.js";
|
||||
import { TopBar } from "#lib/components/molecules/index.js";
|
||||
import { getUploadingFiles, clearUploadedFiles } from "#lib/client/transfer/index.js";
|
||||
import { FullscreenDiv, TopBar } from "#lib/ui/index.js";
|
||||
import File from "./File.svelte";
|
||||
|
||||
const uploadingFiles = getUploadingFiles();
|
||||
|
||||
@@ -1,17 +1,13 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
import { goto } from "$app/navigation";
|
||||
import {
|
||||
FileThumbnailButton,
|
||||
FullscreenDiv,
|
||||
RowVirtualizer,
|
||||
} from "#lib/components/atoms/index.js";
|
||||
import { TopBar } from "#lib/components/molecules/index.js";
|
||||
import { FileThumbnailButton } from "#lib/features/filesystem/components/index.js";
|
||||
import {
|
||||
bulkGetFileInfo,
|
||||
type MaybeFileInfo,
|
||||
type SummarizedFileInfo,
|
||||
} from "#lib/features/filesystem/index.js";
|
||||
import { FullscreenDiv, RowVirtualizer, TopBar } from "#lib/ui/index.js";
|
||||
import { formatDate, formatDateSortable, SortBy, sortEntries } from "#lib/utils/index.js";
|
||||
|
||||
let { data } = $props();
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
<script lang="ts">
|
||||
import FileSaver from "file-saver";
|
||||
import { goto } from "$app/navigation";
|
||||
import { BottomDiv, Button, FullscreenDiv, TextButton } from "#lib/components/atoms/index.js";
|
||||
import { TitledDiv } from "#lib/components/molecules/index.js";
|
||||
import {
|
||||
keyring,
|
||||
registerInitialMasterKeyAndHmacSecret,
|
||||
@@ -10,7 +8,14 @@
|
||||
requestSessionUpgrade,
|
||||
serializeClientKeys,
|
||||
} from "#lib/client/keys/index.js";
|
||||
import { showToast } from "#lib/client/toast.svelte.js";
|
||||
import {
|
||||
BottomDiv,
|
||||
Button,
|
||||
FullscreenDiv,
|
||||
TextButton,
|
||||
TitledDiv,
|
||||
showToast,
|
||||
} from "#lib/ui/index.js";
|
||||
import BeforeContinueBottomSheet from "./BeforeContinueBottomSheet.svelte";
|
||||
import BeforeContinueModal from "./BeforeContinueModal.svelte";
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { BottomDiv, BottomSheet, Button, FullscreenDiv } from "#lib/components/atoms/index.js";
|
||||
import { BottomDiv, BottomSheet, Button, FullscreenDiv } from "#lib/ui/index.js";
|
||||
|
||||
interface Props {
|
||||
onRetryClick: () => void;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { ActionModal } from "#lib/components/molecules/index.js";
|
||||
import { ActionModal } from "#lib/ui/index.js";
|
||||
|
||||
interface Props {
|
||||
isOpen: boolean;
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
import { goto } from "$app/navigation";
|
||||
import { BottomDiv, Button, FullscreenDiv, TextButton } from "#lib/components/atoms/index.js";
|
||||
import { TitledDiv } from "#lib/components/molecules/index.js";
|
||||
import { ForceLoginModal } from "#lib/components/organisms/index.js";
|
||||
import {
|
||||
generateClientKeys,
|
||||
generateInitialHmacSecret,
|
||||
@@ -13,8 +10,16 @@
|
||||
registerInitialMasterKeyAndHmacSecret,
|
||||
requestClientRegistrationAndSessionUpgrade,
|
||||
} from "#lib/client/keys/index.js";
|
||||
import { showToast } from "#lib/client/toast.svelte.js";
|
||||
import { ForceLoginModal } from "#lib/features/auth/components/index.js";
|
||||
import { cleanupDeletedFileCaches } from "#lib/features/filesystem/index.js";
|
||||
import {
|
||||
BottomDiv,
|
||||
Button,
|
||||
FullscreenDiv,
|
||||
TextButton,
|
||||
TitledDiv,
|
||||
showToast,
|
||||
} from "#lib/ui/index.js";
|
||||
import { gotoStateful } from "#lib/utils/index.js";
|
||||
import Order from "./Order.svelte";
|
||||
|
||||
|
||||
@@ -4,17 +4,16 @@
|
||||
import { untrack } from "svelte";
|
||||
import { slide } from "svelte/transition";
|
||||
import { goto } from "$app/navigation";
|
||||
import { Chip, FullscreenDiv, RowVirtualizer } from "#lib/components/atoms/index.js";
|
||||
import { showErrorToast } from "#lib/client/toast.svelte.js";
|
||||
import { showErrorToast } from "#lib/features/errors.js";
|
||||
import { DirectoryEntry, FileEntry } from "#lib/features/filesystem/components/index.js";
|
||||
import {
|
||||
getDirectoryInfo,
|
||||
type LocalCategoryInfo,
|
||||
type MaybeDirectoryInfo,
|
||||
} from "#lib/features/filesystem/index.js";
|
||||
import { search, type SearchFilter, type SearchResult } from "#lib/features/search/index.js";
|
||||
import { Chip, FullscreenDiv, RowVirtualizer } from "#lib/ui/index.js";
|
||||
import { searchString, sortEntries } from "#lib/utils/index.js";
|
||||
import Directory from "./Directory.svelte";
|
||||
import File from "./File.svelte";
|
||||
import SearchBar from "./SearchBar.svelte";
|
||||
import SelectCategoryBottomSheet from "./SelectCategoryBottomSheet.svelte";
|
||||
|
||||
@@ -236,9 +235,9 @@
|
||||
{#snippet item(index)}
|
||||
{@const info = result[index]!}
|
||||
{#if info.type === "directory"}
|
||||
<Directory {info} onclick={() => goto(`/directory/${info.id}?from=search`)} />
|
||||
<DirectoryEntry {info} onclick={() => goto(`/directory/${info.id}?from=search`)} />
|
||||
{:else}
|
||||
<File {info} onclick={() => goto(`/file/${info.id}?from=search`)} />
|
||||
<FileEntry {info} onclick={() => goto(`/file/${info.id}?from=search`)} />
|
||||
{/if}
|
||||
{/snippet}
|
||||
</RowVirtualizer>
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { ActionEntryButton } from "#lib/components/atoms/index.js";
|
||||
import { DirectoryEntryLabel } from "#lib/components/molecules/index.js";
|
||||
import type { SubDirectoryInfo } from "#lib/features/filesystem/index.js";
|
||||
|
||||
interface Props {
|
||||
info: SubDirectoryInfo;
|
||||
onclick: () => void;
|
||||
}
|
||||
|
||||
let { info, onclick }: Props = $props();
|
||||
</script>
|
||||
|
||||
<ActionEntryButton class="h-14" {onclick}>
|
||||
<DirectoryEntryLabel type="directory" name={info.name} isFavorite={info.isFavorite} />
|
||||
</ActionEntryButton>
|
||||
@@ -1,26 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { ActionEntryButton } from "#lib/components/atoms/index.js";
|
||||
import { DirectoryEntryLabel } from "#lib/components/molecules/index.js";
|
||||
import { getFileThumbnail } from "#lib/client/cache/index.js";
|
||||
import type { SummarizedFileInfo } from "#lib/features/filesystem/index.js";
|
||||
import { formatDateTime } from "#lib/utils/index.js";
|
||||
|
||||
interface Props {
|
||||
info: SummarizedFileInfo;
|
||||
onclick: () => void;
|
||||
}
|
||||
|
||||
let { info, onclick }: Props = $props();
|
||||
|
||||
let thumbnail = $derived(getFileThumbnail(info));
|
||||
</script>
|
||||
|
||||
<ActionEntryButton class="h-14" {onclick}>
|
||||
<DirectoryEntryLabel
|
||||
type="file"
|
||||
thumbnail={thumbnail?.url}
|
||||
name={info.name}
|
||||
subtext={formatDateTime(info.createdAt ?? info.lastModifiedAt)}
|
||||
isFavorite={info.isFavorite}
|
||||
/>
|
||||
</ActionEntryButton>
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { BottomDiv, BottomSheet, Button, FullscreenDiv } from "#lib/components/atoms/index.js";
|
||||
import { SubCategories } from "#lib/components/molecules/index.js";
|
||||
import { SubCategories } from "#lib/features/filesystem/components/index.js";
|
||||
import { useCategory, type LocalCategoryInfo } from "#lib/features/filesystem/index.js";
|
||||
import { BottomDiv, BottomSheet, Button, FullscreenDiv } from "#lib/ui/index.js";
|
||||
|
||||
interface Props {
|
||||
isOpen: boolean;
|
||||
|
||||
+2
-3
@@ -1,13 +1,12 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
import { FullscreenDiv } from "#lib/components/atoms/index.js";
|
||||
import { TopBar } from "#lib/components/molecules/index.js";
|
||||
import type { FileCacheIndex } from "#lib/client/storage/indexedDB/index.js";
|
||||
import {
|
||||
getFileCacheIndex,
|
||||
deleteFileCache as doDeleteFileCache,
|
||||
} from "#lib/client/cache/index.js";
|
||||
import type { FileCacheIndex } from "#lib/client/storage/indexedDB/index.js";
|
||||
import { bulkGetFileInfo, type MaybeFileInfo } from "#lib/features/filesystem/index.js";
|
||||
import { FullscreenDiv, TopBar } from "#lib/ui/index.js";
|
||||
import { formatFileSize } from "#lib/utils/index.js";
|
||||
import File from "./File.svelte";
|
||||
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
import { goto } from "$app/navigation";
|
||||
import { BottomDiv, Button, FullscreenDiv } from "#lib/components/atoms/index.js";
|
||||
import { IconEntryButton, TopBar } from "#lib/components/molecules/index.js";
|
||||
import { deleteAllFileThumbnailCaches } from "#lib/client/cache/index.js";
|
||||
import { FileEntry } from "#lib/features/filesystem/components/index.js";
|
||||
import type { MaybeFileInfo } from "#lib/features/filesystem/index.js";
|
||||
import { BottomDiv, Button, FullscreenDiv, IconEntryButton, TopBar } from "#lib/ui/index.js";
|
||||
import { sortEntries } from "#lib/utils/index.js";
|
||||
import File from "./File.svelte";
|
||||
import {
|
||||
getThumbnailGenerationStatus,
|
||||
clearThumbnailGenerationStatuses,
|
||||
@@ -15,10 +14,19 @@
|
||||
type GenerationStatus,
|
||||
} from "./service";
|
||||
|
||||
import IconCamera from "~icons/material-symbols/camera";
|
||||
import IconDelete from "~icons/material-symbols/delete";
|
||||
|
||||
let { data } = $props();
|
||||
|
||||
const statusTexts = {
|
||||
queued: "대기 중",
|
||||
"generation-pending": "준비 중",
|
||||
generating: "생성하는 중",
|
||||
uploading: "업로드하는 중",
|
||||
error: "실패",
|
||||
} as const;
|
||||
|
||||
let fileInfos: MaybeFileInfo[] = $state([]);
|
||||
let files = $derived(
|
||||
fileInfos
|
||||
@@ -69,11 +77,18 @@
|
||||
<div class="space-y-2">
|
||||
{#each files as { info, status } (info.id)}
|
||||
{#if info.exists}
|
||||
<File
|
||||
<FileEntry
|
||||
{info}
|
||||
{status}
|
||||
onclick={({ id }) => goto(`/file/${id}`)}
|
||||
onGenerateThumbnailClick={requestThumbnailGeneration}
|
||||
showThumbnail={false}
|
||||
subtext={status && statusTexts[status]}
|
||||
onclick={() => goto(`/file/${info.id}`)}
|
||||
action={!status || status === "error"
|
||||
? {
|
||||
icon: IconCamera,
|
||||
onclick: () => requestThumbnailGeneration(info),
|
||||
class: "text-gray-800",
|
||||
}
|
||||
: undefined}
|
||||
/>
|
||||
{/if}
|
||||
{/each}
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
<script module lang="ts">
|
||||
const subtexts = {
|
||||
queued: "대기 중",
|
||||
"generation-pending": "준비 중",
|
||||
generating: "생성하는 중",
|
||||
uploading: "업로드하는 중",
|
||||
error: "실패",
|
||||
} as const;
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { ActionEntryButton } from "#lib/components/atoms/index.js";
|
||||
import { DirectoryEntryLabel } from "#lib/components/molecules/index.js";
|
||||
import type { FileInfo } from "#lib/features/filesystem/index.js";
|
||||
import { formatDateTime } from "#lib/utils/index.js";
|
||||
import type { GenerationStatus } from "./service";
|
||||
|
||||
import IconCamera from "~icons/material-symbols/camera";
|
||||
|
||||
interface Props {
|
||||
info: FileInfo;
|
||||
onclick: (file: FileInfo) => void;
|
||||
onGenerateThumbnailClick: (file: FileInfo) => void;
|
||||
status: Exclude<GenerationStatus, "uploaded"> | undefined;
|
||||
}
|
||||
|
||||
let { info, onclick, onGenerateThumbnailClick, status }: Props = $props();
|
||||
</script>
|
||||
|
||||
<ActionEntryButton
|
||||
class="h-14"
|
||||
onclick={() => onclick(info)}
|
||||
actionButtonIcon={!status || status === "error" ? IconCamera : undefined}
|
||||
onActionButtonClick={() => onGenerateThumbnailClick(info)}
|
||||
actionButtonClass="text-gray-800"
|
||||
>
|
||||
{@const subtext = status
|
||||
? subtexts[status]
|
||||
: formatDateTime(info.createdAt ?? info.lastModifiedAt)}
|
||||
<DirectoryEntryLabel type="file" name={info.name} {subtext} />
|
||||
</ActionEntryButton>
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { AdaptiveDiv } from "#lib/components/atoms/index.js";
|
||||
import { AdaptiveDiv } from "#lib/ui/index.js";
|
||||
import BottomBar from "./BottomBar.svelte";
|
||||
|
||||
let { children } = $props();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { goto } from "$app/navigation";
|
||||
import { page } from "$app/state";
|
||||
import { AdaptiveDiv } from "#lib/components/atoms/index.js";
|
||||
import { AdaptiveDiv } from "#lib/ui/index.js";
|
||||
|
||||
import IconHome from "~icons/material-symbols/home";
|
||||
import IconFolder from "~icons/material-symbols/folder";
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
<script lang="ts">
|
||||
import { goto } from "$app/navigation";
|
||||
import { CheckBox, RowVirtualizer } from "#lib/components/atoms/index.js";
|
||||
import { SubCategories, TopBar } from "#lib/components/molecules/index.js";
|
||||
import { CategoryCreateModal } from "#lib/components/organisms/index.js";
|
||||
import { tryAction } from "#lib/client/toast.svelte.js";
|
||||
import { tryAction } from "#lib/features/errors.js";
|
||||
import {
|
||||
FileEntry,
|
||||
SubCategories,
|
||||
CategoryCreateModal,
|
||||
} from "#lib/features/filesystem/components/index.js";
|
||||
import {
|
||||
createCategory,
|
||||
deleteCategory,
|
||||
@@ -13,13 +15,14 @@
|
||||
useCategory,
|
||||
type LocalCategoryInfo,
|
||||
} from "#lib/features/filesystem/index.js";
|
||||
import { CheckBox, OverlayGroup, RowVirtualizer, TopBar } from "#lib/ui/index.js";
|
||||
import { sortEntries } from "#lib/utils/index.js";
|
||||
import CategoryDeleteModal from "./CategoryDeleteModal.svelte";
|
||||
import CategoryMenuBottomSheet from "./CategoryMenuBottomSheet.svelte";
|
||||
import CategoryRenameModal from "./CategoryRenameModal.svelte";
|
||||
import File from "./File.svelte";
|
||||
import { createContext } from "./service.svelte";
|
||||
|
||||
import IconClose from "~icons/material-symbols/close";
|
||||
import IconMoreVert from "~icons/material-symbols/more-vert";
|
||||
|
||||
let { data } = $props();
|
||||
@@ -28,10 +31,9 @@
|
||||
const category = useCategory(() => data.id);
|
||||
let info = $derived(category.current);
|
||||
|
||||
let isCategoryCreateModalOpen = $state(false);
|
||||
let isCategoryMenuBottomSheetOpen = $state(false);
|
||||
let isCategoryRenameModalOpen = $state(false);
|
||||
let isCategoryDeleteModalOpen = $state(false);
|
||||
const overlays = new OverlayGroup<
|
||||
"categoryCreate" | "categoryMenu" | "categoryRename" | "categoryDelete"
|
||||
>();
|
||||
|
||||
let files = $derived(
|
||||
sortEntries(
|
||||
@@ -59,11 +61,11 @@
|
||||
<SubCategories
|
||||
{info}
|
||||
onSubCategoryClick={({ id }) => goto(`/category/${id}`)}
|
||||
onSubCategoryCreateClick={() => (isCategoryCreateModalOpen = true)}
|
||||
onSubCategoryCreateClick={() => overlays.open("categoryCreate")}
|
||||
subCategoryCreatePosition="bottom"
|
||||
onSubCategoryMenuClick={(subCategory) => {
|
||||
context.selectedCategory = subCategory;
|
||||
isCategoryMenuBottomSheetOpen = true;
|
||||
overlays.open("categoryMenu");
|
||||
}}
|
||||
subCategoryMenuIcon={IconMoreVert}
|
||||
/>
|
||||
@@ -89,15 +91,18 @@
|
||||
>
|
||||
{#snippet item(index)}
|
||||
{@const { details } = files[index]!}
|
||||
<File
|
||||
<FileEntry
|
||||
info={details}
|
||||
onclick={({ id }) => goto(`/file/${id}?from=category`)}
|
||||
onRemoveClick={!details.isRecursive
|
||||
? ({ id }) =>
|
||||
tryAction(
|
||||
() => removeFileFromCategory(id, data.id as number),
|
||||
"카테고리에서 파일을 제거하지 못했어요.",
|
||||
)
|
||||
onclick={() => goto(`/file/${details.id}?from=category`)}
|
||||
action={!details.isRecursive
|
||||
? {
|
||||
icon: IconClose,
|
||||
onclick: () =>
|
||||
tryAction(
|
||||
() => removeFileFromCategory(details.id, data.id as number),
|
||||
"카테고리에서 파일을 제거하지 못했어요.",
|
||||
),
|
||||
}
|
||||
: undefined}
|
||||
/>
|
||||
{/snippet}
|
||||
@@ -112,24 +117,18 @@
|
||||
</div>
|
||||
|
||||
<CategoryCreateModal
|
||||
bind:isOpen={isCategoryCreateModalOpen}
|
||||
bind:isOpen={overlays.getter("categoryCreate"), overlays.setter("categoryCreate")}
|
||||
onCreateClick={(name: string) =>
|
||||
tryAction(() => createCategory(data.id, name), "카테고리를 만들지 못했어요.")}
|
||||
/>
|
||||
|
||||
<CategoryMenuBottomSheet
|
||||
bind:isOpen={isCategoryMenuBottomSheetOpen}
|
||||
onRenameClick={() => {
|
||||
isCategoryMenuBottomSheetOpen = false;
|
||||
isCategoryRenameModalOpen = true;
|
||||
}}
|
||||
onDeleteClick={() => {
|
||||
isCategoryMenuBottomSheetOpen = false;
|
||||
isCategoryDeleteModalOpen = true;
|
||||
}}
|
||||
bind:isOpen={overlays.getter("categoryMenu"), overlays.setter("categoryMenu")}
|
||||
onRenameClick={() => overlays.open("categoryRename")}
|
||||
onDeleteClick={() => overlays.open("categoryDelete")}
|
||||
/>
|
||||
<CategoryRenameModal
|
||||
bind:isOpen={isCategoryRenameModalOpen}
|
||||
bind:isOpen={overlays.getter("categoryRename"), overlays.setter("categoryRename")}
|
||||
onRenameClick={(newName: string) =>
|
||||
tryAction(
|
||||
() => renameCategory({ ...context.selectedCategory!, parentId: data.id }, newName),
|
||||
@@ -137,7 +136,7 @@
|
||||
)}
|
||||
/>
|
||||
<CategoryDeleteModal
|
||||
bind:isOpen={isCategoryDeleteModalOpen}
|
||||
bind:isOpen={overlays.getter("categoryDelete"), overlays.setter("categoryDelete")}
|
||||
onDeleteClick={() =>
|
||||
tryAction(
|
||||
() => deleteCategory({ ...context.selectedCategory!, parentId: data.id }),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { ActionModal } from "#lib/components/molecules/index.js";
|
||||
import { ActionModal } from "#lib/ui/index.js";
|
||||
import { truncateString } from "#lib/utils/index.js";
|
||||
import { useContext } from "./service.svelte";
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { BottomSheet } from "#lib/components/atoms/index.js";
|
||||
import { CategoryLabel, IconEntryButton } from "#lib/components/molecules/index.js";
|
||||
import { BottomSheet, CategoryLabel, IconEntryButton } from "#lib/ui/index.js";
|
||||
import { useContext } from "./service.svelte";
|
||||
|
||||
import IconEdit from "~icons/material-symbols/edit";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { RenameModal } from "#lib/components/organisms/index.js";
|
||||
import { RenameModal } from "#lib/ui/index.js";
|
||||
import { useContext } from "./service.svelte";
|
||||
|
||||
interface Props {
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { ActionEntryButton } from "#lib/components/atoms/index.js";
|
||||
import { DirectoryEntryLabel } from "#lib/components/molecules/index.js";
|
||||
import { getFileThumbnail } from "#lib/client/cache/index.js";
|
||||
import type { CategoryFileInfo } from "#lib/features/filesystem/index.js";
|
||||
import { formatDateTime } from "#lib/utils/index.js";
|
||||
import type { SelectedFile } from "./service.svelte";
|
||||
|
||||
import IconClose from "~icons/material-symbols/close";
|
||||
|
||||
interface Props {
|
||||
info: CategoryFileInfo;
|
||||
onclick: (file: SelectedFile) => void;
|
||||
onRemoveClick?: (file: SelectedFile) => void;
|
||||
}
|
||||
|
||||
let { info, onclick, onRemoveClick }: Props = $props();
|
||||
|
||||
let thumbnail = $derived(getFileThumbnail(info));
|
||||
</script>
|
||||
|
||||
<ActionEntryButton
|
||||
class="h-14"
|
||||
onclick={() => onclick(info)}
|
||||
actionButtonIcon={onRemoveClick && IconClose}
|
||||
onActionButtonClick={() => onRemoveClick?.(info)}
|
||||
>
|
||||
<DirectoryEntryLabel
|
||||
type="file"
|
||||
thumbnail={thumbnail?.url}
|
||||
name={info.name}
|
||||
subtext={formatDateTime(info.createdAt ?? info.lastModifiedAt)}
|
||||
isFavorite={info.isFavorite}
|
||||
/>
|
||||
</ActionEntryButton>
|
||||
@@ -1,18 +1,20 @@
|
||||
import { getContext, setContext } from "svelte";
|
||||
import type { SelectedCategory } from "#lib/components/molecules/index.js";
|
||||
import { createContext as createSvelteContext } from "svelte";
|
||||
import type { SelectedCategory } from "#lib/features/filesystem/components/index.js";
|
||||
|
||||
export interface SelectedFile {
|
||||
id: number;
|
||||
name: string;
|
||||
}
|
||||
|
||||
interface Context {
|
||||
selectedCategory?: SelectedCategory;
|
||||
}
|
||||
|
||||
const [getContext, setContext] = createSvelteContext<Context>();
|
||||
|
||||
export const createContext = () => {
|
||||
const context = $state({
|
||||
selectedCategory: undefined as SelectedCategory | undefined,
|
||||
});
|
||||
return setContext("context", context);
|
||||
const context: Context = $state({});
|
||||
return setContext(context);
|
||||
};
|
||||
|
||||
export const useContext = () => {
|
||||
return getContext<ReturnType<typeof createContext>>("context");
|
||||
};
|
||||
export const useContext = getContext;
|
||||
@@ -1,10 +1,9 @@
|
||||
<script lang="ts">
|
||||
import { goto } from "$app/navigation";
|
||||
import { page } from "$app/state";
|
||||
import { FloatingButton } from "#lib/components/atoms/index.js";
|
||||
import { TopBar } from "#lib/components/molecules/index.js";
|
||||
import { tryAction } from "#lib/client/toast.svelte.js";
|
||||
import { tryAction } from "#lib/features/errors.js";
|
||||
import { createDirectory, uploadFile, useDirectory } from "#lib/features/filesystem/index.js";
|
||||
import { FloatingButton, OverlayGroup, TopBar } from "#lib/ui/index.js";
|
||||
import DirectoryCreateModal from "./DirectoryCreateModal.svelte";
|
||||
import DirectoryEntries from "./DirectoryEntries";
|
||||
import DownloadStatusCard from "./DownloadStatusCard.svelte";
|
||||
@@ -28,14 +27,12 @@
|
||||
let duplicatedFile: File | undefined = $state();
|
||||
let resolveForDuplicateFileModal: ((res: boolean) => void) | undefined = $state();
|
||||
|
||||
let isEntryCreateBottomSheetOpen = $state(false);
|
||||
let isDirectoryCreateModalOpen = $state(false);
|
||||
const overlays = new OverlayGroup<
|
||||
"entryCreate" | "directoryCreate" | "entryMenu" | "entryRename" | "entryDelete"
|
||||
>();
|
||||
// Shown independently as it is requested while uploading in the background
|
||||
let isDuplicateFileModalOpen = $state(false);
|
||||
|
||||
let isEntryMenuBottomSheetOpen = $state(false);
|
||||
let isEntryRenameModalOpen = $state(false);
|
||||
let isEntryDeleteModalOpen = $state(false);
|
||||
|
||||
let showParentEntry = $derived(
|
||||
["file", "search", "favorite"].includes(page.url.searchParams.get("from") ?? ""),
|
||||
);
|
||||
@@ -96,7 +93,7 @@
|
||||
onEntryClick={({ type, id }) => goto(`/${type}/${id}`)}
|
||||
onEntryMenuClick={(entry) => {
|
||||
context.selectedEntry = entry;
|
||||
isEntryMenuBottomSheetOpen = true;
|
||||
overlays.open("entryMenu");
|
||||
}}
|
||||
showParentEntry={showParentEntry && data.id !== "root"}
|
||||
onParentClick={() =>
|
||||
@@ -114,23 +111,20 @@
|
||||
<FloatingButton
|
||||
icon={IconAdd}
|
||||
onclick={() => {
|
||||
isEntryCreateBottomSheetOpen = true;
|
||||
overlays.open("entryCreate");
|
||||
}}
|
||||
class="right-4 bottom-24"
|
||||
/>
|
||||
<EntryCreateBottomSheet
|
||||
bind:isOpen={isEntryCreateBottomSheetOpen}
|
||||
onDirectoryCreateClick={() => {
|
||||
isEntryCreateBottomSheetOpen = false;
|
||||
isDirectoryCreateModalOpen = true;
|
||||
}}
|
||||
bind:isOpen={overlays.getter("entryCreate"), overlays.setter("entryCreate")}
|
||||
onDirectoryCreateClick={() => overlays.open("directoryCreate")}
|
||||
onFileUploadClick={() => {
|
||||
isEntryCreateBottomSheetOpen = false;
|
||||
overlays.close();
|
||||
fileInput?.click();
|
||||
}}
|
||||
/>
|
||||
<DirectoryCreateModal
|
||||
bind:isOpen={isDirectoryCreateModalOpen}
|
||||
bind:isOpen={overlays.getter("directoryCreate"), overlays.setter("directoryCreate")}
|
||||
onCreateClick={(name) =>
|
||||
tryAction(() => createDirectory(data.id, name), "폴더를 만들지 못했어요.")}
|
||||
/>
|
||||
@@ -148,15 +142,9 @@
|
||||
/>
|
||||
|
||||
<EntryMenuBottomSheet
|
||||
bind:isOpen={isEntryMenuBottomSheetOpen}
|
||||
onRenameClick={() => {
|
||||
isEntryMenuBottomSheetOpen = false;
|
||||
isEntryRenameModalOpen = true;
|
||||
}}
|
||||
onDeleteClick={() => {
|
||||
isEntryMenuBottomSheetOpen = false;
|
||||
isEntryDeleteModalOpen = true;
|
||||
}}
|
||||
bind:isOpen={overlays.getter("entryMenu"), overlays.setter("entryMenu")}
|
||||
onRenameClick={() => overlays.open("entryRename")}
|
||||
onDeleteClick={() => overlays.open("entryDelete")}
|
||||
onFavoriteClick={async () => {
|
||||
if (
|
||||
await tryAction(
|
||||
@@ -164,16 +152,16 @@
|
||||
"즐겨찾기를 변경하지 못했어요.",
|
||||
)
|
||||
) {
|
||||
isEntryMenuBottomSheetOpen = false;
|
||||
overlays.close();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<EntryRenameModal
|
||||
bind:isOpen={isEntryRenameModalOpen}
|
||||
bind:isOpen={overlays.getter("entryRename"), overlays.setter("entryRename")}
|
||||
onRenameClick={(newName: string) =>
|
||||
tryAction(() => renameEntry(context.selectedEntry!, newName), "이름을 바꾸지 못했어요.")}
|
||||
/>
|
||||
<EntryDeleteModal
|
||||
bind:isOpen={isEntryDeleteModalOpen}
|
||||
bind:isOpen={overlays.getter("entryDelete"), overlays.setter("entryDelete")}
|
||||
onDeleteClick={() => tryAction(() => deleteEntry(context.selectedEntry!), "삭제하지 못했어요.")}
|
||||
/>
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { TextInputModal } from "#lib/components/organisms/index.js";
|
||||
import { TextInputModal } from "#lib/ui/index.js";
|
||||
|
||||
interface Props {
|
||||
isOpen: boolean;
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
<script lang="ts">
|
||||
import { ActionEntryButton, RowVirtualizer } from "#lib/components/atoms/index.js";
|
||||
import { DirectoryEntryLabel } from "#lib/components/molecules/index.js";
|
||||
import { getUploadingFiles, type LiveFileUploadState } from "#lib/client/transfer/index.js";
|
||||
import type { DirectoryInfo } from "#lib/features/filesystem/index.js";
|
||||
import { DirectoryEntry, FileEntry } from "#lib/features/filesystem/components/index.js";
|
||||
import type {
|
||||
DirectoryInfo,
|
||||
SubDirectoryInfo,
|
||||
SummarizedFileInfo,
|
||||
} from "#lib/features/filesystem/index.js";
|
||||
import { ActionEntryButton, RowVirtualizer, DirectoryEntryLabel } from "#lib/ui/index.js";
|
||||
import { sortEntries } from "#lib/utils/index.js";
|
||||
import File from "./File.svelte";
|
||||
import SubDirectory from "./SubDirectory.svelte";
|
||||
import UploadingFile from "./UploadingFile.svelte";
|
||||
import type { SelectedEntry } from "../service.svelte";
|
||||
|
||||
import IconMoreVert from "~icons/material-symbols/more-vert";
|
||||
|
||||
interface Props {
|
||||
info: DirectoryInfo;
|
||||
onEntryClick: (entry: SelectedEntry) => void;
|
||||
@@ -35,6 +39,11 @@
|
||||
<T extends Exclude<Entry["type"], "parent">>(type: T) =>
|
||||
(details: Extract<Entry, { type: T }>["details"]) => ({ type, name: details.name, details });
|
||||
|
||||
const toSelectedEntry = (
|
||||
type: SelectedEntry["type"],
|
||||
{ id, parentId, dataKey, name, isFavorite }: SubDirectoryInfo | SummarizedFileInfo,
|
||||
): SelectedEntry => ({ type, id, parentId, dataKey, name, isFavorite });
|
||||
|
||||
let entries = $derived([
|
||||
...(showParentEntry ? ([{ type: "parent" }] as const) : []),
|
||||
...sortEntries(info.subDirectories.map(toEntry("directory"))),
|
||||
@@ -63,13 +72,19 @@
|
||||
<DirectoryEntryLabel type="parent-directory" name=".." />
|
||||
</ActionEntryButton>
|
||||
{:else if entry.type === "directory"}
|
||||
<SubDirectory
|
||||
{@const selected = toSelectedEntry("directory", entry.details)}
|
||||
<DirectoryEntry
|
||||
info={entry.details}
|
||||
onclick={onEntryClick}
|
||||
onOpenMenuClick={onEntryMenuClick}
|
||||
onclick={() => onEntryClick(selected)}
|
||||
action={{ icon: IconMoreVert, onclick: () => onEntryMenuClick(selected) }}
|
||||
/>
|
||||
{:else if entry.type === "file"}
|
||||
<File info={entry.details} onclick={onEntryClick} onOpenMenuClick={onEntryMenuClick} />
|
||||
{@const selected = toSelectedEntry("file", entry.details)}
|
||||
<FileEntry
|
||||
info={entry.details}
|
||||
onclick={() => onEntryClick(selected)}
|
||||
action={{ icon: IconMoreVert, onclick: () => onEntryMenuClick(selected) }}
|
||||
/>
|
||||
{:else}
|
||||
<UploadingFile state={entry.details} />
|
||||
{/if}
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { ActionEntryButton } from "#lib/components/atoms/index.js";
|
||||
import { DirectoryEntryLabel } from "#lib/components/molecules/index.js";
|
||||
import { getFileThumbnail } from "#lib/client/cache/index.js";
|
||||
import type { SummarizedFileInfo } from "#lib/features/filesystem/index.js";
|
||||
import { formatDateTime } from "#lib/utils/index.js";
|
||||
import type { SelectedEntry } from "../service.svelte";
|
||||
|
||||
import IconMoreVert from "~icons/material-symbols/more-vert";
|
||||
|
||||
interface Props {
|
||||
info: SummarizedFileInfo;
|
||||
onclick: (entry: SelectedEntry) => void;
|
||||
onOpenMenuClick: (entry: SelectedEntry) => void;
|
||||
}
|
||||
|
||||
let { info, onclick, onOpenMenuClick }: Props = $props();
|
||||
|
||||
let thumbnail = $derived(getFileThumbnail(info));
|
||||
|
||||
const action = (callback: typeof onclick) => {
|
||||
callback({
|
||||
type: "file",
|
||||
id: info.id,
|
||||
parentId: info.parentId,
|
||||
dataKey: info.dataKey,
|
||||
name: info.name,
|
||||
isFavorite: info.isFavorite,
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<ActionEntryButton
|
||||
class="h-14"
|
||||
onclick={() => action(onclick)}
|
||||
actionButtonIcon={IconMoreVert}
|
||||
onActionButtonClick={() => action(onOpenMenuClick)}
|
||||
>
|
||||
<DirectoryEntryLabel
|
||||
type="file"
|
||||
thumbnail={thumbnail?.url}
|
||||
name={info.name}
|
||||
subtext={formatDateTime(info.createdAt ?? info.lastModifiedAt)}
|
||||
isFavorite={info.isFavorite}
|
||||
/>
|
||||
</ActionEntryButton>
|
||||
@@ -1,36 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { ActionEntryButton } from "#lib/components/atoms/index.js";
|
||||
import { DirectoryEntryLabel } from "#lib/components/molecules/index.js";
|
||||
import type { SubDirectoryInfo } from "#lib/features/filesystem/index.js";
|
||||
import type { SelectedEntry } from "../service.svelte";
|
||||
|
||||
import IconMoreVert from "~icons/material-symbols/more-vert";
|
||||
|
||||
interface Props {
|
||||
info: SubDirectoryInfo;
|
||||
onclick: (entry: SelectedEntry) => void;
|
||||
onOpenMenuClick: (entry: SelectedEntry) => void;
|
||||
}
|
||||
|
||||
let { info, onclick, onOpenMenuClick }: Props = $props();
|
||||
|
||||
const action = (callback: typeof onclick) => {
|
||||
callback({
|
||||
type: "directory",
|
||||
id: info.id,
|
||||
parentId: info.parentId,
|
||||
dataKey: info.dataKey,
|
||||
name: info.name,
|
||||
isFavorite: info.isFavorite,
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<ActionEntryButton
|
||||
class="h-14"
|
||||
onclick={() => action(onclick)}
|
||||
actionButtonIcon={IconMoreVert}
|
||||
onActionButtonClick={() => action(onOpenMenuClick)}
|
||||
>
|
||||
<DirectoryEntryLabel type="directory" name={info.name} isFavorite={info.isFavorite} />
|
||||
</ActionEntryButton>
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { ActionModal } from "#lib/components/molecules/index.js";
|
||||
import { ActionModal } from "#lib/ui/index.js";
|
||||
import { truncateString } from "#lib/utils/index.js";
|
||||
|
||||
interface Props {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { BottomSheet } from "#lib/components/atoms/index.js";
|
||||
import { IconEntryButton } from "#lib/components/molecules/index.js";
|
||||
import { BottomSheet, IconEntryButton } from "#lib/ui/index.js";
|
||||
|
||||
import IconCreateNewFolder from "~icons/material-symbols/create-new-folder";
|
||||
import IconUploadFile from "~icons/material-symbols/upload-file";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { ActionModal } from "#lib/components/molecules/index.js";
|
||||
import { ActionModal } from "#lib/ui/index.js";
|
||||
import { truncateString } from "#lib/utils/index.js";
|
||||
import { useContext } from "./service.svelte";
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { BottomSheet } from "#lib/components/atoms/index.js";
|
||||
import { DirectoryEntryLabel, IconEntryButton } from "#lib/components/molecules/index.js";
|
||||
import { BottomSheet, DirectoryEntryLabel, IconEntryButton } from "#lib/ui/index.js";
|
||||
import { useContext } from "./service.svelte";
|
||||
|
||||
import IconFavorite from "~icons/material-symbols/favorite";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { RenameModal } from "#lib/components/organisms/index.js";
|
||||
import { RenameModal } from "#lib/ui/index.js";
|
||||
import { useContext } from "./service.svelte";
|
||||
|
||||
interface Props {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getContext, setContext } from "svelte";
|
||||
import { createContext as createSvelteContext } from "svelte";
|
||||
import {
|
||||
deleteDirectory,
|
||||
deleteFile,
|
||||
@@ -18,16 +18,18 @@ export interface SelectedEntry {
|
||||
isFavorite: boolean;
|
||||
}
|
||||
|
||||
interface Context {
|
||||
selectedEntry?: SelectedEntry;
|
||||
}
|
||||
|
||||
const [getContext, setContext] = createSvelteContext<Context>();
|
||||
|
||||
export const createContext = () => {
|
||||
const context = $state({
|
||||
selectedEntry: undefined as SelectedEntry | undefined,
|
||||
});
|
||||
return setContext("context", context);
|
||||
const context: Context = $state({});
|
||||
return setContext(context);
|
||||
};
|
||||
|
||||
export const useContext = () => {
|
||||
return getContext<ReturnType<typeof createContext>>("context");
|
||||
};
|
||||
export const useContext = getContext;
|
||||
|
||||
export const renameEntry = async (entry: SelectedEntry, newName: string) => {
|
||||
if (entry.type === "directory") {
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
import { goto } from "$app/navigation";
|
||||
import { RowVirtualizer } from "#lib/components/atoms/index.js";
|
||||
import { TopBar } from "#lib/components/molecules/index.js";
|
||||
import Directory from "./Directory.svelte";
|
||||
import File from "./File.svelte";
|
||||
import { showErrorToast, tryAction } from "#lib/client/toast.svelte.js";
|
||||
import { showErrorToast, tryAction } from "#lib/features/errors.js";
|
||||
import {
|
||||
getFavoriteEntries,
|
||||
removeFavorite,
|
||||
type FavoriteEntry,
|
||||
} from "#lib/features/favorites/index.js";
|
||||
import { DirectoryEntry, FileEntry } from "#lib/features/filesystem/components/index.js";
|
||||
import { RowVirtualizer, TopBar } from "#lib/ui/index.js";
|
||||
|
||||
import IconClose from "~icons/material-symbols/close";
|
||||
import IconSearch from "~icons/material-symbols/search";
|
||||
|
||||
let entries: FavoriteEntry[] = $state([]);
|
||||
@@ -68,16 +67,16 @@
|
||||
{#snippet item(index)}
|
||||
{@const entry = entries[index]!}
|
||||
{#if entry.type === "directory"}
|
||||
<Directory
|
||||
<DirectoryEntry
|
||||
info={entry.details}
|
||||
onclick={() => handleClick(entry)}
|
||||
onRemoveClick={() => handleRemove(entry)}
|
||||
action={{ icon: IconClose, onclick: () => handleRemove(entry) }}
|
||||
/>
|
||||
{:else}
|
||||
<File
|
||||
<FileEntry
|
||||
info={entry.details}
|
||||
onclick={() => handleClick(entry)}
|
||||
onRemoveClick={() => handleRemove(entry)}
|
||||
action={{ icon: IconClose, onclick: () => handleRemove(entry) }}
|
||||
/>
|
||||
{/if}
|
||||
{/snippet}
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { ActionEntryButton } from "#lib/components/atoms/index.js";
|
||||
import { DirectoryEntryLabel } from "#lib/components/molecules/index.js";
|
||||
import type { SubDirectoryInfo } from "#lib/features/filesystem/index.js";
|
||||
|
||||
import IconClose from "~icons/material-symbols/close";
|
||||
|
||||
interface Props {
|
||||
info: SubDirectoryInfo;
|
||||
onclick: () => void;
|
||||
onRemoveClick: () => void;
|
||||
}
|
||||
|
||||
let { info, onclick, onRemoveClick }: Props = $props();
|
||||
</script>
|
||||
|
||||
<ActionEntryButton
|
||||
class="h-14"
|
||||
{onclick}
|
||||
actionButtonIcon={IconClose}
|
||||
onActionButtonClick={onRemoveClick}
|
||||
>
|
||||
<DirectoryEntryLabel type="directory" name={info.name} isFavorite />
|
||||
</ActionEntryButton>
|
||||
@@ -1,34 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { ActionEntryButton } from "#lib/components/atoms/index.js";
|
||||
import { DirectoryEntryLabel } from "#lib/components/molecules/index.js";
|
||||
import { getFileThumbnail } from "#lib/client/cache/index.js";
|
||||
import type { SummarizedFileInfo } from "#lib/features/filesystem/index.js";
|
||||
import { formatDateTime } from "#lib/utils/index.js";
|
||||
|
||||
import IconClose from "~icons/material-symbols/close";
|
||||
|
||||
interface Props {
|
||||
info: SummarizedFileInfo;
|
||||
onclick: () => void;
|
||||
onRemoveClick: () => void;
|
||||
}
|
||||
|
||||
let { info, onclick, onRemoveClick }: Props = $props();
|
||||
|
||||
let thumbnail = $derived(getFileThumbnail(info));
|
||||
</script>
|
||||
|
||||
<ActionEntryButton
|
||||
class="h-14"
|
||||
{onclick}
|
||||
actionButtonIcon={IconClose}
|
||||
onActionButtonClick={onRemoveClick}
|
||||
>
|
||||
<DirectoryEntryLabel
|
||||
type="file"
|
||||
thumbnail={thumbnail?.url}
|
||||
name={info.name}
|
||||
subtext={formatDateTime(info.createdAt ?? info.lastModifiedAt)}
|
||||
isFavorite
|
||||
/>
|
||||
</ActionEntryButton>
|
||||
@@ -1,8 +1,9 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
import { goto } from "$app/navigation";
|
||||
import { EntryButton, FileThumbnailButton } from "#lib/components/atoms/index.js";
|
||||
import { FileThumbnailButton } from "#lib/features/filesystem/components/index.js";
|
||||
import { type SummarizedFileInfo } from "#lib/features/filesystem/index.js";
|
||||
import { EntryButton } from "#lib/ui/index.js";
|
||||
import { requestFreshMediaFilesRetrieval } from "./service";
|
||||
|
||||
let mediaFiles: SummarizedFileInfo[] = $state([]);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<script lang="ts">
|
||||
import { goto } from "$app/navigation";
|
||||
import MenuEntryButton from "./MenuEntryButton.svelte";
|
||||
import { tryAction } from "#lib/client/toast.svelte.js";
|
||||
import { logout as requestLogout } from "#lib/features/auth/index.js";
|
||||
import { tryAction } from "#lib/features/errors.js";
|
||||
|
||||
import IconStorage from "~icons/material-symbols/storage";
|
||||
import IconImage from "~icons/material-symbols/image";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import type { Component, Snippet } from "svelte";
|
||||
import type { ClassValue, SvelteHTMLElements } from "svelte/elements";
|
||||
import { IconEntryButton } from "#lib/components/molecules/index.js";
|
||||
import { IconEntryButton } from "#lib/ui/index.js";
|
||||
|
||||
interface Props {
|
||||
children: Snippet;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { getDownloadingFiles, getUploadingFiles } from "#lib/client/transfer/index.js";
|
||||
import { Toasts } from "#lib/components/atoms/index.js";
|
||||
import { Toasts } from "#lib/ui/index.js";
|
||||
import "../app.css";
|
||||
|
||||
let { children } = $props();
|
||||
|
||||
Reference in new issue
Block a user