Skip to content

Commit

Permalink
feature(web): Allow changing the bookmark grid layout
Browse files Browse the repository at this point in the history
  • Loading branch information
MohamedBassem committed Apr 13, 2024
1 parent cf0df0e commit acb1169
Show file tree
Hide file tree
Showing 20 changed files with 546 additions and 268 deletions.
16 changes: 15 additions & 1 deletion apps/web/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,14 @@ import "@hoarder/tailwind-config/globals.css";

import type { Viewport } from "next";
import React from "react";
import { cookies } from "next/headers";
import { Toaster } from "@/components/ui/toaster";
import Providers from "@/lib/providers";
import {
defaultUserLocalSettings,
parseUserLocalSettings,
USER_LOCAL_SETTINGS_COOKIE_NAME,
} from "@/lib/userLocalSettings/types";
import { getServerAuthSession } from "@/server/auth";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";

Expand Down Expand Up @@ -45,7 +51,15 @@ export default async function RootLayout({
return (
<html lang="en">
<body className={inter.className}>
<Providers session={session} clientConfig={clientConfig}>
<Providers
session={session}
clientConfig={clientConfig}
userLocalSettings={
parseUserLocalSettings(
cookies().get(USER_LOCAL_SETTINGS_COOKIE_NAME)?.value,
) ?? defaultUserLocalSettings()
}
>
{children}
<ReactQueryDevtools initialIsOpen={false} />
</Providers>
Expand Down
96 changes: 54 additions & 42 deletions apps/web/components/dashboard/bookmarks/AssetCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,48 @@
import Image from "next/image";
import { isBookmarkStillTagging } from "@/lib/bookmarkUtils";
import { api } from "@/lib/trpc";
import { cn } from "@/lib/utils";

import type { ZBookmark } from "@hoarder/trpc/types/bookmarks";
import type {
ZBookmark,
ZBookmarkTypeAsset,
} from "@hoarder/trpc/types/bookmarks";

import BookmarkActionBar from "./BookmarkActionBar";
import TagList from "./TagList";
import { BookmarkLayoutAdaptingCard } from "./BookmarkLayoutAdaptingCard";

function AssetImage({
bookmark,
className,
}: {
bookmark: ZBookmarkTypeAsset;
className?: string;
}) {
const bookmarkedAsset = bookmark.content;
switch (bookmarkedAsset.assetType) {
case "image": {
return (
<Image
alt="asset"
src={`/api/assets/${bookmarkedAsset.assetId}`}
fill={true}
className={className}
/>
);
}
case "pdf": {
return (
<iframe
title={bookmarkedAsset.assetId}
className={className}
src={`/api/assets/${bookmarkedAsset.assetId}`}
/>
);
}
default: {
const _exhaustiveCheck: never = bookmarkedAsset.assetType;
return <span />;
}
}
}

export default function AssetCard({
bookmark: initialData,
Expand All @@ -35,49 +71,25 @@ export default function AssetCard({
},
},
);
const bookmarkedAsset = bookmark.content;
if (bookmarkedAsset.type != "asset") {

if (bookmark.content.type != "asset") {
throw new Error("Unexpected bookmark type");
}

const bookmarkedAsset = { ...bookmark, content: bookmark.content };

return (
<div
className={cn(
className,
cn(
"flex h-min max-h-96 flex-col gap-y-1 overflow-hidden rounded-lg shadow-md",
),
)}
>
{bookmarkedAsset.assetType == "image" && (
<div className="relative h-56 max-h-56">
<Image
alt="asset"
src={`/api/assets/${bookmarkedAsset.assetId}`}
fill={true}
className="rounded-t-lg object-cover"
/>
<BookmarkLayoutAdaptingCard
title={bookmarkedAsset.content.fileName}
footer={null}
bookmark={bookmarkedAsset}
className={className}
wrapTags={true}
image={(_layout, className) => (
<div className="relative size-full flex-1">
<AssetImage bookmark={bookmarkedAsset} className={className} />
</div>
)}
{bookmarkedAsset.assetType == "pdf" && (
<iframe
title={bookmarkedAsset.assetId}
className="h-56 max-h-56 w-full"
src={`/api/assets/${bookmarkedAsset.assetId}`}
/>
)}
<div className="flex flex-col gap-y-1 overflow-hidden p-2">
<div className="flex h-full flex-wrap gap-1 overflow-hidden">
<TagList
bookmark={bookmark}
loading={isBookmarkStillTagging(bookmark)}
/>
</div>
<div className="flex w-full justify-between">
<div />
<BookmarkActionBar bookmark={bookmark} />
</div>
</div>
</div>
/>
);
}
30 changes: 0 additions & 30 deletions apps/web/components/dashboard/bookmarks/BookmarkCardSkeleton.tsx

This file was deleted.

131 changes: 131 additions & 0 deletions apps/web/components/dashboard/bookmarks/BookmarkLayoutAdaptingCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import type { BookmarksLayoutTypes } from "@/lib/userLocalSettings/types";
import React from "react";
import Link from "next/link";
import { isBookmarkStillTagging } from "@/lib/bookmarkUtils";
import {
bookmarkLayoutSwitch,
useBookmarkLayout,
} from "@/lib/userLocalSettings/bookmarksLayout";
import { cn } from "@/lib/utils";
import dayjs from "dayjs";

import type { ZBookmark } from "@hoarder/trpc/types/bookmarks";

import BookmarkActionBar from "./BookmarkActionBar";
import TagList from "./TagList";

interface Props {
bookmark: ZBookmark;
image: (layout: BookmarksLayoutTypes, className: string) => React.ReactNode;
title?: React.ReactNode;
content?: React.ReactNode;
footer?: React.ReactNode;
className?: string;
fitHeight?: boolean;
wrapTags: boolean;
}

function BottomRow({
footer,
bookmark,
}: {
footer?: React.ReactNode;
bookmark: ZBookmark;
}) {
return (
<div className="justify flex w-full shrink-0 justify-between text-gray-500">
<div className="flex items-center gap-2 overflow-hidden text-nowrap">
{footer && <>{footer}</>}
<Link href={`/dashboard/preview/${bookmark.id}`}>
{dayjs(bookmark.createdAt).format("MMM DD")}
</Link>
</div>
<BookmarkActionBar bookmark={bookmark} />
</div>
);
}

function ListView({
bookmark,
image,
title,
content,
footer,
className,
}: Props) {
return (
<div
className={cn(
"flex max-h-96 gap-4 overflow-hidden rounded-lg p-2 shadow-md",
className,
)}
>
<div className="flex size-32 items-center justify-center overflow-hidden">
{image("list", "object-cover rounded-lg size-32")}
</div>
<div className="flex h-full flex-1 flex-col justify-between gap-2 overflow-hidden">
<div className="flex flex-col gap-2 overflow-hidden">
{title && <div className="flex-none shrink-0 text-lg">{title}</div>}
{content && <div className="shrink-1 overflow-hidden">{content}</div>}
<div className="flex shrink-0 flex-wrap gap-1 overflow-hidden">
<TagList
bookmark={bookmark}
loading={isBookmarkStillTagging(bookmark)}
/>
</div>
</div>
<BottomRow footer={footer} bookmark={bookmark} />
</div>
</div>
);
}

function GridView({
bookmark,
image,
title,
content,
footer,
className,
wrapTags,
layout,
fitHeight = false,
}: Props & { layout: BookmarksLayoutTypes }) {
const img = image("grid", "h-56 min-h-56 w-full object-cover rounded-t-lg");

return (
<div
className={cn(
"flex flex-col overflow-hidden rounded-lg shadow-md",
className,
fitHeight && layout != "grid" ? "max-h-96" : "h-96",
)}
>
{img && <div className="h-56 w-full shrink-0 overflow-hidden">{img}</div>}
<div className="flex h-full flex-col justify-between gap-2 overflow-hidden p-2">
<div className="grow-1 flex flex-col gap-2 overflow-hidden">
{title && <div className="flex-none shrink-0 text-lg">{title}</div>}
{content && <div className="shrink-1 overflow-hidden">{content}</div>}
<div className="flex shrink-0 flex-wrap gap-1 overflow-hidden">
<TagList
className={wrapTags ? undefined : "h-full"}
bookmark={bookmark}
loading={isBookmarkStillTagging(bookmark)}
/>
</div>
</div>
<BottomRow footer={footer} bookmark={bookmark} />
</div>
</div>
);
}

export function BookmarkLayoutAdaptingCard(props: Props) {
const layout = useBookmarkLayout();

return bookmarkLayoutSwitch(layout, {
masonry: <GridView layout={layout} {...props} />,
grid: <GridView layout={layout} {...props} />,
list: <ListView {...props} />,
});
}
44 changes: 33 additions & 11 deletions apps/web/components/dashboard/bookmarks/BookmarksGrid.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { useMemo } from "react";
import { ActionButton } from "@/components/ui/action-button";
import {
bookmarkLayoutSwitch,
useBookmarkLayout,
} from "@/lib/userLocalSettings/bookmarksLayout";
import tailwindConfig from "@/tailwind.config";
import { Slot } from "@radix-ui/react-slot";
import Masonry from "react-masonry-css";
Expand Down Expand Up @@ -36,13 +40,15 @@ function renderBookmark(bookmark: ZBookmark) {
let comp;
switch (bookmark.content.type) {
case "link":
comp = <LinkCard bookmark={bookmark} />;
comp = <LinkCard bookmark={{ ...bookmark, content: bookmark.content }} />;
break;
case "text":
comp = <TextCard bookmark={bookmark} />;
comp = <TextCard bookmark={{ ...bookmark, content: bookmark.content }} />;
break;
case "asset":
comp = <AssetCard bookmark={bookmark} />;
comp = (
<AssetCard bookmark={{ ...bookmark, content: bookmark.content }} />
);
break;
}
return <BookmarkCard key={bookmark.id}>{comp}</BookmarkCard>;
Expand All @@ -61,20 +67,36 @@ export default function BookmarksGrid({
isFetchingNextPage?: boolean;
fetchNextPage?: () => void;
}) {
const layout = useBookmarkLayout();
const breakpointConfig = useMemo(() => getBreakpointConfig(), []);

if (bookmarks.length == 0 && !showEditorCard) {
return <p>No bookmarks</p>;
}

const children = [
showEditorCard && (
<BookmarkCard key={"editor"}>
<EditorCard />
</BookmarkCard>
),
...bookmarks.map((b) => renderBookmark(b)),
];
return (
<>
<Masonry className="flex gap-4" breakpointCols={breakpointConfig}>
{showEditorCard && (
<BookmarkCard>
<EditorCard />
</BookmarkCard>
)}
{bookmarks.map((b) => renderBookmark(b))}
</Masonry>
{bookmarkLayoutSwitch(layout, {
masonry: (
<Masonry className="flex gap-4" breakpointCols={breakpointConfig}>
{children}
</Masonry>
),
grid: (
<Masonry className="flex gap-4" breakpointCols={breakpointConfig}>
{children}
</Masonry>
),
list: <div className="grid grid-cols-1">{children}</div>,
})}
{hasNextPage && (
<div className="flex justify-center">
<ActionButton
Expand Down
Loading

0 comments on commit acb1169

Please sign in to comment.