mirror of
https://github.com/kmc7468/arkvault.git
synced 2026-11-04 08:10: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));
|
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(
|
export default defineConfig(
|
||||||
includeIgnoreFile(gitignorePath),
|
includeIgnoreFile(gitignorePath),
|
||||||
js.configs.recommended,
|
js.configs.recommended,
|
||||||
@@ -37,6 +51,17 @@ export default defineConfig(
|
|||||||
"no-undef": "off",
|
"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"],
|
files: ["**/*.svelte", "**/*.svelte.ts", "**/*.svelte.js"],
|
||||||
languageOptions: {
|
languageOptions: {
|
||||||
|
|||||||
+5
-2
@@ -1,7 +1,6 @@
|
|||||||
/* eslint-disable svelte/prefer-svelte-reactivity -- Lookup tables of reactive entries */
|
/* eslint-disable svelte/prefer-svelte-reactivity -- Lookup tables of reactive entries */
|
||||||
import { LRUCache } from "lru-cache";
|
import { LRUCache } from "lru-cache";
|
||||||
import { deleteDirectory, deleteFile, readFile, writeFile } from "#lib/client/storage/opfs.js";
|
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";
|
import { decryptChunk, encodeToBase64 } from "#lib/shared/crypto/index.js";
|
||||||
|
|
||||||
export interface Thumbnail {
|
export interface Thumbnail {
|
||||||
@@ -33,7 +32,11 @@ const fetchFromServer = async (fileId: number, dataKey: CryptoKey) => {
|
|||||||
return getThumbnailUrl(thumbnailBuffer);
|
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/"))) {
|
if (!(file.contentType.startsWith("image/") || file.contentType.startsWith("video/"))) {
|
||||||
return undefined;
|
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">
|
<script lang="ts">
|
||||||
import { ActionModal } from "#lib/components/molecules/index.js";
|
import { ActionModal } from "#lib/ui/index.js";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
isOpen: boolean;
|
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";
|
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) => {
|
const getErrorMessage = (e: unknown, fallbackMessage: string) => {
|
||||||
if (isTRPCClientError(e)) {
|
if (isTRPCClientError(e)) {
|
||||||
switch (e.data?.code) {
|
switch (e.data?.code) {
|
||||||
+2
-4
@@ -1,5 +1,5 @@
|
|||||||
<script module lang="ts">
|
<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 {
|
export interface SelectedCategory {
|
||||||
id: number;
|
id: number;
|
||||||
@@ -11,9 +11,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Component } from "svelte";
|
import type { Component } from "svelte";
|
||||||
import type { SvelteHTMLElements } from "svelte/elements";
|
import type { SvelteHTMLElements } from "svelte/elements";
|
||||||
import { ActionEntryButton } from "#lib/components/atoms/index.js";
|
import { ActionEntryButton, CategoryLabel } from "#lib/ui/index.js";
|
||||||
import { CategoryLabel } from "#lib/components/molecules/index.js";
|
|
||||||
import type { SubCategoryInfo } from "#lib/features/filesystem/index.js";
|
|
||||||
import { sortEntries } from "#lib/utils/index.js";
|
import { sortEntries } from "#lib/utils/index.js";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { TextInputModal } from "#lib/components/organisms/index.js";
|
import { TextInputModal } from "#lib/ui/index.js";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
isOpen: boolean;
|
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">
|
<script lang="ts">
|
||||||
import type { Component } from "svelte";
|
import type { Component } from "svelte";
|
||||||
import type { ClassValue, SvelteHTMLElements } from "svelte/elements";
|
import type { ClassValue, SvelteHTMLElements } from "svelte/elements";
|
||||||
import {
|
import { Categories, type SelectedCategory } from "#lib/features/filesystem/components/index.js";
|
||||||
Categories,
|
|
||||||
IconEntryButton,
|
|
||||||
type SelectedCategory,
|
|
||||||
} from "#lib/components/molecules/index.js";
|
|
||||||
import type { CategoryInfo } from "#lib/features/filesystem/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";
|
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>
|
</div>
|
||||||
|
|
||||||
<style lang="postcss">
|
<style lang="postcss">
|
||||||
@reference "../../../../app.css";
|
@reference "../../../app.css";
|
||||||
|
|
||||||
#container:active:not(:has(#action-button:active)) {
|
#container:active:not(:has(#action-button:active)) {
|
||||||
@apply bg-gray-100;
|
@apply bg-gray-100;
|
||||||
File renamed without changes.
File renamed without changes.
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Component } from "svelte";
|
import type { Component } from "svelte";
|
||||||
import type { ClassValue, SvelteHTMLElements } from "svelte/elements";
|
import type { ClassValue, SvelteHTMLElements } from "svelte/elements";
|
||||||
import { AdaptiveDiv } from "#lib/components/atoms/index.js";
|
import { AdaptiveDiv } from "#lib/ui/index.js";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
class: ClassValue;
|
class: ClassValue;
|
||||||
+1
-2
@@ -1,8 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Component, Snippet } from "svelte";
|
import type { Component, Snippet } from "svelte";
|
||||||
import type { ClassValue, SvelteHTMLElements } from "svelte/elements";
|
import type { ClassValue, SvelteHTMLElements } from "svelte/elements";
|
||||||
import { EntryButton } from "#lib/components/atoms/index.js";
|
import { EntryButton, IconLabel } from "#lib/ui/index.js";
|
||||||
import { IconLabel } from "#lib/components/molecules/index.js";
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
children: Snippet;
|
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>
|
</div>
|
||||||
|
|
||||||
<style lang="postcss">
|
<style lang="postcss">
|
||||||
@reference "../../../../app.css";
|
@reference "../../../app.css";
|
||||||
|
|
||||||
input:focus,
|
input:focus,
|
||||||
input:not(:placeholder-shown) {
|
input:not(:placeholder-shown) {
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { ClassValue } from "svelte/elements";
|
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";
|
import IconCategory from "~icons/material-symbols/category";
|
||||||
|
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { ClassValue } from "svelte/elements";
|
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 IconFolder from "~icons/material-symbols/folder";
|
||||||
import IconDriveFolderUpload from "~icons/material-symbols/drive-folder-upload";
|
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">
|
<script lang="ts">
|
||||||
import type { Component, Snippet } from "svelte";
|
import type { Component, Snippet } from "svelte";
|
||||||
import type { ClassValue, SvelteHTMLElements } from "svelte/elements";
|
import type { ClassValue, SvelteHTMLElements } from "svelte/elements";
|
||||||
import { TitleLabel } from "#lib/components/molecules/index.js";
|
import { TitleLabel } from "#lib/ui/index.js";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
children?: Snippet;
|
children?: Snippet;
|
||||||
File renamed without changes.
+1
-1
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Snippet } from "svelte";
|
import type { Snippet } from "svelte";
|
||||||
import { Button, Modal } from "#lib/components/atoms/index.js";
|
import { Button, Modal } from "#lib/ui/index.js";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
cancelText?: string;
|
cancelText?: string;
|
||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
import type { Snippet } from "svelte";
|
import type { Snippet } from "svelte";
|
||||||
import type { ClassValue } from "svelte/elements";
|
import type { ClassValue } from "svelte/elements";
|
||||||
import { fade, fly } from "svelte/transition";
|
import { fade, fly } from "svelte/transition";
|
||||||
import { AdaptiveDiv } from "#lib/components/atoms/index.js";
|
import { AdaptiveDiv } from "#lib/ui/index.js";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
children: Snippet;
|
children: Snippet;
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
import type { Snippet } from "svelte";
|
import type { Snippet } from "svelte";
|
||||||
import type { ClassValue } from "svelte/elements";
|
import type { ClassValue } from "svelte/elements";
|
||||||
import { fade } from "svelte/transition";
|
import { fade } from "svelte/transition";
|
||||||
import { AdaptiveDiv } from "#lib/components/atoms/index.js";
|
import { AdaptiveDiv } from "#lib/ui/index.js";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
children: Snippet;
|
children: Snippet;
|
||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { TextInputModal } from "#lib/components/organisms/index.js";
|
import { TextInputModal } from "#lib/ui/index.js";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
+1
-2
@@ -1,6 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { TextInput } from "#lib/components/atoms/index.js";
|
import { TextInput, ActionModal, type ConfirmHandler } from "#lib/ui/index.js";
|
||||||
import { ActionModal, type ConfirmHandler } from "#lib/components/molecules/index.js";
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
defaultValue?: string;
|
defaultValue?: string;
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { fly } from "svelte/transition";
|
import { fly } from "svelte/transition";
|
||||||
import { getToasts } from "#lib/client/toast.svelte.js";
|
import { getToasts } from "./toast.svelte";
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="pointer-events-none fixed inset-x-0 top-4 z-50 flex flex-col items-center gap-y-2 px-4">
|
<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">
|
<script lang="ts">
|
||||||
import { AdaptiveDiv } from "#lib/components/atoms/index.js";
|
import { AdaptiveDiv } from "#lib/ui/index.js";
|
||||||
|
|
||||||
let { children } = $props();
|
let { children } = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,9 +1,16 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { goto } from "$app/navigation";
|
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 { 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 oldPassword = $state("");
|
||||||
let newPassword = $state("");
|
let newPassword = $state("");
|
||||||
|
|||||||
@@ -1,22 +1,23 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { goto } from "$app/navigation";
|
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 {
|
import {
|
||||||
BottomDiv,
|
BottomDiv,
|
||||||
Button,
|
Button,
|
||||||
FullscreenDiv,
|
FullscreenDiv,
|
||||||
TextButton,
|
TextButton,
|
||||||
TextInput,
|
TextInput,
|
||||||
} from "#lib/components/atoms/index.js";
|
TitledDiv,
|
||||||
import { TitledDiv } from "#lib/components/molecules/index.js";
|
showToast,
|
||||||
import { ForceLoginModal } from "#lib/components/organisms/index.js";
|
} from "#lib/ui/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";
|
|
||||||
import { isTRPCClientError } from "#trpc/client.js";
|
import { isTRPCClientError } from "#trpc/client.js";
|
||||||
|
|
||||||
let { data } = $props();
|
let { data } = $props();
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
import { goto } from "$app/navigation";
|
import { goto } from "$app/navigation";
|
||||||
import { FullscreenDiv } from "#lib/components/atoms/index.js";
|
|
||||||
import { TitledDiv } from "#lib/components/molecules/index.js";
|
|
||||||
import {
|
import {
|
||||||
downloadMasterKeys,
|
downloadMasterKeys,
|
||||||
generatePublicKeyFingerprint,
|
generatePublicKeyFingerprint,
|
||||||
keyring,
|
keyring,
|
||||||
} from "#lib/client/keys/index.js";
|
} from "#lib/client/keys/index.js";
|
||||||
|
import { FullscreenDiv, TitledDiv } from "#lib/ui/index.js";
|
||||||
|
|
||||||
import IconFingerprint from "~icons/material-symbols/fingerprint";
|
import IconFingerprint from "~icons/material-symbols/fingerprint";
|
||||||
|
|
||||||
|
|||||||
@@ -3,11 +3,10 @@
|
|||||||
import { untrack } from "svelte";
|
import { untrack } from "svelte";
|
||||||
import { goto } from "$app/navigation";
|
import { goto } from "$app/navigation";
|
||||||
import { page } from "$app/state";
|
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 { captureVideoThumbnail } from "#lib/client/media/thumbnail.js";
|
||||||
import { getFileDownloadState } from "#lib/client/transfer/index.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 {
|
import {
|
||||||
addFileToCategory,
|
addFileToCategory,
|
||||||
getFileContent,
|
getFileContent,
|
||||||
@@ -19,6 +18,7 @@
|
|||||||
type DataKey,
|
type DataKey,
|
||||||
type FileInfo,
|
type FileInfo,
|
||||||
} from "#lib/features/filesystem/index.js";
|
} from "#lib/features/filesystem/index.js";
|
||||||
|
import { FullscreenDiv, IconEntryButton, TopBar } from "#lib/ui/index.js";
|
||||||
import AddToCategoryBottomSheet from "./AddToCategoryBottomSheet.svelte";
|
import AddToCategoryBottomSheet from "./AddToCategoryBottomSheet.svelte";
|
||||||
import DownloadStatus from "./DownloadStatus.svelte";
|
import DownloadStatus from "./DownloadStatus.svelte";
|
||||||
import TopBarMenu from "./TopBarMenu.svelte";
|
import TopBarMenu from "./TopBarMenu.svelte";
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { BottomDiv, BottomSheet, Button, FullscreenDiv } from "#lib/components/atoms/index.js";
|
import { tryAction } from "#lib/features/errors.js";
|
||||||
import { SubCategories } from "#lib/components/molecules/index.js";
|
import { SubCategories, CategoryCreateModal } from "#lib/features/filesystem/components/index.js";
|
||||||
import { CategoryCreateModal } from "#lib/components/organisms/index.js";
|
|
||||||
import { tryAction } from "#lib/client/toast.svelte.js";
|
|
||||||
import { createCategory, useCategory } from "#lib/features/filesystem/index.js";
|
import { createCategory, useCategory } from "#lib/features/filesystem/index.js";
|
||||||
|
import { BottomDiv, BottomSheet, Button, FullscreenDiv } from "#lib/ui/index.js";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
onAddToCategoryClick: (categoryId: number) => void;
|
onAddToCategoryClick: (categoryId: number) => void;
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
import { FullscreenDiv } from "#lib/components/atoms/index.js";
|
|
||||||
import { TopBar } from "#lib/components/molecules/index.js";
|
|
||||||
import {
|
import {
|
||||||
getDownloadingFiles,
|
getDownloadingFiles,
|
||||||
clearDownloadedFiles,
|
clearDownloadedFiles,
|
||||||
type FileDownloadState,
|
type FileDownloadState,
|
||||||
} from "#lib/client/transfer/index.js";
|
} from "#lib/client/transfer/index.js";
|
||||||
import { bulkGetFileInfo, type MaybeFileInfo } from "#lib/features/filesystem/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";
|
import File from "./File.svelte";
|
||||||
|
|
||||||
let downloadingFiles: { info: MaybeFileInfo; state: FileDownloadState }[] = $state([]);
|
let downloadingFiles: { info: MaybeFileInfo; state: FileDownloadState }[] = $state([]);
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
<script lang="ts">
|
<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 { getUploadingFiles, clearUploadedFiles } from "#lib/client/transfer/index.js";
|
||||||
|
import { FullscreenDiv, TopBar } from "#lib/ui/index.js";
|
||||||
import File from "./File.svelte";
|
import File from "./File.svelte";
|
||||||
|
|
||||||
const uploadingFiles = getUploadingFiles();
|
const uploadingFiles = getUploadingFiles();
|
||||||
|
|||||||
@@ -1,17 +1,13 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
import { goto } from "$app/navigation";
|
import { goto } from "$app/navigation";
|
||||||
import {
|
import { FileThumbnailButton } from "#lib/features/filesystem/components/index.js";
|
||||||
FileThumbnailButton,
|
|
||||||
FullscreenDiv,
|
|
||||||
RowVirtualizer,
|
|
||||||
} from "#lib/components/atoms/index.js";
|
|
||||||
import { TopBar } from "#lib/components/molecules/index.js";
|
|
||||||
import {
|
import {
|
||||||
bulkGetFileInfo,
|
bulkGetFileInfo,
|
||||||
type MaybeFileInfo,
|
type MaybeFileInfo,
|
||||||
type SummarizedFileInfo,
|
type SummarizedFileInfo,
|
||||||
} from "#lib/features/filesystem/index.js";
|
} 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";
|
import { formatDate, formatDateSortable, SortBy, sortEntries } from "#lib/utils/index.js";
|
||||||
|
|
||||||
let { data } = $props();
|
let { data } = $props();
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import FileSaver from "file-saver";
|
import FileSaver from "file-saver";
|
||||||
import { goto } from "$app/navigation";
|
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 {
|
import {
|
||||||
keyring,
|
keyring,
|
||||||
registerInitialMasterKeyAndHmacSecret,
|
registerInitialMasterKeyAndHmacSecret,
|
||||||
@@ -10,7 +8,14 @@
|
|||||||
requestSessionUpgrade,
|
requestSessionUpgrade,
|
||||||
serializeClientKeys,
|
serializeClientKeys,
|
||||||
} from "#lib/client/keys/index.js";
|
} 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 BeforeContinueBottomSheet from "./BeforeContinueBottomSheet.svelte";
|
||||||
import BeforeContinueModal from "./BeforeContinueModal.svelte";
|
import BeforeContinueModal from "./BeforeContinueModal.svelte";
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<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 {
|
interface Props {
|
||||||
onRetryClick: () => void;
|
onRetryClick: () => void;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { ActionModal } from "#lib/components/molecules/index.js";
|
import { ActionModal } from "#lib/ui/index.js";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
import { goto } from "$app/navigation";
|
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 {
|
import {
|
||||||
generateClientKeys,
|
generateClientKeys,
|
||||||
generateInitialHmacSecret,
|
generateInitialHmacSecret,
|
||||||
@@ -13,8 +10,16 @@
|
|||||||
registerInitialMasterKeyAndHmacSecret,
|
registerInitialMasterKeyAndHmacSecret,
|
||||||
requestClientRegistrationAndSessionUpgrade,
|
requestClientRegistrationAndSessionUpgrade,
|
||||||
} from "#lib/client/keys/index.js";
|
} 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 { 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 { gotoStateful } from "#lib/utils/index.js";
|
||||||
import Order from "./Order.svelte";
|
import Order from "./Order.svelte";
|
||||||
|
|
||||||
|
|||||||
@@ -4,17 +4,16 @@
|
|||||||
import { untrack } from "svelte";
|
import { untrack } from "svelte";
|
||||||
import { slide } from "svelte/transition";
|
import { slide } from "svelte/transition";
|
||||||
import { goto } from "$app/navigation";
|
import { goto } from "$app/navigation";
|
||||||
import { Chip, FullscreenDiv, RowVirtualizer } from "#lib/components/atoms/index.js";
|
import { showErrorToast } from "#lib/features/errors.js";
|
||||||
import { showErrorToast } from "#lib/client/toast.svelte.js";
|
import { DirectoryEntry, FileEntry } from "#lib/features/filesystem/components/index.js";
|
||||||
import {
|
import {
|
||||||
getDirectoryInfo,
|
getDirectoryInfo,
|
||||||
type LocalCategoryInfo,
|
type LocalCategoryInfo,
|
||||||
type MaybeDirectoryInfo,
|
type MaybeDirectoryInfo,
|
||||||
} from "#lib/features/filesystem/index.js";
|
} from "#lib/features/filesystem/index.js";
|
||||||
import { search, type SearchFilter, type SearchResult } from "#lib/features/search/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 { searchString, sortEntries } from "#lib/utils/index.js";
|
||||||
import Directory from "./Directory.svelte";
|
|
||||||
import File from "./File.svelte";
|
|
||||||
import SearchBar from "./SearchBar.svelte";
|
import SearchBar from "./SearchBar.svelte";
|
||||||
import SelectCategoryBottomSheet from "./SelectCategoryBottomSheet.svelte";
|
import SelectCategoryBottomSheet from "./SelectCategoryBottomSheet.svelte";
|
||||||
|
|
||||||
@@ -236,9 +235,9 @@
|
|||||||
{#snippet item(index)}
|
{#snippet item(index)}
|
||||||
{@const info = result[index]!}
|
{@const info = result[index]!}
|
||||||
{#if info.type === "directory"}
|
{#if info.type === "directory"}
|
||||||
<Directory {info} onclick={() => goto(`/directory/${info.id}?from=search`)} />
|
<DirectoryEntry {info} onclick={() => goto(`/directory/${info.id}?from=search`)} />
|
||||||
{:else}
|
{:else}
|
||||||
<File {info} onclick={() => goto(`/file/${info.id}?from=search`)} />
|
<FileEntry {info} onclick={() => goto(`/file/${info.id}?from=search`)} />
|
||||||
{/if}
|
{/if}
|
||||||
{/snippet}
|
{/snippet}
|
||||||
</RowVirtualizer>
|
</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">
|
<script lang="ts">
|
||||||
import { BottomDiv, BottomSheet, Button, FullscreenDiv } from "#lib/components/atoms/index.js";
|
import { SubCategories } from "#lib/features/filesystem/components/index.js";
|
||||||
import { SubCategories } from "#lib/components/molecules/index.js";
|
|
||||||
import { useCategory, type LocalCategoryInfo } from "#lib/features/filesystem/index.js";
|
import { useCategory, type LocalCategoryInfo } from "#lib/features/filesystem/index.js";
|
||||||
|
import { BottomDiv, BottomSheet, Button, FullscreenDiv } from "#lib/ui/index.js";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
|
|||||||
+2
-3
@@ -1,13 +1,12 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount } from "svelte";
|
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 {
|
import {
|
||||||
getFileCacheIndex,
|
getFileCacheIndex,
|
||||||
deleteFileCache as doDeleteFileCache,
|
deleteFileCache as doDeleteFileCache,
|
||||||
} from "#lib/client/cache/index.js";
|
} 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 { 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 { formatFileSize } from "#lib/utils/index.js";
|
||||||
import File from "./File.svelte";
|
import File from "./File.svelte";
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
import { goto } from "$app/navigation";
|
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 { 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 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 { sortEntries } from "#lib/utils/index.js";
|
||||||
import File from "./File.svelte";
|
|
||||||
import {
|
import {
|
||||||
getThumbnailGenerationStatus,
|
getThumbnailGenerationStatus,
|
||||||
clearThumbnailGenerationStatuses,
|
clearThumbnailGenerationStatuses,
|
||||||
@@ -15,10 +14,19 @@
|
|||||||
type GenerationStatus,
|
type GenerationStatus,
|
||||||
} from "./service";
|
} from "./service";
|
||||||
|
|
||||||
|
import IconCamera from "~icons/material-symbols/camera";
|
||||||
import IconDelete from "~icons/material-symbols/delete";
|
import IconDelete from "~icons/material-symbols/delete";
|
||||||
|
|
||||||
let { data } = $props();
|
let { data } = $props();
|
||||||
|
|
||||||
|
const statusTexts = {
|
||||||
|
queued: "대기 중",
|
||||||
|
"generation-pending": "준비 중",
|
||||||
|
generating: "생성하는 중",
|
||||||
|
uploading: "업로드하는 중",
|
||||||
|
error: "실패",
|
||||||
|
} as const;
|
||||||
|
|
||||||
let fileInfos: MaybeFileInfo[] = $state([]);
|
let fileInfos: MaybeFileInfo[] = $state([]);
|
||||||
let files = $derived(
|
let files = $derived(
|
||||||
fileInfos
|
fileInfos
|
||||||
@@ -69,11 +77,18 @@
|
|||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
{#each files as { info, status } (info.id)}
|
{#each files as { info, status } (info.id)}
|
||||||
{#if info.exists}
|
{#if info.exists}
|
||||||
<File
|
<FileEntry
|
||||||
{info}
|
{info}
|
||||||
{status}
|
showThumbnail={false}
|
||||||
onclick={({ id }) => goto(`/file/${id}`)}
|
subtext={status && statusTexts[status]}
|
||||||
onGenerateThumbnailClick={requestThumbnailGeneration}
|
onclick={() => goto(`/file/${info.id}`)}
|
||||||
|
action={!status || status === "error"
|
||||||
|
? {
|
||||||
|
icon: IconCamera,
|
||||||
|
onclick: () => requestThumbnailGeneration(info),
|
||||||
|
class: "text-gray-800",
|
||||||
|
}
|
||||||
|
: undefined}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/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">
|
<script lang="ts">
|
||||||
import { AdaptiveDiv } from "#lib/components/atoms/index.js";
|
import { AdaptiveDiv } from "#lib/ui/index.js";
|
||||||
import BottomBar from "./BottomBar.svelte";
|
import BottomBar from "./BottomBar.svelte";
|
||||||
|
|
||||||
let { children } = $props();
|
let { children } = $props();
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { goto } from "$app/navigation";
|
import { goto } from "$app/navigation";
|
||||||
import { page } from "$app/state";
|
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 IconHome from "~icons/material-symbols/home";
|
||||||
import IconFolder from "~icons/material-symbols/folder";
|
import IconFolder from "~icons/material-symbols/folder";
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { goto } from "$app/navigation";
|
import { goto } from "$app/navigation";
|
||||||
import { CheckBox, RowVirtualizer } from "#lib/components/atoms/index.js";
|
import { tryAction } from "#lib/features/errors.js";
|
||||||
import { SubCategories, TopBar } from "#lib/components/molecules/index.js";
|
import {
|
||||||
import { CategoryCreateModal } from "#lib/components/organisms/index.js";
|
FileEntry,
|
||||||
import { tryAction } from "#lib/client/toast.svelte.js";
|
SubCategories,
|
||||||
|
CategoryCreateModal,
|
||||||
|
} from "#lib/features/filesystem/components/index.js";
|
||||||
import {
|
import {
|
||||||
createCategory,
|
createCategory,
|
||||||
deleteCategory,
|
deleteCategory,
|
||||||
@@ -13,13 +15,14 @@
|
|||||||
useCategory,
|
useCategory,
|
||||||
type LocalCategoryInfo,
|
type LocalCategoryInfo,
|
||||||
} from "#lib/features/filesystem/index.js";
|
} from "#lib/features/filesystem/index.js";
|
||||||
|
import { CheckBox, OverlayGroup, RowVirtualizer, TopBar } from "#lib/ui/index.js";
|
||||||
import { sortEntries } from "#lib/utils/index.js";
|
import { sortEntries } from "#lib/utils/index.js";
|
||||||
import CategoryDeleteModal from "./CategoryDeleteModal.svelte";
|
import CategoryDeleteModal from "./CategoryDeleteModal.svelte";
|
||||||
import CategoryMenuBottomSheet from "./CategoryMenuBottomSheet.svelte";
|
import CategoryMenuBottomSheet from "./CategoryMenuBottomSheet.svelte";
|
||||||
import CategoryRenameModal from "./CategoryRenameModal.svelte";
|
import CategoryRenameModal from "./CategoryRenameModal.svelte";
|
||||||
import File from "./File.svelte";
|
|
||||||
import { createContext } from "./service.svelte";
|
import { createContext } from "./service.svelte";
|
||||||
|
|
||||||
|
import IconClose from "~icons/material-symbols/close";
|
||||||
import IconMoreVert from "~icons/material-symbols/more-vert";
|
import IconMoreVert from "~icons/material-symbols/more-vert";
|
||||||
|
|
||||||
let { data } = $props();
|
let { data } = $props();
|
||||||
@@ -28,10 +31,9 @@
|
|||||||
const category = useCategory(() => data.id);
|
const category = useCategory(() => data.id);
|
||||||
let info = $derived(category.current);
|
let info = $derived(category.current);
|
||||||
|
|
||||||
let isCategoryCreateModalOpen = $state(false);
|
const overlays = new OverlayGroup<
|
||||||
let isCategoryMenuBottomSheetOpen = $state(false);
|
"categoryCreate" | "categoryMenu" | "categoryRename" | "categoryDelete"
|
||||||
let isCategoryRenameModalOpen = $state(false);
|
>();
|
||||||
let isCategoryDeleteModalOpen = $state(false);
|
|
||||||
|
|
||||||
let files = $derived(
|
let files = $derived(
|
||||||
sortEntries(
|
sortEntries(
|
||||||
@@ -59,11 +61,11 @@
|
|||||||
<SubCategories
|
<SubCategories
|
||||||
{info}
|
{info}
|
||||||
onSubCategoryClick={({ id }) => goto(`/category/${id}`)}
|
onSubCategoryClick={({ id }) => goto(`/category/${id}`)}
|
||||||
onSubCategoryCreateClick={() => (isCategoryCreateModalOpen = true)}
|
onSubCategoryCreateClick={() => overlays.open("categoryCreate")}
|
||||||
subCategoryCreatePosition="bottom"
|
subCategoryCreatePosition="bottom"
|
||||||
onSubCategoryMenuClick={(subCategory) => {
|
onSubCategoryMenuClick={(subCategory) => {
|
||||||
context.selectedCategory = subCategory;
|
context.selectedCategory = subCategory;
|
||||||
isCategoryMenuBottomSheetOpen = true;
|
overlays.open("categoryMenu");
|
||||||
}}
|
}}
|
||||||
subCategoryMenuIcon={IconMoreVert}
|
subCategoryMenuIcon={IconMoreVert}
|
||||||
/>
|
/>
|
||||||
@@ -89,15 +91,18 @@
|
|||||||
>
|
>
|
||||||
{#snippet item(index)}
|
{#snippet item(index)}
|
||||||
{@const { details } = files[index]!}
|
{@const { details } = files[index]!}
|
||||||
<File
|
<FileEntry
|
||||||
info={details}
|
info={details}
|
||||||
onclick={({ id }) => goto(`/file/${id}?from=category`)}
|
onclick={() => goto(`/file/${details.id}?from=category`)}
|
||||||
onRemoveClick={!details.isRecursive
|
action={!details.isRecursive
|
||||||
? ({ id }) =>
|
? {
|
||||||
tryAction(
|
icon: IconClose,
|
||||||
() => removeFileFromCategory(id, data.id as number),
|
onclick: () =>
|
||||||
"카테고리에서 파일을 제거하지 못했어요.",
|
tryAction(
|
||||||
)
|
() => removeFileFromCategory(details.id, data.id as number),
|
||||||
|
"카테고리에서 파일을 제거하지 못했어요.",
|
||||||
|
),
|
||||||
|
}
|
||||||
: undefined}
|
: undefined}
|
||||||
/>
|
/>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
@@ -112,24 +117,18 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<CategoryCreateModal
|
<CategoryCreateModal
|
||||||
bind:isOpen={isCategoryCreateModalOpen}
|
bind:isOpen={overlays.getter("categoryCreate"), overlays.setter("categoryCreate")}
|
||||||
onCreateClick={(name: string) =>
|
onCreateClick={(name: string) =>
|
||||||
tryAction(() => createCategory(data.id, name), "카테고리를 만들지 못했어요.")}
|
tryAction(() => createCategory(data.id, name), "카테고리를 만들지 못했어요.")}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<CategoryMenuBottomSheet
|
<CategoryMenuBottomSheet
|
||||||
bind:isOpen={isCategoryMenuBottomSheetOpen}
|
bind:isOpen={overlays.getter("categoryMenu"), overlays.setter("categoryMenu")}
|
||||||
onRenameClick={() => {
|
onRenameClick={() => overlays.open("categoryRename")}
|
||||||
isCategoryMenuBottomSheetOpen = false;
|
onDeleteClick={() => overlays.open("categoryDelete")}
|
||||||
isCategoryRenameModalOpen = true;
|
|
||||||
}}
|
|
||||||
onDeleteClick={() => {
|
|
||||||
isCategoryMenuBottomSheetOpen = false;
|
|
||||||
isCategoryDeleteModalOpen = true;
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
<CategoryRenameModal
|
<CategoryRenameModal
|
||||||
bind:isOpen={isCategoryRenameModalOpen}
|
bind:isOpen={overlays.getter("categoryRename"), overlays.setter("categoryRename")}
|
||||||
onRenameClick={(newName: string) =>
|
onRenameClick={(newName: string) =>
|
||||||
tryAction(
|
tryAction(
|
||||||
() => renameCategory({ ...context.selectedCategory!, parentId: data.id }, newName),
|
() => renameCategory({ ...context.selectedCategory!, parentId: data.id }, newName),
|
||||||
@@ -137,7 +136,7 @@
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<CategoryDeleteModal
|
<CategoryDeleteModal
|
||||||
bind:isOpen={isCategoryDeleteModalOpen}
|
bind:isOpen={overlays.getter("categoryDelete"), overlays.setter("categoryDelete")}
|
||||||
onDeleteClick={() =>
|
onDeleteClick={() =>
|
||||||
tryAction(
|
tryAction(
|
||||||
() => deleteCategory({ ...context.selectedCategory!, parentId: data.id }),
|
() => deleteCategory({ ...context.selectedCategory!, parentId: data.id }),
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<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 { truncateString } from "#lib/utils/index.js";
|
||||||
import { useContext } from "./service.svelte";
|
import { useContext } from "./service.svelte";
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { BottomSheet } from "#lib/components/atoms/index.js";
|
import { BottomSheet, CategoryLabel, IconEntryButton } from "#lib/ui/index.js";
|
||||||
import { CategoryLabel, IconEntryButton } from "#lib/components/molecules/index.js";
|
|
||||||
import { useContext } from "./service.svelte";
|
import { useContext } from "./service.svelte";
|
||||||
|
|
||||||
import IconEdit from "~icons/material-symbols/edit";
|
import IconEdit from "~icons/material-symbols/edit";
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { RenameModal } from "#lib/components/organisms/index.js";
|
import { RenameModal } from "#lib/ui/index.js";
|
||||||
import { useContext } from "./service.svelte";
|
import { useContext } from "./service.svelte";
|
||||||
|
|
||||||
interface Props {
|
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 { createContext as createSvelteContext } from "svelte";
|
||||||
import type { SelectedCategory } from "#lib/components/molecules/index.js";
|
import type { SelectedCategory } from "#lib/features/filesystem/components/index.js";
|
||||||
|
|
||||||
export interface SelectedFile {
|
export interface SelectedFile {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface Context {
|
||||||
|
selectedCategory?: SelectedCategory;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [getContext, setContext] = createSvelteContext<Context>();
|
||||||
|
|
||||||
export const createContext = () => {
|
export const createContext = () => {
|
||||||
const context = $state({
|
const context: Context = $state({});
|
||||||
selectedCategory: undefined as SelectedCategory | undefined,
|
return setContext(context);
|
||||||
});
|
|
||||||
return setContext("context", context);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useContext = () => {
|
export const useContext = getContext;
|
||||||
return getContext<ReturnType<typeof createContext>>("context");
|
|
||||||
};
|
|
||||||
@@ -1,10 +1,9 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { goto } from "$app/navigation";
|
import { goto } from "$app/navigation";
|
||||||
import { page } from "$app/state";
|
import { page } from "$app/state";
|
||||||
import { FloatingButton } from "#lib/components/atoms/index.js";
|
import { tryAction } from "#lib/features/errors.js";
|
||||||
import { TopBar } from "#lib/components/molecules/index.js";
|
|
||||||
import { tryAction } from "#lib/client/toast.svelte.js";
|
|
||||||
import { createDirectory, uploadFile, useDirectory } from "#lib/features/filesystem/index.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 DirectoryCreateModal from "./DirectoryCreateModal.svelte";
|
||||||
import DirectoryEntries from "./DirectoryEntries";
|
import DirectoryEntries from "./DirectoryEntries";
|
||||||
import DownloadStatusCard from "./DownloadStatusCard.svelte";
|
import DownloadStatusCard from "./DownloadStatusCard.svelte";
|
||||||
@@ -28,14 +27,12 @@
|
|||||||
let duplicatedFile: File | undefined = $state();
|
let duplicatedFile: File | undefined = $state();
|
||||||
let resolveForDuplicateFileModal: ((res: boolean) => void) | undefined = $state();
|
let resolveForDuplicateFileModal: ((res: boolean) => void) | undefined = $state();
|
||||||
|
|
||||||
let isEntryCreateBottomSheetOpen = $state(false);
|
const overlays = new OverlayGroup<
|
||||||
let isDirectoryCreateModalOpen = $state(false);
|
"entryCreate" | "directoryCreate" | "entryMenu" | "entryRename" | "entryDelete"
|
||||||
|
>();
|
||||||
|
// Shown independently as it is requested while uploading in the background
|
||||||
let isDuplicateFileModalOpen = $state(false);
|
let isDuplicateFileModalOpen = $state(false);
|
||||||
|
|
||||||
let isEntryMenuBottomSheetOpen = $state(false);
|
|
||||||
let isEntryRenameModalOpen = $state(false);
|
|
||||||
let isEntryDeleteModalOpen = $state(false);
|
|
||||||
|
|
||||||
let showParentEntry = $derived(
|
let showParentEntry = $derived(
|
||||||
["file", "search", "favorite"].includes(page.url.searchParams.get("from") ?? ""),
|
["file", "search", "favorite"].includes(page.url.searchParams.get("from") ?? ""),
|
||||||
);
|
);
|
||||||
@@ -96,7 +93,7 @@
|
|||||||
onEntryClick={({ type, id }) => goto(`/${type}/${id}`)}
|
onEntryClick={({ type, id }) => goto(`/${type}/${id}`)}
|
||||||
onEntryMenuClick={(entry) => {
|
onEntryMenuClick={(entry) => {
|
||||||
context.selectedEntry = entry;
|
context.selectedEntry = entry;
|
||||||
isEntryMenuBottomSheetOpen = true;
|
overlays.open("entryMenu");
|
||||||
}}
|
}}
|
||||||
showParentEntry={showParentEntry && data.id !== "root"}
|
showParentEntry={showParentEntry && data.id !== "root"}
|
||||||
onParentClick={() =>
|
onParentClick={() =>
|
||||||
@@ -114,23 +111,20 @@
|
|||||||
<FloatingButton
|
<FloatingButton
|
||||||
icon={IconAdd}
|
icon={IconAdd}
|
||||||
onclick={() => {
|
onclick={() => {
|
||||||
isEntryCreateBottomSheetOpen = true;
|
overlays.open("entryCreate");
|
||||||
}}
|
}}
|
||||||
class="right-4 bottom-24"
|
class="right-4 bottom-24"
|
||||||
/>
|
/>
|
||||||
<EntryCreateBottomSheet
|
<EntryCreateBottomSheet
|
||||||
bind:isOpen={isEntryCreateBottomSheetOpen}
|
bind:isOpen={overlays.getter("entryCreate"), overlays.setter("entryCreate")}
|
||||||
onDirectoryCreateClick={() => {
|
onDirectoryCreateClick={() => overlays.open("directoryCreate")}
|
||||||
isEntryCreateBottomSheetOpen = false;
|
|
||||||
isDirectoryCreateModalOpen = true;
|
|
||||||
}}
|
|
||||||
onFileUploadClick={() => {
|
onFileUploadClick={() => {
|
||||||
isEntryCreateBottomSheetOpen = false;
|
overlays.close();
|
||||||
fileInput?.click();
|
fileInput?.click();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<DirectoryCreateModal
|
<DirectoryCreateModal
|
||||||
bind:isOpen={isDirectoryCreateModalOpen}
|
bind:isOpen={overlays.getter("directoryCreate"), overlays.setter("directoryCreate")}
|
||||||
onCreateClick={(name) =>
|
onCreateClick={(name) =>
|
||||||
tryAction(() => createDirectory(data.id, name), "폴더를 만들지 못했어요.")}
|
tryAction(() => createDirectory(data.id, name), "폴더를 만들지 못했어요.")}
|
||||||
/>
|
/>
|
||||||
@@ -148,15 +142,9 @@
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<EntryMenuBottomSheet
|
<EntryMenuBottomSheet
|
||||||
bind:isOpen={isEntryMenuBottomSheetOpen}
|
bind:isOpen={overlays.getter("entryMenu"), overlays.setter("entryMenu")}
|
||||||
onRenameClick={() => {
|
onRenameClick={() => overlays.open("entryRename")}
|
||||||
isEntryMenuBottomSheetOpen = false;
|
onDeleteClick={() => overlays.open("entryDelete")}
|
||||||
isEntryRenameModalOpen = true;
|
|
||||||
}}
|
|
||||||
onDeleteClick={() => {
|
|
||||||
isEntryMenuBottomSheetOpen = false;
|
|
||||||
isEntryDeleteModalOpen = true;
|
|
||||||
}}
|
|
||||||
onFavoriteClick={async () => {
|
onFavoriteClick={async () => {
|
||||||
if (
|
if (
|
||||||
await tryAction(
|
await tryAction(
|
||||||
@@ -164,16 +152,16 @@
|
|||||||
"즐겨찾기를 변경하지 못했어요.",
|
"즐겨찾기를 변경하지 못했어요.",
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
isEntryMenuBottomSheetOpen = false;
|
overlays.close();
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<EntryRenameModal
|
<EntryRenameModal
|
||||||
bind:isOpen={isEntryRenameModalOpen}
|
bind:isOpen={overlays.getter("entryRename"), overlays.setter("entryRename")}
|
||||||
onRenameClick={(newName: string) =>
|
onRenameClick={(newName: string) =>
|
||||||
tryAction(() => renameEntry(context.selectedEntry!, newName), "이름을 바꾸지 못했어요.")}
|
tryAction(() => renameEntry(context.selectedEntry!, newName), "이름을 바꾸지 못했어요.")}
|
||||||
/>
|
/>
|
||||||
<EntryDeleteModal
|
<EntryDeleteModal
|
||||||
bind:isOpen={isEntryDeleteModalOpen}
|
bind:isOpen={overlays.getter("entryDelete"), overlays.setter("entryDelete")}
|
||||||
onDeleteClick={() => tryAction(() => deleteEntry(context.selectedEntry!), "삭제하지 못했어요.")}
|
onDeleteClick={() => tryAction(() => deleteEntry(context.selectedEntry!), "삭제하지 못했어요.")}
|
||||||
/>
|
/>
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { TextInputModal } from "#lib/components/organisms/index.js";
|
import { TextInputModal } from "#lib/ui/index.js";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
|
|||||||
@@ -1,14 +1,18 @@
|
|||||||
<script lang="ts">
|
<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 { 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 { sortEntries } from "#lib/utils/index.js";
|
||||||
import File from "./File.svelte";
|
|
||||||
import SubDirectory from "./SubDirectory.svelte";
|
|
||||||
import UploadingFile from "./UploadingFile.svelte";
|
import UploadingFile from "./UploadingFile.svelte";
|
||||||
import type { SelectedEntry } from "../service.svelte";
|
import type { SelectedEntry } from "../service.svelte";
|
||||||
|
|
||||||
|
import IconMoreVert from "~icons/material-symbols/more-vert";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
info: DirectoryInfo;
|
info: DirectoryInfo;
|
||||||
onEntryClick: (entry: SelectedEntry) => void;
|
onEntryClick: (entry: SelectedEntry) => void;
|
||||||
@@ -35,6 +39,11 @@
|
|||||||
<T extends Exclude<Entry["type"], "parent">>(type: T) =>
|
<T extends Exclude<Entry["type"], "parent">>(type: T) =>
|
||||||
(details: Extract<Entry, { type: T }>["details"]) => ({ type, name: details.name, details });
|
(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([
|
let entries = $derived([
|
||||||
...(showParentEntry ? ([{ type: "parent" }] as const) : []),
|
...(showParentEntry ? ([{ type: "parent" }] as const) : []),
|
||||||
...sortEntries(info.subDirectories.map(toEntry("directory"))),
|
...sortEntries(info.subDirectories.map(toEntry("directory"))),
|
||||||
@@ -63,13 +72,19 @@
|
|||||||
<DirectoryEntryLabel type="parent-directory" name=".." />
|
<DirectoryEntryLabel type="parent-directory" name=".." />
|
||||||
</ActionEntryButton>
|
</ActionEntryButton>
|
||||||
{:else if entry.type === "directory"}
|
{:else if entry.type === "directory"}
|
||||||
<SubDirectory
|
{@const selected = toSelectedEntry("directory", entry.details)}
|
||||||
|
<DirectoryEntry
|
||||||
info={entry.details}
|
info={entry.details}
|
||||||
onclick={onEntryClick}
|
onclick={() => onEntryClick(selected)}
|
||||||
onOpenMenuClick={onEntryMenuClick}
|
action={{ icon: IconMoreVert, onclick: () => onEntryMenuClick(selected) }}
|
||||||
/>
|
/>
|
||||||
{:else if entry.type === "file"}
|
{: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}
|
{:else}
|
||||||
<UploadingFile state={entry.details} />
|
<UploadingFile state={entry.details} />
|
||||||
{/if}
|
{/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">
|
<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 { truncateString } from "#lib/utils/index.js";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { BottomSheet } from "#lib/components/atoms/index.js";
|
import { BottomSheet, IconEntryButton } from "#lib/ui/index.js";
|
||||||
import { IconEntryButton } from "#lib/components/molecules/index.js";
|
|
||||||
|
|
||||||
import IconCreateNewFolder from "~icons/material-symbols/create-new-folder";
|
import IconCreateNewFolder from "~icons/material-symbols/create-new-folder";
|
||||||
import IconUploadFile from "~icons/material-symbols/upload-file";
|
import IconUploadFile from "~icons/material-symbols/upload-file";
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<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 { truncateString } from "#lib/utils/index.js";
|
||||||
import { useContext } from "./service.svelte";
|
import { useContext } from "./service.svelte";
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { BottomSheet } from "#lib/components/atoms/index.js";
|
import { BottomSheet, DirectoryEntryLabel, IconEntryButton } from "#lib/ui/index.js";
|
||||||
import { DirectoryEntryLabel, IconEntryButton } from "#lib/components/molecules/index.js";
|
|
||||||
import { useContext } from "./service.svelte";
|
import { useContext } from "./service.svelte";
|
||||||
|
|
||||||
import IconFavorite from "~icons/material-symbols/favorite";
|
import IconFavorite from "~icons/material-symbols/favorite";
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { RenameModal } from "#lib/components/organisms/index.js";
|
import { RenameModal } from "#lib/ui/index.js";
|
||||||
import { useContext } from "./service.svelte";
|
import { useContext } from "./service.svelte";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { getContext, setContext } from "svelte";
|
import { createContext as createSvelteContext } from "svelte";
|
||||||
import {
|
import {
|
||||||
deleteDirectory,
|
deleteDirectory,
|
||||||
deleteFile,
|
deleteFile,
|
||||||
@@ -18,16 +18,18 @@ export interface SelectedEntry {
|
|||||||
isFavorite: boolean;
|
isFavorite: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface Context {
|
||||||
|
selectedEntry?: SelectedEntry;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [getContext, setContext] = createSvelteContext<Context>();
|
||||||
|
|
||||||
export const createContext = () => {
|
export const createContext = () => {
|
||||||
const context = $state({
|
const context: Context = $state({});
|
||||||
selectedEntry: undefined as SelectedEntry | undefined,
|
return setContext(context);
|
||||||
});
|
|
||||||
return setContext("context", context);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useContext = () => {
|
export const useContext = getContext;
|
||||||
return getContext<ReturnType<typeof createContext>>("context");
|
|
||||||
};
|
|
||||||
|
|
||||||
export const renameEntry = async (entry: SelectedEntry, newName: string) => {
|
export const renameEntry = async (entry: SelectedEntry, newName: string) => {
|
||||||
if (entry.type === "directory") {
|
if (entry.type === "directory") {
|
||||||
|
|||||||
@@ -1,17 +1,16 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
import { goto } from "$app/navigation";
|
import { goto } from "$app/navigation";
|
||||||
import { RowVirtualizer } from "#lib/components/atoms/index.js";
|
import { showErrorToast, tryAction } from "#lib/features/errors.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 {
|
import {
|
||||||
getFavoriteEntries,
|
getFavoriteEntries,
|
||||||
removeFavorite,
|
removeFavorite,
|
||||||
type FavoriteEntry,
|
type FavoriteEntry,
|
||||||
} from "#lib/features/favorites/index.js";
|
} 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";
|
import IconSearch from "~icons/material-symbols/search";
|
||||||
|
|
||||||
let entries: FavoriteEntry[] = $state([]);
|
let entries: FavoriteEntry[] = $state([]);
|
||||||
@@ -68,16 +67,16 @@
|
|||||||
{#snippet item(index)}
|
{#snippet item(index)}
|
||||||
{@const entry = entries[index]!}
|
{@const entry = entries[index]!}
|
||||||
{#if entry.type === "directory"}
|
{#if entry.type === "directory"}
|
||||||
<Directory
|
<DirectoryEntry
|
||||||
info={entry.details}
|
info={entry.details}
|
||||||
onclick={() => handleClick(entry)}
|
onclick={() => handleClick(entry)}
|
||||||
onRemoveClick={() => handleRemove(entry)}
|
action={{ icon: IconClose, onclick: () => handleRemove(entry) }}
|
||||||
/>
|
/>
|
||||||
{:else}
|
{:else}
|
||||||
<File
|
<FileEntry
|
||||||
info={entry.details}
|
info={entry.details}
|
||||||
onclick={() => handleClick(entry)}
|
onclick={() => handleClick(entry)}
|
||||||
onRemoveClick={() => handleRemove(entry)}
|
action={{ icon: IconClose, onclick: () => handleRemove(entry) }}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
{/snippet}
|
{/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">
|
<script lang="ts">
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
import { goto } from "$app/navigation";
|
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 { type SummarizedFileInfo } from "#lib/features/filesystem/index.js";
|
||||||
|
import { EntryButton } from "#lib/ui/index.js";
|
||||||
import { requestFreshMediaFilesRetrieval } from "./service";
|
import { requestFreshMediaFilesRetrieval } from "./service";
|
||||||
|
|
||||||
let mediaFiles: SummarizedFileInfo[] = $state([]);
|
let mediaFiles: SummarizedFileInfo[] = $state([]);
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { goto } from "$app/navigation";
|
import { goto } from "$app/navigation";
|
||||||
import MenuEntryButton from "./MenuEntryButton.svelte";
|
import MenuEntryButton from "./MenuEntryButton.svelte";
|
||||||
import { tryAction } from "#lib/client/toast.svelte.js";
|
|
||||||
import { logout as requestLogout } from "#lib/features/auth/index.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 IconStorage from "~icons/material-symbols/storage";
|
||||||
import IconImage from "~icons/material-symbols/image";
|
import IconImage from "~icons/material-symbols/image";
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Component, Snippet } from "svelte";
|
import type { Component, Snippet } from "svelte";
|
||||||
import type { ClassValue, SvelteHTMLElements } from "svelte/elements";
|
import type { ClassValue, SvelteHTMLElements } from "svelte/elements";
|
||||||
import { IconEntryButton } from "#lib/components/molecules/index.js";
|
import { IconEntryButton } from "#lib/ui/index.js";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
children: Snippet;
|
children: Snippet;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { getDownloadingFiles, getUploadingFiles } from "#lib/client/transfer/index.js";
|
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";
|
import "../app.css";
|
||||||
|
|
||||||
let { children } = $props();
|
let { children } = $props();
|
||||||
|
|||||||
Reference in new issue
Block a user