diff --git a/apps/web/app/api/assets/route.ts b/apps/web/app/api/assets/route.ts index 0e52ff93..603aa159 100644 --- a/apps/web/app/api/assets/route.ts +++ b/apps/web/app/api/assets/route.ts @@ -3,11 +3,8 @@ import { TRPCError } from "@trpc/server"; import type { ZUploadResponse } from "@hoarder/shared/types/uploads"; import { assets, AssetTypes } from "@hoarder/db/schema"; -import { - newAssetId, - saveAsset, - SUPPORTED_UPLOAD_ASSET_TYPES, -} from "@hoarder/shared/assetdb"; +import { newAssetId, saveAsset } from "@hoarder/shared/assetdb"; +import { SUPPORTED_UPLOAD_ASSET_TYPES } from "@hoarder/shared/assetTypes"; import serverConfig from "@hoarder/shared/config"; const MAX_UPLOAD_SIZE_BYTES = serverConfig.maxAssetSizeMb * 1024 * 1024; diff --git a/apps/web/components/dashboard/UploadDropzone.tsx b/apps/web/components/dashboard/UploadDropzone.tsx index 335ac72a..461418e3 100644 --- a/apps/web/components/dashboard/UploadDropzone.tsx +++ b/apps/web/components/dashboard/UploadDropzone.tsx @@ -81,11 +81,7 @@ function useUploadAssets({ }; } -export default function UploadDropzone({ - children, -}: { - children: React.ReactNode; -}) { +export function useFileUploader() { const [numUploading, setNumUploading] = useState(0); const [numUploaded, setNumUploaded] = useState(0); const uploadAssets = useUploadAssets({ @@ -102,6 +98,23 @@ export default function UploadDropzone({ }, }); + return { + numUploading, + setNumUploading, + numUploaded, + setNumUploaded, + uploadAssets, + }; +} + +export default function UploadDropzone({ + children, +}: { + children: React.ReactNode; +}) { + const { numUploading, setNumUploading, numUploaded, uploadAssets } = + useFileUploader(); + const [isDragging, setDragging] = useState(false); const onDrop = (acceptedFiles: File[]) => { uploadAssets(acceptedFiles); diff --git a/apps/web/components/dashboard/bookmarks/EditorCard.tsx b/apps/web/components/dashboard/bookmarks/EditorCard.tsx index d1489b09..2cbccee2 100644 --- a/apps/web/components/dashboard/bookmarks/EditorCard.tsx +++ b/apps/web/components/dashboard/bookmarks/EditorCard.tsx @@ -15,13 +15,15 @@ import { } from "@/lib/userLocalSettings/bookmarksLayout"; import { cn, getOS } from "@/lib/utils"; import { zodResolver } from "@hookform/resolvers/zod"; +import { FilePlus } from "lucide-react"; import { useForm } from "react-hook-form"; import { z } from "zod"; import { useCreateBookmarkWithPostHook } from "@hoarder/shared-react/hooks/bookmarks"; +import { SUPPORTED_UPLOAD_ASSET_TYPES } from "@hoarder/shared/assetTypes"; import { BookmarkTypes } from "@hoarder/shared/types/bookmarks"; -import { useUploadAsset } from "../UploadDropzone"; +import { useFileUploader, useUploadAsset } from "../UploadDropzone"; function useFocusOnKeyPress(inputRef: React.RefObject) { useEffect(() => { @@ -167,6 +169,16 @@ export default function EditorCard({ className }: { className?: string }) { } }; + const uploadFile = useRef(null); + + const { numUploading, setNumUploading, numUploaded, uploadAssets } = + useFileUploader(); + + const handleFileUpload = (acceptedFiles: File[]) => { + uploadAssets(acceptedFiles); + setNumUploading(acceptedFiles.length); + }; + const OS = getOS(); return ( @@ -219,13 +231,48 @@ export default function EditorCard({ className }: { className?: string }) { /> - - {form.formState.dirtyFields.text - ? demoMode - ? "Submissions are disabled" - : `Save (${OS === "macos" ? "⌘" : "Ctrl"} + Enter)` - : "Save"} - +
+ + {form.formState.dirtyFields.text + ? demoMode + ? "Submissions are disabled" + : `Save (${OS === "macos" ? "⌘" : "Ctrl"} + Enter)` + : "Save"} + + +
+ {numUploading > 0 && ( + + {numUploaded} / {numUploading} + + )} + + { + if (e.target.files) { + handleFileUpload(Array.from(e.target.files)); + } + }} + > + uploadFile.current?.click()} + > + + +
+
{multiUrlImportState && ( = new Set([ + ASSET_TYPES.IMAGE_JPEG, + ASSET_TYPES.IMAGE_PNG, + ASSET_TYPES.IMAGE_WEBP, +]); + +export const FILE_ASSET_TYPES: Set = new Set([ + ASSET_TYPES.APPLICATION_PDF, +]); + +// The assets that we allow the users to upload +export const SUPPORTED_UPLOAD_ASSET_TYPES: Set = new Set([ + ...IMAGE_ASSET_TYPES, + ...FILE_ASSET_TYPES, +]); + +// The assets that we support saving in the asset db +export const SUPPORTED_ASSET_TYPES: Set = new Set([ + ...SUPPORTED_UPLOAD_ASSET_TYPES, + ASSET_TYPES.TEXT_HTML, +]); diff --git a/packages/shared/assetdb.ts b/packages/shared/assetdb.ts index 64413e9f..0c01db1b 100644 --- a/packages/shared/assetdb.ts +++ b/packages/shared/assetdb.ts @@ -3,36 +3,11 @@ import * as path from "path"; import { Glob } from "glob"; import { z } from "zod"; +import { SUPPORTED_ASSET_TYPES } from "./assetTypes"; import serverConfig from "./config"; const ROOT_PATH = path.join(serverConfig.dataDir, "assets"); -export const enum ASSET_TYPES { - IMAGE_JPEG = "image/jpeg", - IMAGE_PNG = "image/png", - IMAGE_WEBP = "image/webp", - APPLICATION_PDF = "application/pdf", - TEXT_HTML = "text/html", -} - -export const IMAGE_ASSET_TYPES: Set = new Set([ - ASSET_TYPES.IMAGE_JPEG, - ASSET_TYPES.IMAGE_PNG, - ASSET_TYPES.IMAGE_WEBP, -]); - -// The assets that we allow the users to upload -export const SUPPORTED_UPLOAD_ASSET_TYPES: Set = new Set([ - ...IMAGE_ASSET_TYPES, - ASSET_TYPES.APPLICATION_PDF, -]); - -// The assets that we support saving in the asset db -export const SUPPORTED_ASSET_TYPES: Set = new Set([ - ...SUPPORTED_UPLOAD_ASSET_TYPES, - ASSET_TYPES.TEXT_HTML, -]); - function getAssetDir(userId: string, assetId: string) { return path.join(ROOT_PATH, userId, assetId); }