diff --git a/package.json b/package.json
index 8cc76cd3e..e5d044891 100644
--- a/package.json
+++ b/package.json
@@ -27,6 +27,7 @@
"@noble/secp256k1": "^1.7.0",
"@webscopeio/react-textarea-autocomplete": "^4.9.2",
"bech32": "^2.0.0",
+ "blurhash": "^2.0.5",
"cheerio": "^1.0.0-rc.12",
"chroma-js": "^2.4.2",
"dayjs": "^1.11.9",
diff --git a/src/app.tsx b/src/app.tsx
index bc32a8c8b..a886c0342 100644
--- a/src/app.tsx
+++ b/src/app.tsx
@@ -90,6 +90,7 @@ const StreamView = lazy(() => import("./views/streams/stream"));
const SearchView = lazy(() => import("./views/search"));
const MapView = lazy(() => import("./views/map"));
+const FilesView = lazy(() => import("./views/files"));
const ChannelsHomeView = lazy(() => import("./views/channels"));
const ChannelView = lazy(() => import("./views/channels/channel"));
@@ -224,6 +225,7 @@ const router = createHashRouter([
element: ,
},
{ path: "settings", element: },
+ { path: "files", element: },
{
path: "relays",
children: [
diff --git a/src/components/blured-image.tsx b/src/components/blured-image.tsx
new file mode 100644
index 000000000..e76677c0c
--- /dev/null
+++ b/src/components/blured-image.tsx
@@ -0,0 +1,23 @@
+import { Box, Image, ImageProps, useDisclosure } from "@chakra-ui/react";
+
+export default function BlurredImage(props: ImageProps) {
+ const { isOpen, onOpen } = useDisclosure();
+ return (
+
+ {
+ e.stopPropagation();
+ e.preventDefault();
+ onOpen();
+ }
+ : undefined
+ }
+ cursor="pointer"
+ filter={isOpen ? "" : "blur(1.5rem)"}
+ {...props}
+ />
+
+ );
+}
diff --git a/src/components/blurhash-image.tsx b/src/components/blurhash-image.tsx
new file mode 100644
index 000000000..2ca970c2e
--- /dev/null
+++ b/src/components/blurhash-image.tsx
@@ -0,0 +1,29 @@
+import { Box, BoxProps } from "@chakra-ui/react";
+import { decode } from "blurhash";
+import { useEffect, useRef } from "react";
+
+export type BlurhashImageProps = {
+ blurhash: string;
+ width: number;
+ height: number;
+} & Omit;
+
+export default function BlurhashImage({ blurhash, width, height, ...props }: BlurhashImageProps) {
+ const canvasRef = useRef(null);
+
+ useEffect(() => {
+ if (!canvasRef.current) return;
+ const ctx = canvasRef.current.getContext("2d");
+ if (!ctx) return;
+
+ ctx.canvas.width = width;
+ ctx.canvas.height = height;
+
+ const imageData = ctx.createImageData(width, height);
+ const pixels = decode(blurhash, width, height);
+ imageData.data.set(pixels);
+ ctx.putImageData(imageData, 0, 0);
+ }, [blurhash, width, height]);
+
+ return ;
+}
diff --git a/src/components/icons.tsx b/src/components/icons.tsx
index 2dc14f50e..eef970b90 100644
--- a/src/components/icons.tsx
+++ b/src/components/icons.tsx
@@ -63,6 +63,7 @@ import ReverseLeft from "./icons/reverse-left";
import Pin01 from "./icons/pin-01";
import Translate01 from "./icons/translate-01";
import MessageChatSquare from "./icons/message-chat-square";
+import File01 from "./icons/file-01";
const defaultProps: IconProps = { boxSize: 4 };
@@ -232,7 +233,6 @@ export const WalletIcon = Wallet02;
export const DownloadIcon = Download01;
export const TranslateIcon = Translate01;
-
export const ChannelsIcon = MessageChatSquare;
-
export const ThreadIcon = MessageChatSquare;
+export const FileIcon = File01;
\ No newline at end of file
diff --git a/src/components/layout/nav-items.tsx b/src/components/layout/nav-items.tsx
index 5d57cd77b..c4f2e2dcf 100644
--- a/src/components/layout/nav-items.tsx
+++ b/src/components/layout/nav-items.tsx
@@ -20,6 +20,7 @@ import {
NotesIcon,
LightningIcon,
ChannelsIcon,
+ FileIcon,
} from "../icons";
import useCurrentAccount from "../../hooks/use-current-account";
import accountService from "../../services/account";
@@ -53,6 +54,7 @@ export default function NavItems() {
else if (location.pathname.startsWith("/c/")) active = "communities";
else if (location.pathname.startsWith("/goals")) active = "goals";
else if (location.pathname.startsWith("/badges")) active = "badges";
+ else if (location.pathname.startsWith("/files")) active = "files";
else if (location.pathname.startsWith("/emojis")) active = "emojis";
else if (location.pathname.startsWith("/settings")) active = "settings";
else if (location.pathname.startsWith("/tools")) active = "tools";
@@ -179,6 +181,15 @@ export default function NavItems() {
>
Lists
+ }
+ colorScheme={active === "files" ? "primary" : undefined}
+ {...buttonProps}
+ >
+ Files
+