fully implemented the custom slider for the number of columns to show
This commit is contained in:
@@ -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
@@ -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) &&
|
||||
|
||||
@@ -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";
|
||||
|
||||
Reference in New Issue
Block a user