fully implemented the custom slider for the number of columns to show

This commit is contained in:
daniel31x13
2024-09-09 23:05:57 -04:00
parent e1149c2733
commit dc9db05e75
6 changed files with 192 additions and 79 deletions
@@ -16,17 +16,10 @@ import { useRouter } from "next/router";
type Props = {
link: LinkIncludingShortenedCollectionAndTags;
collection: CollectionIncludingMembersAndLinkCount;
position?: string;
alignToTop?: boolean;
flipDropdown?: boolean;
className?: string;
};
export default function LinkActions({
link,
position,
alignToTop,
flipDropdown,
}: Props) {
export default function LinkActions({ link, className }: Props) {
const { t } = useTranslation();
const permissions = usePermissions(link.collection.id as number);
@@ -92,9 +85,7 @@ export default function LinkActions({
<>
{isPublicRoute ? (
<div
className={`absolute ${position || "top-3 right-3"} ${
alignToTop ? "" : "dropdown-end"
} z-20`}
className={`absolute ${className || "top-3 right-3"} z-20`}
tabIndex={0}
role="button"
onMouseDown={dropdownTriggerer}
@@ -107,21 +98,21 @@ export default function LinkActions({
) : (
<div
className={`dropdown dropdown-left absolute ${
position || "top-3 right-3"
} ${alignToTop ? "" : "dropdown-end"} z-20`}
className || "top-3 right-3"
} z-20`}
>
<div
tabIndex={0}
role="button"
onMouseDown={dropdownTriggerer}
className="btn btn-ghost btn-sm btn-square text-neutral"
className="btn btn-sm btn-square text-neutral"
>
<i title="More" className="bi-three-dots text-xl" />
</div>
<ul
className={`dropdown-content z-[20] menu shadow bg-base-200 border border-neutral-content rounded-box mr-1 ${
alignToTop ? "" : "translate-y-10"
}`}
className={
"dropdown-content z-[20] menu shadow bg-base-200 border border-neutral-content rounded-box mr-1"
}
>
<li>
<div
@@ -3,7 +3,7 @@ import {
CollectionIncludingMembersAndLinkCount,
LinkIncludingShortenedCollectionAndTags,
} from "@/types/global";
import { useEffect, useRef, useState } from "react";
import { useEffect, useMemo, useRef, useState } from "react";
import useLinkStore from "@/store/links";
import unescapeString from "@/lib/client/unescapeString";
import LinkActions from "@/components/LinkViews/LinkComponents/LinkActions";
@@ -11,7 +11,6 @@ import LinkDate from "@/components/LinkViews/LinkComponents/LinkDate";
import LinkCollection from "@/components/LinkViews/LinkComponents/LinkCollection";
import Image from "next/image";
import { previewAvailable } from "@/lib/shared/getArchiveValidity";
import Link from "next/link";
import LinkIcon from "./LinkIcon";
import useOnScreen from "@/hooks/useOnScreen";
import { generateLinkHref } from "@/lib/client/generateLinkHref";
@@ -24,19 +23,34 @@ import { useUser } from "@/hooks/store/user";
import { useGetLink, useLinks } from "@/hooks/store/links";
import { useRouter } from "next/router";
import useLocalSettingsStore from "@/store/localSettings";
import clsx from "clsx";
type Props = {
link: LinkIncludingShortenedCollectionAndTags;
count: number;
columns: number;
className?: string;
flipDropdown?: boolean;
editMode?: boolean;
};
export default function LinkCard({ link, flipDropdown, editMode }: Props) {
export default function LinkCard({ link, columns, editMode }: Props) {
const { t } = useTranslation();
const heightMap = {
1: "h-48",
2: "h-44",
3: "h-40",
4: "h-36",
5: "h-32",
6: "h-28",
7: "h-24",
8: "h-20",
};
const imageHeightClass = useMemo(
() => (columns ? heightMap[columns as keyof typeof heightMap] : "h-40"),
[columns]
);
const { data: collections = [] } = useCollections();
const { data: user = {} } = useUser();
@@ -134,7 +148,7 @@ export default function LinkCard({ link, flipDropdown, editMode }: Props) {
return (
<div
ref={ref}
className={`${selectedStyle} border border-solid border-neutral-content bg-base-200 shadow-md hover:shadow-none duration-100 rounded-2xl relative`}
className={`${selectedStyle} border border-solid border-neutral-content bg-base-200 shadow-md hover:shadow-none duration-100 rounded-2xl relative group`}
onClick={() =>
selectable
? handleCheckboxClick(link)
@@ -151,14 +165,16 @@ export default function LinkCard({ link, flipDropdown, editMode }: Props) {
>
{show.image && (
<div>
<div className="relative rounded-t-2xl h-40 overflow-hidden">
<div
className={`relative rounded-t-2xl ${imageHeightClass} overflow-hidden`}
>
{previewAvailable(link) ? (
<Image
src={`/api/v1/archives/${link.id}?format=${ArchivedFormat.jpeg}&preview=true&updatedAt=${link.updatedAt}`}
width={1280}
height={720}
alt=""
className="rounded-t-2xl select-none object-cover z-10 h-40 w-full shadow opacity-80 scale-105"
className={`rounded-t-2xl select-none object-cover z-10 ${imageHeightClass} w-full shadow opacity-80 scale-105`}
style={show.icon ? { filter: "blur(1px)" } : undefined}
draggable="false"
onError={(e) => {
@@ -167,9 +183,13 @@ export default function LinkCard({ link, flipDropdown, editMode }: Props) {
}}
/>
) : link.preview === "unavailable" ? (
<div className="bg-gray-50 duration-100 h-40 bg-opacity-80"></div>
<div
className={`bg-gray-50 ${imageHeightClass} bg-opacity-80`}
></div>
) : (
<div className="duration-100 h-40 bg-opacity-80 skeleton rounded-none"></div>
<div
className={`${imageHeightClass} bg-opacity-80 skeleton rounded-none`}
></div>
)}
{show.icon && (
<div className="absolute top-0 left-0 right-0 bottom-0 rounded-t-2xl flex items-center justify-center rounded-md">
@@ -184,7 +204,7 @@ export default function LinkCard({ link, flipDropdown, editMode }: Props) {
<div className="flex flex-col justify-between h-full min-h-24">
<div className="p-3 flex flex-col gap-2">
{show.name && (
<p className="truncate w-full pr-9 text-primary text-sm">
<p className="truncate w-full text-primary text-sm">
{unescapeString(link.name)}
</p>
)}
@@ -209,11 +229,14 @@ export default function LinkCard({ link, flipDropdown, editMode }: Props) {
</div>
</div>
{/* Overlay on hover */}
<div className="absolute pointer-events-none top-0 left-0 right-0 bottom-0 bg-base-100 bg-opacity-0 group-hover:bg-opacity-20 group-focus-within:opacity-20 rounded-2xl duration-100"></div>
<LinkActions
link={link}
collection={collection}
position={clsx(show.image ? "top-[10.75rem]" : "top-3", "right-3")}
flipDropdown={flipDropdown}
className={
"top-3 right-3 group-hover:opacity-100 group-focus-within:opacity-100 opacity-0 duration-100"
}
/>
</div>
);
@@ -24,15 +24,10 @@ type Props = {
link: LinkIncludingShortenedCollectionAndTags;
count: number;
className?: string;
flipDropdown?: boolean;
editMode?: boolean;
};
export default function LinkCardCompact({
link,
flipDropdown,
editMode,
}: Props) {
export default function LinkCardCompact({ link, editMode }: Props) {
const { t } = useTranslation();
const { data: collections = [] } = useCollections();
@@ -142,8 +137,7 @@ export default function LinkCardCompact({
<LinkActions
link={link}
collection={collection}
position="top-3 right-3"
flipDropdown={flipDropdown}
className="top-3 right-3"
/>
</div>
<div className="last:hidden rounded-none my-0 mx-1 border-t border-base-300 h-[1px]"></div>
@@ -3,7 +3,7 @@ import {
CollectionIncludingMembersAndLinkCount,
LinkIncludingShortenedCollectionAndTags,
} from "@/types/global";
import { useEffect, useRef, useState } from "react";
import { useEffect, useMemo, useRef, useState } from "react";
import useLinkStore from "@/store/links";
import unescapeString from "@/lib/client/unescapeString";
import LinkActions from "@/components/LinkViews/LinkComponents/LinkActions";
@@ -27,15 +27,29 @@ import clsx from "clsx";
type Props = {
link: LinkIncludingShortenedCollectionAndTags;
count: number;
className?: string;
flipDropdown?: boolean;
columns: number;
editMode?: boolean;
};
export default function LinkMasonry({ link, flipDropdown, editMode }: Props) {
export default function LinkMasonry({ link, editMode, columns }: Props) {
const { t } = useTranslation();
const heightMap = {
1: "h-48",
2: "h-44",
3: "h-40",
4: "h-36",
5: "h-32",
6: "h-28",
7: "h-24",
8: "h-20",
};
const imageHeightClass = useMemo(
() => (columns ? heightMap[columns as keyof typeof heightMap] : "h-40"),
[columns]
);
const { data: collections = [] } = useCollections();
const { data: user = {} } = useUser();
@@ -126,7 +140,7 @@ export default function LinkMasonry({ link, flipDropdown, editMode }: Props) {
return (
<div
ref={ref}
className={`${selectedStyle} border border-solid border-neutral-content bg-base-200 shadow-md hover:shadow-none duration-100 rounded-2xl relative`}
className={`${selectedStyle} border border-solid border-neutral-content bg-base-200 shadow-md hover:shadow-none duration-100 rounded-2xl relative group`}
onClick={() =>
selectable
? handleCheckboxClick(link)
@@ -150,7 +164,7 @@ export default function LinkMasonry({ link, flipDropdown, editMode }: Props) {
width={1280}
height={720}
alt=""
className="rounded-t-2xl select-none object-cover z-10 h-40 w-full shadow opacity-80 scale-105"
className={`rounded-t-2xl select-none object-cover z-10 ${imageHeightClass} w-full shadow opacity-80 scale-105`}
style={show.icon ? { filter: "blur(1px)" } : undefined}
draggable="false"
onError={(e) => {
@@ -159,7 +173,9 @@ export default function LinkMasonry({ link, flipDropdown, editMode }: Props) {
}}
/>
) : link.preview === "unavailable" ? null : (
<div className="duration-100 h-40 bg-opacity-80 skeleton rounded-none"></div>
<div
className={`duration-100 ${imageHeightClass} bg-opacity-80 skeleton rounded-none`}
></div>
)}
{show.icon && (
<div className="absolute top-0 left-0 right-0 bottom-0 rounded-t-2xl flex items-center justify-center rounded-md">
@@ -174,7 +190,7 @@ export default function LinkMasonry({ link, flipDropdown, editMode }: Props) {
<div className="p-3 flex flex-col gap-2 h-full min-h-14">
{show.name && (
<p className="hyphens-auto w-full pr-9 text-primary text-sm">
<p className="hyphens-auto w-full text-primary text-sm">
{unescapeString(link.name)}
</p>
)}
@@ -182,12 +198,7 @@ export default function LinkMasonry({ link, flipDropdown, editMode }: Props) {
{show.link && <LinkTypeBadge link={link} />}
{show.description && link.description && (
<p
className={clsx(
"hyphens-auto text-sm w-full",
((!show.name && !show.link) || !link.name) && "pr-9"
)}
>
<p className={clsx("hyphens-auto text-sm w-full")}>
{unescapeString(link.description)}
</p>
)}
@@ -226,15 +237,14 @@ export default function LinkMasonry({ link, flipDropdown, editMode }: Props) {
)}
</div>
{/* Overlay on hover */}
<div className="absolute pointer-events-none top-0 left-0 right-0 bottom-0 bg-base-100 bg-opacity-0 group-hover:bg-opacity-20 group-focus-within:opacity-20 rounded-2xl duration-100"></div>
<LinkActions
link={link}
collection={collection}
position={
previewAvailable(link) && show.image
? "top-[10.75rem] right-3"
: "top-3 right-3"
className={
"top-3 right-3 group-hover:opacity-100 group-focus-within:opacity-100 opacity-0 duration-100"
}
flipDropdown={flipDropdown}
/>
</div>
);
+114 -16
View File
@@ -3,7 +3,7 @@ import {
LinkIncludingShortenedCollectionAndTags,
ViewMode,
} from "@/types/global";
import { useEffect } from "react";
import { useEffect, useState } from "react";
import { useInView } from "react-intersection-observer";
import LinkMasonry from "@/components/LinkViews/LinkComponents/LinkMasonry";
import Masonry from "react-masonry-css";
@@ -11,6 +11,7 @@ import resolveConfig from "tailwindcss/resolveConfig";
import tailwindConfig from "../../tailwind.config.js";
import { useMemo } from "react";
import LinkList from "@/components/LinkViews/LinkComponents/LinkList";
import useLocalSettingsStore from "@/store/localSettings";
export function CardView({
links,
@@ -27,16 +28,68 @@ export function CardView({
hasNextPage?: boolean;
placeHolderRef?: any;
}) {
const settings = useLocalSettingsStore((state) => state.settings);
const gridMap = {
1: "grid-cols-1",
2: "grid-cols-2",
3: "grid-cols-3",
4: "grid-cols-4",
5: "grid-cols-5",
6: "grid-cols-6",
7: "grid-cols-7",
8: "grid-cols-8",
};
const getColumnCount = () => {
const width = window.innerWidth;
if (width >= 1901) return 5;
if (width >= 1501) return 4;
if (width >= 881) return 3;
if (width >= 551) return 2;
return 1;
};
const [columnCount, setColumnCount] = useState(
settings.columns || getColumnCount()
);
const gridColClass = useMemo(
() => gridMap[columnCount as keyof typeof gridMap],
[columnCount]
);
useEffect(() => {
const handleResize = () => {
if (settings.columns === 0) {
// Only recalculate if zustandColumns is zero
setColumnCount(getColumnCount());
}
};
if (settings.columns === 0) {
window.addEventListener("resize", handleResize);
}
setColumnCount(settings.columns || getColumnCount());
return () => {
if (settings.columns === 0) {
window.removeEventListener("resize", handleResize);
}
};
}, [settings.columns]);
return (
<div className="grid min-[1901px]:grid-cols-5 min-[1501px]:grid-cols-4 min-[881px]:grid-cols-3 min-[551px]:grid-cols-2 grid-cols-1 gap-5 pb-5">
<div className={`${gridColClass} grid gap-5 pb-5`}>
{links?.map((e, i) => {
return (
<LinkCard
key={i}
link={e}
count={i}
flipDropdown={i === links.length - 1}
editMode={editMode}
columns={columnCount}
/>
);
})}
@@ -76,6 +129,58 @@ export function MasonryView({
hasNextPage?: boolean;
placeHolderRef?: any;
}) {
const settings = useLocalSettingsStore((state) => state.settings);
const gridMap = {
1: "grid-cols-1",
2: "grid-cols-2",
3: "grid-cols-3",
4: "grid-cols-4",
5: "grid-cols-5",
6: "grid-cols-6",
7: "grid-cols-7",
8: "grid-cols-8",
};
const getColumnCount = () => {
const width = window.innerWidth;
if (width >= 1901) return 5;
if (width >= 1501) return 4;
if (width >= 881) return 3;
if (width >= 551) return 2;
return 1;
};
const [columnCount, setColumnCount] = useState(
settings.columns || getColumnCount()
);
const gridColClass = useMemo(
() => gridMap[columnCount as keyof typeof gridMap],
[columnCount]
);
useEffect(() => {
const handleResize = () => {
if (settings.columns === 0) {
// Only recalculate if zustandColumns is zero
setColumnCount(getColumnCount());
}
};
if (settings.columns === 0) {
window.addEventListener("resize", handleResize);
}
setColumnCount(settings.columns || getColumnCount());
return () => {
if (settings.columns === 0) {
window.removeEventListener("resize", handleResize);
}
};
}, [settings.columns]);
const fullConfig = resolveConfig(tailwindConfig as any);
const breakpointColumnsObj = useMemo(() => {
@@ -90,18 +195,19 @@ export function MasonryView({
return (
<Masonry
breakpointCols={breakpointColumnsObj}
breakpointCols={
settings.columns === 0 ? breakpointColumnsObj : columnCount
}
columnClassName="flex flex-col gap-5 !w-full"
className="grid min-[1901px]:grid-cols-5 min-[1501px]:grid-cols-4 min-[881px]:grid-cols-3 min-[551px]:grid-cols-2 grid-cols-1 gap-5 pb-5"
className={`${gridColClass} grid gap-5 pb-5`}
>
{links?.map((e, i) => {
return (
<LinkMasonry
key={i}
link={e}
count={i}
flipDropdown={i === links.length - 1}
editMode={editMode}
columns={columnCount}
/>
);
})}
@@ -144,15 +250,7 @@ export function ListView({
return (
<div className="flex flex-col">
{links?.map((e, i) => {
return (
<LinkList
key={i}
link={e}
count={i}
flipDropdown={i === links.length - 1}
editMode={editMode}
/>
);
return <LinkList key={i} link={e} count={i} editMode={editMode} />;
})}
{(hasNextPage || isLoading) &&
+1 -4
View File
@@ -3,7 +3,6 @@ import { readabilityAvailable } from "@/lib/shared/getArchiveValidity";
import isValidUrl from "@/lib/shared/isValidUrl";
import {
ArchivedFormat,
CollectionIncludingMembersAndLinkCount,
LinkIncludingShortenedCollectionAndTags,
} from "@/types/global";
import ColorThief, { RGBColor } from "colorthief";
@@ -11,10 +10,8 @@ import DOMPurify from "dompurify";
import Image from "next/image";
import Link from "next/link";
import { useRouter } from "next/router";
import React, { useEffect, useMemo, useState } from "react";
import LinkActions from "./LinkViews/LinkComponents/LinkActions";
import React, { useEffect, useState } from "react";
import { useTranslation } from "next-i18next";
import { useCollections } from "@/hooks/store/collections";
import { useGetLink } from "@/hooks/store/links";
import { IconWeight } from "@phosphor-icons/react";
import Icon from "./Icon";