From 2893d3caf22b7e4188cd41b2217c33a9c6d39197 Mon Sep 17 00:00:00 2001 From: daniel31x13 Date: Sun, 18 Aug 2024 16:52:08 -0400 Subject: [PATCH 01/21] minor improvement --- pages/links/[id].tsx | 10 ++++++---- pages/public/links/[id].tsx | 12 +++++++----- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/pages/links/[id].tsx b/pages/links/[id].tsx index 9f2aaeca..55f56485 100644 --- a/pages/links/[id].tsx +++ b/pages/links/[id].tsx @@ -19,10 +19,12 @@ const Index = () => { return (
{getLink.data ? ( - +
+ +
) : (
diff --git a/pages/public/links/[id].tsx b/pages/public/links/[id].tsx index 9f2aaeca..8f3ef136 100644 --- a/pages/public/links/[id].tsx +++ b/pages/public/links/[id].tsx @@ -17,12 +17,14 @@ const Index = () => { }, []); return ( -
+
{getLink.data ? ( - +
+ +
) : (
From dc388ebba550e9343f89965cd8dcdfcc102baecb Mon Sep 17 00:00:00 2001 From: daniel31x13 Date: Mon, 19 Aug 2024 18:14:09 -0400 Subject: [PATCH 02/21] improved iconPicker component + other improvements --- components/Icon.tsx | 16 +++ components/IconPicker.tsx | 104 ++++++++++++++---- .../LinkViews/LinkComponents/LinkActions.tsx | 40 +++---- .../ModalContent/EditCollectionModal.tsx | 40 +++++-- components/ModalContent/LinkDetailModal.tsx | 33 +++--- .../ModalContent/NewCollectionModal.tsx | 4 +- components/ModalContent/NewLinkModal.tsx | 3 + components/ModalContent/UploadFileModal.tsx | 3 + components/Popover.tsx | 21 ++++ components/ReadableView.tsx | 2 +- pages/collections/[id].tsx | 2 +- .../migration.sql | 8 ++ .../migration.sql | 3 + prisma/schema.prisma | 7 +- styles/globals.css | 8 ++ 15 files changed, 222 insertions(+), 72 deletions(-) create mode 100644 components/Icon.tsx create mode 100644 components/Popover.tsx create mode 100644 prisma/migrations/20240819003838_add_icon_related_fields_to_link_and_collection_model/migration.sql create mode 100644 prisma/migrations/20240819005010_remove_collection_model_default_color/migration.sql diff --git a/components/Icon.tsx b/components/Icon.tsx new file mode 100644 index 00000000..178b9e66 --- /dev/null +++ b/components/Icon.tsx @@ -0,0 +1,16 @@ +import React, { forwardRef } from "react"; +import * as Icons from "@phosphor-icons/react"; + +type Props = { + icon: string; +} & Icons.IconProps; + +const Icon = forwardRef(({ icon, ...rest }) => { + const IconComponent: any = Icons[icon as keyof typeof Icons]; + + if (!IconComponent) { + return <>; + } else return ; +}); + +export default Icon; diff --git a/components/IconPicker.tsx b/components/IconPicker.tsx index 994a5af0..f90dc1fb 100644 --- a/components/IconPicker.tsx +++ b/components/IconPicker.tsx @@ -2,6 +2,9 @@ import { icons } from "@/lib/client/icons"; import React, { useMemo, useState } from "react"; import Fuse from "fuse.js"; import TextInput from "./TextInput"; +import Popover from "./Popover"; +import { HexColorPicker } from "react-colorful"; +import Icon from "./Icon"; const fuse = new Fuse(icons, { keys: [{ name: "name", weight: 4 }, "tags", "categories"], @@ -9,9 +12,29 @@ const fuse = new Fuse(icons, { useExtendedSearch: true, }); -type Props = {}; +type Props = { + onClose: Function; + alignment?: "left" | "right" | "bottom" | "top"; + color: string; + setColor: Function; + iconName: string; + setIconName: Function; + weight: "light" | "regular" | "bold" | "fill" | "duotone"; + setWeight: Function; + className?: string; +}; -const IconPicker = (props: Props) => { +const IconPicker = ({ + onClose, + alignment, + color, + setColor, + iconName, + setIconName, + weight, + setWeight, + className, +}: Props) => { const [query, setQuery] = useState(""); const filteredQueryResultsSelector = useMemo(() => { @@ -22,24 +45,67 @@ const IconPicker = (props: Props) => { }, [query]); return ( -
- setQuery(e.target.value)} - /> -
- {filteredQueryResultsSelector.map((icon) => { - const IconComponent = icon.Icon; - return ( -
console.log(icon.name)}> - -
- ); - })} + +
+
+
+ + + +
+ setColor(e)} /> +
+ + setQuery(e.target.value)} + /> + +
+ {filteredQueryResultsSelector.map((icon) => { + const IconComponent = icon.Icon; + return ( +
setIconName(icon.pascal_name)} + className={`cursor-pointer btn p-1 box-border ${ + icon.pascal_name === iconName + ? "outline outline-1 outline-primary" + : "" + }`} + > + +
+ ); + })} +
-
+ ); }; diff --git a/components/LinkViews/LinkComponents/LinkActions.tsx b/components/LinkViews/LinkComponents/LinkActions.tsx index f93f9690..0e04491d 100644 --- a/components/LinkViews/LinkComponents/LinkActions.tsx +++ b/components/LinkViews/LinkComponents/LinkActions.tsx @@ -105,24 +105,23 @@ export default function LinkActions({ alignToTop ? "" : "translate-y-10" }`} > - {permissions === true || - (permissions?.canUpdate && ( -
  • -
    { - (document?.activeElement as HTMLElement)?.blur(); - pinLink(); - }} - className="whitespace-nowrap" - > - {link?.pinnedBy && link.pinnedBy[0] - ? t("unpin") - : t("pin_to_dashboard")} -
    -
  • - ))} + {(permissions === true || permissions?.canUpdate) && ( +
  • +
    { + (document?.activeElement as HTMLElement)?.blur(); + pinLink(); + }} + className="whitespace-nowrap" + > + {link?.pinnedBy && link.pinnedBy[0] + ? t("unpin") + : t("pin_to_dashboard")} +
    +
  • + )}
  • { (document?.activeElement as HTMLElement)?.blur(); + console.log(e.shiftKey); e.shiftKey - ? async () => { + ? (async () => { const load = toast.loading(t("deleting")); await deleteLink.mutateAsync(link.id as number, { @@ -188,7 +188,7 @@ export default function LinkActions({ } }, }); - } + })() : setDeleteLinkModal(true); }} className="whitespace-nowrap" diff --git a/components/ModalContent/EditCollectionModal.tsx b/components/ModalContent/EditCollectionModal.tsx index f8dfd20f..76833e74 100644 --- a/components/ModalContent/EditCollectionModal.tsx +++ b/components/ModalContent/EditCollectionModal.tsx @@ -6,6 +6,8 @@ import Modal from "../Modal"; import { useTranslation } from "next-i18next"; import { useUpdateCollection } from "@/hooks/store/collections"; import toast from "react-hot-toast"; +import IconPicker from "../IconPicker"; +import Icon from "../Icon"; type Props = { onClose: Function; @@ -20,6 +22,7 @@ export default function EditCollectionModal({ const [collection, setCollection] = useState(activeCollection); + const [iconPicker, setIconPicker] = useState(false); const [submitLoader, setSubmitLoader] = useState(false); const updateCollection = useUpdateCollection(); @@ -71,17 +74,32 @@ export default function EditCollectionModal({

    {t("color")}

    - - setCollection({ ...collection, color }) - } - /> -
    - +
    + setIconPicker(true)} + /> + {iconPicker && ( + setIconPicker(false)} + className="top-20" + color={collection.color as string} + setColor={(color: string) => + setCollection({ ...collection, color }) + } + weight={collection.iconWeight as any} + setWeight={(iconWeight: string) => + setCollection({ ...collection, iconWeight }) + } + iconName={collection.icon as string} + setIconName={(icon: string) => + setCollection({ ...collection, icon }) + } + /> + )}
    diff --git a/components/ModalContent/LinkDetailModal.tsx b/components/ModalContent/LinkDetailModal.tsx index e9f00868..ad7d36cb 100644 --- a/components/ModalContent/LinkDetailModal.tsx +++ b/components/ModalContent/LinkDetailModal.tsx @@ -120,25 +120,24 @@ export default function LinkDetailModal({ onClose, onEdit, link }: Props) {
    - {permissions === true || - (permissions?.canUpdate && ( - <> -
    -
    + {(permissions === true || permissions?.canUpdate) && ( + <> +
    +
    -
    -
    { - onEdit(); - onClose(); - }} - > - {t("edit_link")} -
    +
    +
    { + onEdit(); + onClose(); + }} + > + {t("edit_link")}
    - - ))} +
    + + )}
    ); diff --git a/components/ModalContent/NewCollectionModal.tsx b/components/ModalContent/NewCollectionModal.tsx index df9d9bb6..32cb5f99 100644 --- a/components/ModalContent/NewCollectionModal.tsx +++ b/components/ModalContent/NewCollectionModal.tsx @@ -88,7 +88,7 @@ export default function NewCollectionModal({ onClose, parent }: Props) {

    {t("color")}

    setCollection({ ...collection, color }) } @@ -96,7 +96,7 @@ export default function NewCollectionModal({ onClose, parent }: Props) {
    { + return ( + onClose()} + className={`absolute z-50 ${className || ""}`} + > + {children} + + ); +}; + +export default Popover; diff --git a/components/ReadableView.tsx b/components/ReadableView.tsx index 18477a0a..11954b63 100644 --- a/components/ReadableView.tsx +++ b/components/ReadableView.tsx @@ -205,7 +205,7 @@ export default function ReadableView({ link }: Props) { >

    diff --git a/prisma/migrations/20240819003838_add_icon_related_fields_to_link_and_collection_model/migration.sql b/prisma/migrations/20240819003838_add_icon_related_fields_to_link_and_collection_model/migration.sql new file mode 100644 index 00000000..e208342b --- /dev/null +++ b/prisma/migrations/20240819003838_add_icon_related_fields_to_link_and_collection_model/migration.sql @@ -0,0 +1,8 @@ +-- AlterTable +ALTER TABLE "Collection" ADD COLUMN "icon" TEXT, +ADD COLUMN "iconWeight" TEXT; + +-- AlterTable +ALTER TABLE "Link" ADD COLUMN "color" TEXT, +ADD COLUMN "icon" TEXT, +ADD COLUMN "iconWeight" TEXT; diff --git a/prisma/migrations/20240819005010_remove_collection_model_default_color/migration.sql b/prisma/migrations/20240819005010_remove_collection_model_default_color/migration.sql new file mode 100644 index 00000000..199e1043 --- /dev/null +++ b/prisma/migrations/20240819005010_remove_collection_model_default_color/migration.sql @@ -0,0 +1,3 @@ +-- AlterTable +ALTER TABLE "Collection" ALTER COLUMN "color" DROP NOT NULL, +ALTER COLUMN "color" DROP DEFAULT; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 71b5b103..664dec5d 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -94,7 +94,9 @@ model Collection { id Int @id @default(autoincrement()) name String description String @default("") - color String @default("#0ea5e9") + icon String? + iconWeight String? + color String? parentId Int? parent Collection? @relation("SubCollections", fields: [parentId], references: [id]) subCollections Collection[] @relation("SubCollections") @@ -133,6 +135,9 @@ model Link { collection Collection @relation(fields: [collectionId], references: [id]) collectionId Int tags Tag[] + icon String? + iconWeight String? + color String? url String? textContent String? preview String? diff --git a/styles/globals.css b/styles/globals.css index 1f97b6e2..022c160e 100644 --- a/styles/globals.css +++ b/styles/globals.css @@ -36,6 +36,14 @@ scrollbar-width: none; } +.hide-color-picker { + opacity: 0; + display: block; + width: 32px; + height: 32px; + border: none; +} + .hyphens { hyphens: auto; } From ae2324ecd352acfc215a6222727bacc34c80f444 Mon Sep 17 00:00:00 2001 From: daniel31x13 Date: Tue, 20 Aug 2024 16:59:01 -0400 Subject: [PATCH 03/21] progressed icon picker component --- components/IconPicker.tsx | 168 ++++++++++-------- .../ModalContent/EditCollectionModal.tsx | 73 +++----- pages/_app.tsx | 7 +- styles/globals.css | 9 +- 4 files changed, 130 insertions(+), 127 deletions(-) diff --git a/components/IconPicker.tsx b/components/IconPicker.tsx index f90dc1fb..513da2c2 100644 --- a/components/IconPicker.tsx +++ b/components/IconPicker.tsx @@ -1,31 +1,26 @@ import { icons } from "@/lib/client/icons"; -import React, { useMemo, useState } from "react"; +import React, { useMemo, useState, lazy, Suspense } from "react"; import Fuse from "fuse.js"; import TextInput from "./TextInput"; import Popover from "./Popover"; import { HexColorPicker } from "react-colorful"; +import { useTranslation } from "next-i18next"; import Icon from "./Icon"; - -const fuse = new Fuse(icons, { - keys: [{ name: "name", weight: 4 }, "tags", "categories"], - threshold: 0.2, - useExtendedSearch: true, -}); +import { IconWeight } from "@phosphor-icons/react"; type Props = { - onClose: Function; - alignment?: "left" | "right" | "bottom" | "top"; + alignment?: "left" | "right"; color: string; setColor: Function; - iconName: string; + iconName?: string; setIconName: Function; - weight: "light" | "regular" | "bold" | "fill" | "duotone"; + weight: "light" | "regular" | "bold" | "fill" | "duotone" | "thin"; setWeight: Function; + reset: Function; className?: string; }; const IconPicker = ({ - onClose, alignment, color, setColor, @@ -34,8 +29,17 @@ const IconPicker = ({ weight, setWeight, className, + reset, }: Props) => { + const fuse = new Fuse(icons, { + keys: [{ name: "name", weight: 4 }, "tags", "categories"], + threshold: 0.2, + useExtendedSearch: true, + }); + + const { t } = useTranslation(); const [query, setQuery] = useState(""); + const [iconPicker, setIconPicker] = useState(false); const filteredQueryResultsSelector = useMemo(() => { if (!query) { @@ -45,67 +49,87 @@ const IconPicker = ({ }, [query]); return ( - -

    -
    -
    - - - -
    - setColor(e)} /> -
    - - setQuery(e.target.value)} - /> - -
    - {filteredQueryResultsSelector.map((icon) => { - const IconComponent = icon.Icon; - return ( -
    setIconName(icon.pascal_name)} - className={`cursor-pointer btn p-1 box-border ${ - icon.pascal_name === iconName - ? "outline outline-1 outline-primary" - : "" - }`} - > - -
    - ); - })} -
    +
    +
    setIconPicker(!iconPicker)} + className="btn btn-square w-20 h-20" + > + {iconName ? ( + + ) : ( + + )}
    - + {iconPicker && ( + setIconPicker(false)} + className={ + className + + " fade-in bg-base-200 border border-neutral-content p-2 h-44 w-[22.5rem] rounded-lg backdrop-blur-sm bg-opacity-90 top-20 left-0 lg:-translate-x-1/3" + } + > +
    +
    +
    } + > + {t("reset")} +
    + + setColor(e)} /> +
    + +
    + setQuery(e.target.value)} + /> + +
    + {filteredQueryResultsSelector.map((icon) => { + const IconComponent = icon.Icon; + return ( +
    setIconName(icon.pascal_name)} + className={`cursor-pointer btn p-1 box-border bg-base-100 border-none aspect-square ${ + icon.pascal_name === iconName + ? "outline outline-1 outline-primary" + : "" + }`} + > + +
    + ); + })} +
    +
    +
    +
    + )} +
    ); }; diff --git a/components/ModalContent/EditCollectionModal.tsx b/components/ModalContent/EditCollectionModal.tsx index 76833e74..20a6cdd5 100644 --- a/components/ModalContent/EditCollectionModal.tsx +++ b/components/ModalContent/EditCollectionModal.tsx @@ -1,6 +1,5 @@ import React, { useState } from "react"; import TextInput from "@/components/TextInput"; -import { HexColorPicker } from "react-colorful"; import { CollectionIncludingMembersAndLinkCount } from "@/types/global"; import Modal from "../Modal"; import { useTranslation } from "next-i18next"; @@ -8,6 +7,7 @@ import { useUpdateCollection } from "@/hooks/store/collections"; import toast from "react-hot-toast"; import IconPicker from "../IconPicker"; import Icon from "../Icon"; +import { IconWeight } from "@phosphor-icons/react"; type Props = { onClose: Function; @@ -22,7 +22,6 @@ export default function EditCollectionModal({ const [collection, setCollection] = useState(activeCollection); - const [iconPicker, setIconPicker] = useState(false); const [submitLoader, setSubmitLoader] = useState(false); const updateCollection = useUpdateCollection(); @@ -59,10 +58,32 @@ export default function EditCollectionModal({
    -
    -
    -

    {t("name")}

    -
    +
    +
    + + setCollection({ ...collection, color }) + } + weight={(collection.iconWeight || "regular") as IconWeight} + setWeight={(iconWeight: string) => + setCollection({ ...collection, iconWeight }) + } + iconName={collection.icon as string} + setIconName={(icon: string) => + setCollection({ ...collection, icon }) + } + reset={() => + setCollection({ + ...collection, + color: "#0ea5e9", + icon: "", + iconWeight: "", + }) + } + /> +
    +

    {t("name")}

    -
    -

    {t("color")}

    -
    -
    - setIconPicker(true)} - /> - {iconPicker && ( - setIconPicker(false)} - className="top-20" - color={collection.color as string} - setColor={(color: string) => - setCollection({ ...collection, color }) - } - weight={collection.iconWeight as any} - setWeight={(iconWeight: string) => - setCollection({ ...collection, iconWeight }) - } - iconName={collection.icon as string} - setIconName={(icon: string) => - setCollection({ ...collection, icon }) - } - /> - )} -
    - setCollection({ ...collection, color: "#0ea5e9" }) - } - > - {t("reset")} -
    -
    -
    -
    diff --git a/pages/_app.tsx b/pages/_app.tsx index dc6603af..c99d26a3 100644 --- a/pages/_app.tsx +++ b/pages/_app.tsx @@ -88,13 +88,10 @@ function App({ {icon} {message} {t.type !== "loading" && ( - + >
    )}
    )} diff --git a/styles/globals.css b/styles/globals.css index 022c160e..28a01054 100644 --- a/styles/globals.css +++ b/styles/globals.css @@ -144,15 +144,16 @@ /* For react-colorful */ .color-picker .react-colorful { - width: 100%; - height: 7.5rem; + height: 7rem; + width: 7rem; } .color-picker .react-colorful__hue { height: 1rem; } .color-picker .react-colorful__pointer { - width: 1.3rem; - height: 1.3rem; + width: 1rem; + height: 1rem; + border-width: 1px; } /* For the Link banner */ From 6df2e44213ce028026d9b0c1266999e79bc51daf Mon Sep 17 00:00:00 2001 From: daniel31x13 Date: Tue, 20 Aug 2024 18:11:20 -0400 Subject: [PATCH 04/21] added translation to icon picker component + other fixes and improvements --- components/IconGrid.tsx | 45 +++++++++++++++++++++++++++ components/IconPicker.tsx | 57 +++++++++++------------------------ public/locales/en/common.json | 9 +++++- 3 files changed, 70 insertions(+), 41 deletions(-) create mode 100644 components/IconGrid.tsx diff --git a/components/IconGrid.tsx b/components/IconGrid.tsx new file mode 100644 index 00000000..edb2362d --- /dev/null +++ b/components/IconGrid.tsx @@ -0,0 +1,45 @@ +import { icons } from "@/lib/client/icons"; +import Fuse from "fuse.js"; +import { useMemo } from "react"; + +const fuse = new Fuse(icons, { + keys: [{ name: "name", weight: 4 }, "tags", "categories"], + threshold: 0.2, + useExtendedSearch: true, +}); + +type Props = { + query: string; + color: string; + weight: "light" | "regular" | "bold" | "fill" | "duotone" | "thin"; + iconName?: string; + setIconName: Function; +}; + +const IconGrid = ({ query, color, weight, iconName, setIconName }: Props) => { + const filteredQueryResultsSelector = useMemo(() => { + if (!query) { + return icons; + } + return fuse.search(query).map((result) => result.item); + }, [query]); + + return filteredQueryResultsSelector.map((icon) => { + const IconComponent = icon.Icon; + return ( +
    setIconName(icon.pascal_name)} + className={`cursor-pointer btn p-1 box-border bg-base-100 border-none w-full ${ + icon.pascal_name === iconName + ? "outline outline-1 outline-primary" + : "" + }`} + > + +
    + ); + }); +}; + +export default IconGrid; diff --git a/components/IconPicker.tsx b/components/IconPicker.tsx index 513da2c2..a7122623 100644 --- a/components/IconPicker.tsx +++ b/components/IconPicker.tsx @@ -1,12 +1,11 @@ -import { icons } from "@/lib/client/icons"; -import React, { useMemo, useState, lazy, Suspense } from "react"; -import Fuse from "fuse.js"; +import React, { useState } from "react"; import TextInput from "./TextInput"; import Popover from "./Popover"; import { HexColorPicker } from "react-colorful"; import { useTranslation } from "next-i18next"; import Icon from "./Icon"; import { IconWeight } from "@phosphor-icons/react"; +import IconGrid from "./IconGrid"; type Props = { alignment?: "left" | "right"; @@ -31,23 +30,10 @@ const IconPicker = ({ className, reset, }: Props) => { - const fuse = new Fuse(icons, { - keys: [{ name: "name", weight: 4 }, "tags", "categories"], - threshold: 0.2, - useExtendedSearch: true, - }); - const { t } = useTranslation(); const [query, setQuery] = useState(""); const [iconPicker, setIconPicker] = useState(false); - const filteredQueryResultsSelector = useMemo(() => { - if (!query) { - return icons; - } - return fuse.search(query).map((result) => result.item); - }, [query]); - return (
    setWeight(e.target.value)} > - - - - - - + + + + + + setColor(e)} />
    -
    +
    setQuery(e.target.value)} />
    - {filteredQueryResultsSelector.map((icon) => { - const IconComponent = icon.Icon; - return ( -
    setIconName(icon.pascal_name)} - className={`cursor-pointer btn p-1 box-border bg-base-100 border-none aspect-square ${ - icon.pascal_name === iconName - ? "outline outline-1 outline-primary" - : "" - }`} - > - -
    - ); - })} +
    diff --git a/public/locales/en/common.json b/public/locales/en/common.json index c2e9a82b..61658069 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -372,5 +372,12 @@ "demo_desc": "This is only a demo instance of Linkwarden and uploads are disabled.", "demo_desc_2": "If you want to try out the full version, you can sign up for a free trial at:", "demo_button": "Login as demo user", - "notes": "Notes" + "notes": "Notes", + "regular": "Regular", + "thin": "Thin", + "bold": "Bold", + "fill": "Fill", + "duotone": "Duotone", + "light_icon": "Light", + "search": "Search" } \ No newline at end of file From bf1a6efd2e20887241c1174a1cca4538b71e179b Mon Sep 17 00:00:00 2001 From: daniel31x13 Date: Tue, 20 Aug 2024 19:25:35 -0400 Subject: [PATCH 05/21] custom icons fully implemented for collections --- components/CollectionListing.tsx | 27 +++++++-- components/LinkDetails.tsx | 19 ++++-- .../LinkComponents/LinkCollection.tsx | 19 ++++-- .../ModalContent/EditCollectionModal.tsx | 1 - .../ModalContent/NewCollectionModal.tsx | 58 +++++++++---------- components/ReadableView.tsx | 21 +++++-- .../collectionId/updateCollectionById.ts | 4 +- .../controllers/collections/postCollection.ts | 2 + pages/collections/[id].tsx | 21 +++++-- .../migration.sql | 9 +++ prisma/schema.prisma | 2 +- 11 files changed, 127 insertions(+), 56 deletions(-) create mode 100644 prisma/migrations/20240820230618_add_default_value_for_collection_color/migration.sql diff --git a/components/CollectionListing.tsx b/components/CollectionListing.tsx index f3a722ea..1533c3b8 100644 --- a/components/CollectionListing.tsx +++ b/components/CollectionListing.tsx @@ -17,6 +17,8 @@ import toast from "react-hot-toast"; import { useTranslation } from "next-i18next"; import { useCollections, useUpdateCollection } from "@/hooks/store/collections"; import { useUpdateUser, useUser } from "@/hooks/store/user"; +import Icon from "./Icon"; +import { IconWeight } from "@phosphor-icons/react"; interface ExtendedTreeItem extends TreeItem { data: Collection; @@ -256,7 +258,7 @@ const renderItem = ( : "hover:bg-neutral/20" } duration-100 flex gap-1 items-center pr-2 pl-1 rounded-md`} > - {Icon(item as ExtendedTreeItem, onExpand, onCollapse)} + {Dropdown(item as ExtendedTreeItem, onExpand, onCollapse)} - + {collection.icon ? ( + + ) : ( + + )} +

    {collection.name}

    {collection.isPublic && ( @@ -288,7 +301,7 @@ const renderItem = ( ); }; -const Icon = ( +const Dropdown = ( item: ExtendedTreeItem, onExpand: (id: ItemId) => void, onCollapse: (id: ItemId) => void @@ -332,6 +345,8 @@ const buildTreeFromCollections = ( name: collection.name, description: collection.description, color: collection.color, + icon: collection.icon, + iconWeight: collection.iconWeight, isPublic: collection.isPublic, ownerId: collection.ownerId, createdAt: collection.createdAt, diff --git a/components/LinkDetails.tsx b/components/LinkDetails.tsx index 0f237ca2..0b7ca149 100644 --- a/components/LinkDetails.tsx +++ b/components/LinkDetails.tsx @@ -20,6 +20,8 @@ import { useGetLink } from "@/hooks/store/links"; import LinkIcon from "./LinkViews/LinkComponents/LinkIcon"; import CopyButton from "./CopyButton"; import { useRouter } from "next/router"; +import Icon from "./Icon"; +import { IconWeight } from "@phosphor-icons/react"; type Props = { className?: string; @@ -163,10 +165,19 @@ export default function LinkDetails({ className, link }: Props) { >

    {link.collection.name}

    - + {link.collection.icon ? ( + + ) : ( + + )}
    diff --git a/components/LinkViews/LinkComponents/LinkCollection.tsx b/components/LinkViews/LinkComponents/LinkCollection.tsx index f6e508f8..45c17252 100644 --- a/components/LinkViews/LinkComponents/LinkCollection.tsx +++ b/components/LinkViews/LinkComponents/LinkCollection.tsx @@ -1,7 +1,9 @@ +import Icon from "@/components/Icon"; import { CollectionIncludingMembersAndLinkCount, LinkIncludingShortenedCollectionAndTags, } from "@/types/global"; +import { IconWeight } from "@phosphor-icons/react"; import Link from "next/link"; import React from "react"; @@ -22,10 +24,19 @@ export default function LinkCollection({ className="flex items-center gap-1 max-w-full w-fit hover:opacity-70 duration-100 select-none" title={collection?.name} > - + {link.collection.icon ? ( + + ) : ( + + )}

    {collection?.name}

    diff --git a/components/ModalContent/EditCollectionModal.tsx b/components/ModalContent/EditCollectionModal.tsx index 20a6cdd5..e5f81daf 100644 --- a/components/ModalContent/EditCollectionModal.tsx +++ b/components/ModalContent/EditCollectionModal.tsx @@ -6,7 +6,6 @@ import { useTranslation } from "next-i18next"; import { useUpdateCollection } from "@/hooks/store/collections"; import toast from "react-hot-toast"; import IconPicker from "../IconPicker"; -import Icon from "../Icon"; import { IconWeight } from "@phosphor-icons/react"; type Props = { diff --git a/components/ModalContent/NewCollectionModal.tsx b/components/ModalContent/NewCollectionModal.tsx index 32cb5f99..292a8d00 100644 --- a/components/ModalContent/NewCollectionModal.tsx +++ b/components/ModalContent/NewCollectionModal.tsx @@ -1,12 +1,13 @@ import React, { useEffect, useState } from "react"; import TextInput from "@/components/TextInput"; -import { HexColorPicker } from "react-colorful"; import { Collection } from "@prisma/client"; import Modal from "../Modal"; import { CollectionIncludingMembersAndLinkCount } from "@/types/global"; import { useTranslation } from "next-i18next"; import { useCreateCollection } from "@/hooks/store/collections"; import toast from "react-hot-toast"; +import IconPicker from "../IconPicker"; +import { IconWeight } from "@phosphor-icons/react"; type Props = { onClose: Function; @@ -72,10 +73,32 @@ export default function NewCollectionModal({ onClose, parent }: Props) {
    -
    -
    -

    {t("name")}

    -
    +
    +
    + + setCollection({ ...collection, color }) + } + weight={(collection.iconWeight || "regular") as IconWeight} + setWeight={(iconWeight: string) => + setCollection({ ...collection, iconWeight }) + } + iconName={collection.icon as string} + setIconName={(icon: string) => + setCollection({ ...collection, icon }) + } + reset={() => + setCollection({ + ...collection, + color: "#0ea5e9", + icon: "", + iconWeight: "", + }) + } + /> +
    +

    {t("name")}

    -
    -

    {t("color")}

    -
    - - setCollection({ ...collection, color }) - } - /> -
    - -
    - setCollection({ ...collection, color: "#0ea5e9" }) - } - > - {t("reset")} -
    -
    -
    -
    diff --git a/components/ReadableView.tsx b/components/ReadableView.tsx index 11954b63..b352489c 100644 --- a/components/ReadableView.tsx +++ b/components/ReadableView.tsx @@ -16,6 +16,8 @@ import LinkActions from "./LinkViews/LinkComponents/LinkActions"; 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"; type LinkContent = { title: string; @@ -203,10 +205,21 @@ export default function ReadableView({ link }: Props) { href={`/collections/${link?.collection.id}`} className="flex items-center gap-1 cursor-pointer hover:opacity-60 duration-100 mr-2 z-10" > - + {link.collection.icon ? ( + + ) : ( + + )}

    - + {activeCollection.icon ? ( + + ) : ( + + )}

    {activeCollection?.name} diff --git a/prisma/migrations/20240820230618_add_default_value_for_collection_color/migration.sql b/prisma/migrations/20240820230618_add_default_value_for_collection_color/migration.sql new file mode 100644 index 00000000..f634b68e --- /dev/null +++ b/prisma/migrations/20240820230618_add_default_value_for_collection_color/migration.sql @@ -0,0 +1,9 @@ +/* + Warnings: + + - Made the column `color` on table `Collection` required. This step will fail if there are existing NULL values in that column. + +*/ +-- AlterTable +ALTER TABLE "Collection" ALTER COLUMN "color" SET NOT NULL, +ALTER COLUMN "color" SET DEFAULT '#0ea5e9'; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 664dec5d..922480f4 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -96,7 +96,7 @@ model Collection { description String @default("") icon String? iconWeight String? - color String? + color String @default("#0ea5e9") parentId Int? parent Collection? @relation("SubCollections", fields: [parentId], references: [id]) subCollections Collection[] @relation("SubCollections") From fae9e95fa9962abf80e90585c2706cda58d03da2 Mon Sep 17 00:00:00 2001 From: daniel31x13 Date: Sat, 24 Aug 2024 15:50:29 -0400 Subject: [PATCH 06/21] added custom icons for links --- components/IconPicker.tsx | 9 ++- components/LinkDetails.tsx | 4 +- .../LinkViews/LinkComponents/LinkIcon.tsx | 66 +++++++++---------- .../LinkViews/LinkComponents/LinkList.tsx | 2 +- components/ModalContent/EditLinkModal.tsx | 25 +++++++ .../links/linkId/updateLinkById.ts | 3 + public/locales/en/common.json | 3 +- 7 files changed, 72 insertions(+), 40 deletions(-) diff --git a/components/IconPicker.tsx b/components/IconPicker.tsx index a7122623..f5611377 100644 --- a/components/IconPicker.tsx +++ b/components/IconPicker.tsx @@ -8,13 +8,14 @@ import { IconWeight } from "@phosphor-icons/react"; import IconGrid from "./IconGrid"; type Props = { - alignment?: "left" | "right"; + alignment?: string; color: string; setColor: Function; iconName?: string; setIconName: Function; weight: "light" | "regular" | "bold" | "fill" | "duotone" | "thin"; setWeight: Function; + hideDefaultIcon?: boolean; reset: Function; className?: string; }; @@ -27,6 +28,7 @@ const IconPicker = ({ setIconName, weight, setWeight, + hideDefaultIcon, className, reset, }: Props) => { @@ -47,6 +49,8 @@ const IconPicker = ({ weight={(weight || "regular") as IconWeight} color={color || "#0ea5e9"} /> + ) : !iconName && hideDefaultIcon ? ( +

    {t("set_custom_icon")}

    ) : ( setIconPicker(false)} className={ className + - " fade-in bg-base-200 border border-neutral-content p-2 h-44 w-[22.5rem] rounded-lg backdrop-blur-sm bg-opacity-90 top-20 left-0 lg:-translate-x-1/3" + " fade-in bg-base-200 border border-neutral-content p-2 h-44 w-[22.5rem] rounded-lg backdrop-blur-sm bg-opacity-90 " + + (alignment || " lg:-translate-x-1/3 top-20 left-0 ") } >
    diff --git a/components/LinkDetails.tsx b/components/LinkDetails.tsx index 0b7ca149..84aabe9b 100644 --- a/components/LinkDetails.tsx +++ b/components/LinkDetails.tsx @@ -127,7 +127,9 @@ export default function LinkDetails({ className, link }: Props) { return (
    - +
    + +
    {link.name &&

    {link.name}

    } diff --git a/components/LinkViews/LinkComponents/LinkIcon.tsx b/components/LinkViews/LinkComponents/LinkIcon.tsx index e60c23a6..2ebc5301 100644 --- a/components/LinkViews/LinkComponents/LinkIcon.tsx +++ b/components/LinkViews/LinkComponents/LinkIcon.tsx @@ -2,34 +2,24 @@ import { LinkIncludingShortenedCollectionAndTags } from "@/types/global"; import Image from "next/image"; import isValidUrl from "@/lib/shared/isValidUrl"; import React from "react"; +import Icon from "@/components/Icon"; +import { IconWeight } from "@phosphor-icons/react"; +import clsx from "clsx"; export default function LinkIcon({ link, className, - size, + hideBackground, }: { link: LinkIncludingShortenedCollectionAndTags; className?: string; - size?: "small" | "medium"; + hideBackground?: boolean; }) { - let iconClasses: string = - "bg-white shadow rounded-md border-[2px] flex item-center justify-center border-white select-none z-10 " + - (className || ""); - - let dimension; - - switch (size) { - case "small": - dimension = " w-8 h-8"; - break; - case "medium": - dimension = " w-12 h-12"; - break; - default: - size = "medium"; - dimension = " w-12 h-12"; - break; - } + let iconClasses: string = clsx( + "rounded-md flex item-center justify-center select-none z-10 w-12 h-12", + !hideBackground && "bg-white backdrop-blur-lg bg-opacity-50 p-1", + className + ); const url = isValidUrl(link.url || "") && link.url ? new URL(link.url) : undefined; @@ -38,14 +28,22 @@ export default function LinkIcon({ return ( <> - {link.type === "url" && url ? ( + {link.icon ? ( + + ) : link.type === "url" && url ? ( showFavicon ? ( { setShowFavicon(false); @@ -53,22 +51,22 @@ export default function LinkIcon({ /> ) : ( ) ) : link.type === "pdf" ? ( ) : link.type === "image" ? ( ) : // : link.type === "monolith" ? ( // { return ( -
    +
    ); }; + +// `text-black aspect-square text-4xl ${iconClasses}` diff --git a/components/LinkViews/LinkComponents/LinkList.tsx b/components/LinkViews/LinkComponents/LinkList.tsx index 31a214bb..2eddc054 100644 --- a/components/LinkViews/LinkComponents/LinkList.tsx +++ b/components/LinkViews/LinkComponents/LinkList.tsx @@ -111,7 +111,7 @@ export default function LinkCardCompact({ } >
    - +
    diff --git a/components/ModalContent/EditLinkModal.tsx b/components/ModalContent/EditLinkModal.tsx index 07911f47..fa338709 100644 --- a/components/ModalContent/EditLinkModal.tsx +++ b/components/ModalContent/EditLinkModal.tsx @@ -9,6 +9,8 @@ import Modal from "../Modal"; import { useTranslation } from "next-i18next"; import { useUpdateLink } from "@/hooks/store/links"; import toast from "react-hot-toast"; +import IconPicker from "../IconPicker"; +import { IconWeight } from "@phosphor-icons/react"; type Props = { onClose: Function; @@ -138,6 +140,29 @@ export default function EditLinkModal({ onClose, activeLink }: Props) { className="resize-none w-full rounded-md p-2 border-neutral-content bg-base-200 focus:border-sky-300 dark:focus:border-sky-600 border-solid border outline-none duration-100" />
    + +
    + setLink({ ...link, color })} + weight={(link.iconWeight || "regular") as IconWeight} + setWeight={(iconWeight: string) => + setLink({ ...link, iconWeight }) + } + iconName={link.icon as string} + setIconName={(icon: string) => setLink({ ...link, icon })} + reset={() => + setLink({ + ...link, + color: "", + icon: "", + iconWeight: "", + }) + } + alignment="-top-10 translate-x-20" + /> +
    diff --git a/lib/api/controllers/links/linkId/updateLinkById.ts b/lib/api/controllers/links/linkId/updateLinkById.ts index 306696ae..53daa876 100644 --- a/lib/api/controllers/links/linkId/updateLinkById.ts +++ b/lib/api/controllers/links/linkId/updateLinkById.ts @@ -96,6 +96,9 @@ export default async function updateLinkById( data: { name: data.name, description: data.description, + icon: data.icon, + iconWeight: data.iconWeight, + color: data.color, collection: { connect: { id: data.collection.id, diff --git a/public/locales/en/common.json b/public/locales/en/common.json index 61658069..9a9cc869 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -379,5 +379,6 @@ "fill": "Fill", "duotone": "Duotone", "light_icon": "Light", - "search": "Search" + "search": "Search", + "set_custom_icon": "Set Custom Icon" } \ No newline at end of file From f368c2aa81c4700e056fbfb9772edb71c6f19e24 Mon Sep 17 00:00:00 2001 From: daniel31x13 Date: Mon, 26 Aug 2024 16:11:02 -0400 Subject: [PATCH 07/21] less padding for list view --- components/LinkViews/LinkComponents/LinkIcon.tsx | 14 +++----------- components/LinkViews/LinkComponents/LinkList.tsx | 13 ++++--------- components/LinkViews/Links.tsx | 14 +++++++------- components/ToggleDarkMode.tsx | 6 ++++-- pages/public/links/[id].tsx | 4 +--- 5 files changed, 19 insertions(+), 32 deletions(-) diff --git a/components/LinkViews/LinkComponents/LinkIcon.tsx b/components/LinkViews/LinkComponents/LinkIcon.tsx index 2ebc5301..c7c42ddc 100644 --- a/components/LinkViews/LinkComponents/LinkIcon.tsx +++ b/components/LinkViews/LinkComponents/LinkIcon.tsx @@ -16,8 +16,8 @@ export default function LinkIcon({ hideBackground?: boolean; }) { let iconClasses: string = clsx( - "rounded-md flex item-center justify-center select-none z-10 w-12 h-12", - !hideBackground && "bg-white backdrop-blur-lg bg-opacity-50 p-1", + "rounded flex item-center justify-center select-none z-10 w-12 h-12", + !hideBackground && "rounded-md bg-white backdrop-blur-lg bg-opacity-50 p-1", className ); @@ -50,23 +50,17 @@ export default function LinkIcon({ }} /> ) : ( - + ) ) : link.type === "pdf" ? ( ) : link.type === "image" ? ( ) : // : link.type === "monolith" ? ( // { return (
    diff --git a/components/LinkViews/LinkComponents/LinkList.tsx b/components/LinkViews/LinkComponents/LinkList.tsx index 2eddc054..d4e32c7c 100644 --- a/components/LinkViews/LinkComponents/LinkList.tsx +++ b/components/LinkViews/LinkComponents/LinkList.tsx @@ -93,9 +93,9 @@ export default function LinkCardCompact({ return ( <>
    selectable ? handleCheckboxClick(link) @@ -143,12 +143,7 @@ export default function LinkCardCompact({ flipDropdown={flipDropdown} />
    -
    +
    ); } diff --git a/components/LinkViews/Links.tsx b/components/LinkViews/Links.tsx index 511c1d2c..c8de1e5d 100644 --- a/components/LinkViews/Links.tsx +++ b/components/LinkViews/Links.tsx @@ -142,7 +142,7 @@ export function ListView({ placeHolderRef?: any; }) { return ( -
    +
    {links?.map((e, i) => { return ( -
    -
    -
    -
    -
    +
    +
    +
    +
    +
    ); diff --git a/components/ToggleDarkMode.tsx b/components/ToggleDarkMode.tsx index 28631b98..6a9a5dda 100644 --- a/components/ToggleDarkMode.tsx +++ b/components/ToggleDarkMode.tsx @@ -1,12 +1,14 @@ import useLocalSettingsStore from "@/store/localSettings"; import { useEffect, useState, ChangeEvent } from "react"; import { useTranslation } from "next-i18next"; +import clsx from "clsx"; type Props = { className?: string; + align?: "left" | "right"; }; -export default function ToggleDarkMode({ className }: Props) { +export default function ToggleDarkMode({ className, align }: Props) { const { t } = useTranslation(); const { settings, updateSettings } = useLocalSettingsStore(); @@ -26,7 +28,7 @@ export default function ToggleDarkMode({ className }: Props) { return (
    { const router = useRouter(); const { id } = router.query; - useState; - const getLink = useGetLink(); useEffect(() => { From 642374c2e5f768098ae8c209ad378d31ff532189 Mon Sep 17 00:00:00 2001 From: daniel31x13 Date: Mon, 26 Aug 2024 16:22:59 -0400 Subject: [PATCH 08/21] remove commented code --- components/ViewDropdown.tsx | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/components/ViewDropdown.tsx b/components/ViewDropdown.tsx index 6eba3fc8..2333970b 100644 --- a/components/ViewDropdown.tsx +++ b/components/ViewDropdown.tsx @@ -56,17 +56,6 @@ export default function ViewDropdown({ viewMode, setViewMode }: Props) { > - - {/* */}
    ); } From 9ae9c7c81ab348c32eb80dba4647a93c0e4a12a3 Mon Sep 17 00:00:00 2001 From: daniel31x13 Date: Mon, 26 Aug 2024 18:47:10 -0400 Subject: [PATCH 09/21] refactored view dropdown --- .../LinkViews/LinkComponents/LinkCard.tsx | 13 +- .../LinkViews/LinkComponents/LinkMasonry.tsx | 8 +- components/ViewDropdown.tsx | 133 +++++++++++++----- lib/client/icons.ts | 6 +- public/locales/en/common.json | 7 +- store/localSettings.ts | 106 ++++++++++---- 6 files changed, 191 insertions(+), 82 deletions(-) diff --git a/components/LinkViews/LinkComponents/LinkCard.tsx b/components/LinkViews/LinkComponents/LinkCard.tsx index f1e32dfe..285437a5 100644 --- a/components/LinkViews/LinkComponents/LinkCard.tsx +++ b/components/LinkViews/LinkComponents/LinkCard.tsx @@ -23,6 +23,7 @@ import { useCollections } from "@/hooks/store/collections"; import { useUser } from "@/hooks/store/user"; import { useGetLink, useLinks } from "@/hooks/store/links"; import { useRouter } from "next/router"; +import useLocalSettingsStore from "@/store/localSettings"; type Props = { link: LinkIncludingShortenedCollectionAndTags; @@ -41,6 +42,10 @@ export default function LinkCard({ link, flipDropdown, editMode }: Props) { const { setSelectedLinks, selectedLinks } = useLinkStore(); + const { + settings: { show }, + } = useLocalSettingsStore(); + const { data: { data: links = [] }, } = useLinks(); @@ -166,11 +171,9 @@ export default function LinkCard({ link, flipDropdown, editMode }: Props) { ) : (
    )} - {link.type !== "image" && ( -
    - -
    - )} +
    + +

    diff --git a/components/LinkViews/LinkComponents/LinkMasonry.tsx b/components/LinkViews/LinkComponents/LinkMasonry.tsx index 4dd70f18..b63019af 100644 --- a/components/LinkViews/LinkComponents/LinkMasonry.tsx +++ b/components/LinkViews/LinkComponents/LinkMasonry.tsx @@ -155,11 +155,9 @@ export default function LinkMasonry({ link, flipDropdown, editMode }: Props) { ) : link.preview === "unavailable" ? null : (
    )} - {link.type !== "image" && ( -
    - -
    - )} +
    + +
    {link.preview !== "unavailable" && ( diff --git a/components/ViewDropdown.tsx b/components/ViewDropdown.tsx index 2333970b..a9450650 100644 --- a/components/ViewDropdown.tsx +++ b/components/ViewDropdown.tsx @@ -1,7 +1,8 @@ import React, { Dispatch, SetStateAction, useEffect } from "react"; import useLocalSettingsStore from "@/store/localSettings"; - import { ViewMode } from "@/types/global"; +import { dropdownTriggerer } from "@/lib/client/utils"; +import { useTranslation } from "next-i18next"; type Props = { viewMode: ViewMode; @@ -9,53 +10,111 @@ type Props = { }; export default function ViewDropdown({ viewMode, setViewMode }: Props) { - const { updateSettings } = useLocalSettingsStore(); + const { settings, updateSettings } = useLocalSettingsStore((state) => state); + const { t } = useTranslation(); const onChangeViewMode = ( e: React.MouseEvent, - viewMode: ViewMode + mode: ViewMode ) => { - setViewMode(viewMode); + setViewMode(mode); + }; + + const toggleShowSetting = (setting: keyof typeof settings.show) => { + const newShowSettings = { + ...settings.show, + [setting]: !settings.show[setting], + }; + updateSettings({ show: newShowSettings }); }; useEffect(() => { updateSettings({ viewMode }); - }, [viewMode]); + }, [viewMode, updateSettings]); return ( -
    - - - - - + {viewMode === ViewMode.Card ? ( + + ) : viewMode === ViewMode.Masonry ? ( + + ) : ( + + )} +
    +
      +

      {t("view")}

      +
      + + + +
      +

      {t("show")}

      + {Object.entries(settings.show) + .filter((e) => + settings.viewMode === ViewMode.List // Hide tags and image checkbox in list view + ? e[0] !== "tags" && e[0] !== "image" + : settings.viewMode === ViewMode.Card // Hide tags checkbox in card view + ? e[0] !== "tags" + : true + ) + .map(([key, value]) => ( +
    • + +
    • + ))} +
    ); } diff --git a/lib/client/icons.ts b/lib/client/icons.ts index 2083bd00..e133df5a 100644 --- a/lib/client/icons.ts +++ b/lib/client/icons.ts @@ -11,8 +11,8 @@ export const icons: ReadonlyArray = iconData.map((entry) => ({ Icon: Icons[entry.pascal_name as keyof typeof Icons] as Icons.Icon, })); -if (process.env.NODE_ENV === "development") { - console.log(`${icons.length} icons`); -} +// if (process.env.NODE_ENV === "development") { +// console.log(`${icons.length} icons`); +// } export const iconCount = Intl.NumberFormat("en-US").format(icons.length * 6); diff --git a/public/locales/en/common.json b/public/locales/en/common.json index 9a9cc869..e5141481 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -380,5 +380,10 @@ "duotone": "Duotone", "light_icon": "Light", "search": "Search", - "set_custom_icon": "Set Custom Icon" + "set_custom_icon": "Set Custom Icon", + "view": "View", + "show": "Show", + "image": "Image", + "icon": "Icon", + "date": "Date" } \ No newline at end of file diff --git a/store/localSettings.ts b/store/localSettings.ts index 864fa280..cd8e6ca3 100644 --- a/store/localSettings.ts +++ b/store/localSettings.ts @@ -2,14 +2,24 @@ import { Sort } from "@/types/global"; import { create } from "zustand"; type LocalSettings = { - theme?: string; - viewMode?: string; + theme: string; + viewMode: string; + show: { + link: boolean; + title: boolean; + description: boolean; + image: boolean; + tags: boolean; + icon: boolean; + collection: boolean; + date: boolean; + }; sortBy?: Sort; }; type LocalSettingsStore = { settings: LocalSettings; - updateSettings: (settings: LocalSettings) => void; + updateSettings: (settings: Partial) => void; setSettings: () => void; }; @@ -17,50 +27,84 @@ const useLocalSettingsStore = create((set) => ({ settings: { theme: "", viewMode: "", + show: { + link: true, + title: true, + description: true, + image: true, + tags: true, + icon: true, + collection: true, + date: true, + }, sortBy: Sort.DateNewestFirst, }, - updateSettings: async (newSettings) => { - if ( - newSettings.theme !== undefined && - newSettings.theme !== localStorage.getItem("theme") - ) { - localStorage.setItem("theme", newSettings.theme); + updateSettings: (newSettings) => { + const { theme, viewMode, sortBy, show } = newSettings; - const localTheme = localStorage.getItem("theme") || ""; - - document.querySelector("html")?.setAttribute("data-theme", localTheme); + if (theme !== undefined && theme !== localStorage.getItem("theme")) { + localStorage.setItem("theme", theme); + document.querySelector("html")?.setAttribute("data-theme", theme); } if ( - newSettings.viewMode !== undefined && - newSettings.viewMode !== localStorage.getItem("viewMode") + viewMode !== undefined && + viewMode !== localStorage.getItem("viewMode") ) { - localStorage.setItem("viewMode", newSettings.viewMode); - - // const localTheme = localStorage.getItem("viewMode") || ""; + localStorage.setItem("viewMode", viewMode); } - if ( - newSettings.sortBy !== undefined && - newSettings.sortBy !== Number(localStorage.getItem("sortBy")) - ) { - localStorage.setItem("sortBy", newSettings.sortBy.toString()); + if (sortBy !== undefined) { + localStorage.setItem("sortBy", sortBy.toString()); } - set((state) => ({ settings: { ...state.settings, ...newSettings } })); - }, - setSettings: async () => { - if (!localStorage.getItem("theme")) { - localStorage.setItem("theme", "dark"); - } + const currentShowString = localStorage.getItem("show"); + const newShowString = show ? JSON.stringify(show) : currentShowString; - const localTheme = localStorage.getItem("theme") || ""; + if (newShowString !== currentShowString) { + localStorage.setItem("show", newShowString || ""); + } set((state) => ({ - settings: { ...state.settings, theme: localTheme }, + settings: { + ...state.settings, + ...newSettings, + show: show ? { ...state.settings.show, ...show } : state.settings.show, + }, })); + }, + setSettings: () => { + const theme = localStorage.getItem("theme") || "dark"; + localStorage.setItem("theme", theme); - document.querySelector("html")?.setAttribute("data-theme", localTheme); + const viewMode = localStorage.getItem("viewMode") || "card"; + localStorage.setItem("viewMode", viewMode); + + const storedShow = localStorage.getItem("show"); + const show = storedShow + ? JSON.parse(storedShow) + : { + link: true, + name: true, + description: true, + image: true, + tags: true, + icon: true, + collection: true, + date: true, + }; + localStorage.setItem("show", JSON.stringify(show)); + + set({ + settings: { + theme, + viewMode, + show, + sortBy: useLocalSettingsStore.getState().settings.sortBy, + }, + }); + + document.querySelector("html")?.setAttribute("data-theme", theme); }, })); From 0371695eb3635d8dd427b2538dd699a0472c6931 Mon Sep 17 00:00:00 2001 From: daniel31x13 Date: Mon, 26 Aug 2024 19:56:04 -0400 Subject: [PATCH 10/21] choose to show which detail in each views --- components/Icon.tsx | 6 +- components/LinkDetails.tsx | 2 +- .../LinkViews/LinkComponents/LinkCard.tsx | 105 ++++++++-------- .../LinkViews/LinkComponents/LinkList.tsx | 35 +++--- .../LinkViews/LinkComponents/LinkMasonry.tsx | 117 +++++++++++------- components/ViewDropdown.tsx | 11 +- store/localSettings.ts | 4 +- 7 files changed, 158 insertions(+), 122 deletions(-) diff --git a/components/Icon.tsx b/components/Icon.tsx index 178b9e66..b1790669 100644 --- a/components/Icon.tsx +++ b/components/Icon.tsx @@ -5,12 +5,12 @@ type Props = { icon: string; } & Icons.IconProps; -const Icon = forwardRef(({ icon, ...rest }) => { +const Icon = forwardRef(({ icon, ...rest }, ref) => { const IconComponent: any = Icons[icon as keyof typeof Icons]; if (!IconComponent) { - return <>; - } else return ; + return null; + } else return ; }); export default Icon; diff --git a/components/LinkDetails.tsx b/components/LinkDetails.tsx index 84aabe9b..c9b1e59b 100644 --- a/components/LinkDetails.tsx +++ b/components/LinkDetails.tsx @@ -192,7 +192,7 @@ export default function LinkDetails({ className, link }: Props) {

    {t("tags")}

    -
    +
    {link.tags.map((tag) => isPublicRoute ? (
    diff --git a/components/LinkViews/LinkComponents/LinkCard.tsx b/components/LinkViews/LinkComponents/LinkCard.tsx index 285437a5..7d13b54a 100644 --- a/components/LinkViews/LinkComponents/LinkCard.tsx +++ b/components/LinkViews/LinkComponents/LinkCard.tsx @@ -24,6 +24,7 @@ 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; @@ -148,64 +149,70 @@ export default function LinkCard({ link, flipDropdown, editMode }: Props) { !editMode && window.open(generateLinkHref(link, user), "_blank") } > -
    -
    - {previewAvailable(link) ? ( - { - const target = e.target as HTMLElement; - target.style.display = "none"; - }} - /> - ) : link.preview === "unavailable" ? ( -
    - ) : ( -
    - )} -
    - -
    -
    -
    -
    - -
    -
    -

    - {unescapeString(link.name)} -

    - - -
    - + {show.image && (
    -
    - -
    -
    - {collection && ( - - )} -
    - +
    + {previewAvailable(link) ? ( + { + const target = e.target as HTMLElement; + target.style.display = "none"; + }} + /> + ) : link.preview === "unavailable" ? ( +
    + ) : ( +
    + )} + {show.icon && ( +
    + +
    + )}
    +
    + )} + +
    +
    + {show.name && ( +

    + {unescapeString(link.name)} +

    + )} + + {show.link && } +
    + + {(show.collection || show.date) && ( +
    +
    + +
    + {show.collection && ( +
    + +
    + )} + {show.date && } +
    +
    + )}
    diff --git a/components/LinkViews/LinkComponents/LinkList.tsx b/components/LinkViews/LinkComponents/LinkList.tsx index d4e32c7c..90baa8bc 100644 --- a/components/LinkViews/LinkComponents/LinkList.tsx +++ b/components/LinkViews/LinkComponents/LinkList.tsx @@ -18,6 +18,7 @@ import { useTranslation } from "next-i18next"; import { useCollections } from "@/hooks/store/collections"; import { useUser } from "@/hooks/store/user"; import { useLinks } from "@/hooks/store/links"; +import useLocalSettingsStore from "@/store/localSettings"; type Props = { link: LinkIncludingShortenedCollectionAndTags; @@ -39,6 +40,10 @@ export default function LinkCardCompact({ const { data: user = {} } = useUser(); const { setSelectedLinks, selectedLinks } = useLinkStore(); + const { + settings: { show }, + } = useLocalSettingsStore(); + const { links } = useLinks(); useEffect(() => { @@ -105,33 +110,31 @@ export default function LinkCardCompact({ } >
    !editMode && window.open(generateLinkHref(link, user), "_blank") } > -
    - -
    + {show.icon && ( +
    + +
    + )}
    -

    - {link.name ? ( - unescapeString(link.name) - ) : ( -

    - -
    - )} -

    + {show.name && ( +

    + {unescapeString(link.name)} +

    + )}
    - {collection && ( + {show.link && } + {show.collection && ( )} - {link.name && } - + {show.date && }
    diff --git a/components/LinkViews/LinkComponents/LinkMasonry.tsx b/components/LinkViews/LinkComponents/LinkMasonry.tsx index b63019af..b59233d8 100644 --- a/components/LinkViews/LinkComponents/LinkMasonry.tsx +++ b/components/LinkViews/LinkComponents/LinkMasonry.tsx @@ -22,6 +22,8 @@ import { useTranslation } from "next-i18next"; import { useCollections } from "@/hooks/store/collections"; import { useUser } from "@/hooks/store/user"; import { useGetLink, useLinks } from "@/hooks/store/links"; +import useLocalSettingsStore from "@/store/localSettings"; +import clsx from "clsx"; type Props = { link: LinkIncludingShortenedCollectionAndTags; @@ -39,6 +41,10 @@ export default function LinkMasonry({ link, flipDropdown, editMode }: Props) { const { setSelectedLinks, selectedLinks } = useLinkStore(); + const { + settings: { show }, + } = useLocalSettingsStore(); + const { links } = useLinks(); const getLink = useGetLink(); @@ -129,55 +135,64 @@ export default function LinkMasonry({ link, flipDropdown, editMode }: Props) { : undefined } > -
    - !editMode && window.open(generateLinkHref(link, user), "_blank") - } - > -
    - {previewAvailable(link) ? ( - { - const target = e.target as HTMLElement; - target.style.display = "none"; - }} - /> - ) : link.preview === "unavailable" ? null : ( -
    - )} -
    - -
    -
    +
    + {show.image && previewAvailable(link) && ( +
    + !editMode && window.open(generateLinkHref(link, user), "_blank") + } + > +
    + {previewAvailable(link) ? ( + { + const target = e.target as HTMLElement; + target.style.display = "none"; + }} + /> + ) : link.preview === "unavailable" ? null : ( +
    + )} + {show.icon && ( +
    + +
    + )} +
    - {link.preview !== "unavailable" && ( -
    +
    +
    )} -
    -

    - {unescapeString(link.name)} -

    +
    + {show.name && ( +

    + {unescapeString(link.name)} +

    + )} - + {show.link && } - {link.description && ( -

    + {show.description && link.description && ( +

    {unescapeString(link.description)}

    )} - {link.tags && link.tags[0] && ( + {show.tags && link.tags && link.tags[0] && (
    {link.tags.map((e, i) => ( -
    + {(show.collection || show.date) && ( +
    +
    -
    - {collection && } - -
    +
    + {show.collection && ( +
    + +
    + )} + {show.date && } +
    +
    + )}
    diff --git a/components/ViewDropdown.tsx b/components/ViewDropdown.tsx index a9450650..f06c45dd 100644 --- a/components/ViewDropdown.tsx +++ b/components/ViewDropdown.tsx @@ -89,10 +89,13 @@ export default function ViewDropdown({ viewMode, setViewMode }: Props) {

    {t("show")}

    {Object.entries(settings.show) .filter((e) => - settings.viewMode === ViewMode.List // Hide tags and image checkbox in list view - ? e[0] !== "tags" && e[0] !== "image" - : settings.viewMode === ViewMode.Card // Hide tags checkbox in card view - ? e[0] !== "tags" + settings.viewMode === ViewMode.List // Hide tags, image, icon, and description checkboxes in list view + ? e[0] !== "tags" && + e[0] !== "image" && + e[0] !== "icon" && + e[0] !== "description" + : settings.viewMode === ViewMode.Card // Hide tags and description checkboxes in card view + ? e[0] !== "tags" && e[0] !== "description" : true ) .map(([key, value]) => ( diff --git a/store/localSettings.ts b/store/localSettings.ts index cd8e6ca3..9b576ce7 100644 --- a/store/localSettings.ts +++ b/store/localSettings.ts @@ -6,7 +6,7 @@ type LocalSettings = { viewMode: string; show: { link: boolean; - title: boolean; + name: boolean; description: boolean; image: boolean; tags: boolean; @@ -29,7 +29,7 @@ const useLocalSettingsStore = create((set) => ({ viewMode: "", show: { link: true, - title: true, + name: true, description: true, image: true, tags: true, From 6498ae794b59f677fc7f4814c9197bcc5d87da1d Mon Sep 17 00:00:00 2001 From: daniel31x13 Date: Mon, 26 Aug 2024 21:04:52 -0400 Subject: [PATCH 11/21] custom preview initial commit --- components/Drawer.tsx | 2 +- components/Modal.tsx | 2 +- .../ModalContent/EditCollectionModal.tsx | 2 +- components/ModalContent/EditLinkModal.tsx | 74 +++++++++++++------ .../ModalContent/NewCollectionModal.tsx | 2 +- components/ModalContent/NewLinkModal.tsx | 2 +- components/ModalContent/UploadFileModal.tsx | 2 +- public/locales/en/common.json | 4 +- 8 files changed, 61 insertions(+), 29 deletions(-) diff --git a/components/Drawer.tsx b/components/Drawer.tsx index 12f931ec..07673fb1 100644 --- a/components/Drawer.tsx +++ b/components/Drawer.tsx @@ -40,7 +40,7 @@ export default function Drawer({ dismissible && setDrawerIsOpen(false)} > - +
    dismissible && setDrawerIsOpen(false)} > - +

    {t("description")}