refactored view dropdown

This commit is contained in:
daniel31x13 2024-08-26 18:47:10 -04:00
parent 642374c2e5
commit 9ae9c7c81a
6 changed files with 191 additions and 82 deletions

View File

@ -23,6 +23,7 @@ import { useCollections } from "@/hooks/store/collections";
import { useUser } from "@/hooks/store/user"; import { useUser } from "@/hooks/store/user";
import { useGetLink, useLinks } from "@/hooks/store/links"; import { useGetLink, useLinks } from "@/hooks/store/links";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import useLocalSettingsStore from "@/store/localSettings";
type Props = { type Props = {
link: LinkIncludingShortenedCollectionAndTags; link: LinkIncludingShortenedCollectionAndTags;
@ -41,6 +42,10 @@ export default function LinkCard({ link, flipDropdown, editMode }: Props) {
const { setSelectedLinks, selectedLinks } = useLinkStore(); const { setSelectedLinks, selectedLinks } = useLinkStore();
const {
settings: { show },
} = useLocalSettingsStore();
const { const {
data: { data: links = [] }, data: { data: links = [] },
} = useLinks(); } = useLinks();
@ -166,11 +171,9 @@ export default function LinkCard({ link, flipDropdown, editMode }: Props) {
) : ( ) : (
<div className="duration-100 h-40 bg-opacity-80 skeleton rounded-none"></div> <div className="duration-100 h-40 bg-opacity-80 skeleton rounded-none"></div>
)} )}
{link.type !== "image" && (
<div className="absolute top-0 left-0 right-0 bottom-0 rounded-t-2xl flex items-center justify-center shadow rounded-md"> <div className="absolute top-0 left-0 right-0 bottom-0 rounded-t-2xl flex items-center justify-center shadow rounded-md">
<LinkIcon link={link} /> <LinkIcon link={link} />
</div> </div>
)}
</div> </div>
<hr className="divider my-0 border-t border-neutral-content h-[1px]" /> <hr className="divider my-0 border-t border-neutral-content h-[1px]" />
</div> </div>

View File

@ -155,11 +155,9 @@ export default function LinkMasonry({ link, flipDropdown, editMode }: Props) {
) : link.preview === "unavailable" ? null : ( ) : link.preview === "unavailable" ? null : (
<div className="duration-100 h-40 bg-opacity-80 skeleton rounded-none"></div> <div className="duration-100 h-40 bg-opacity-80 skeleton rounded-none"></div>
)} )}
{link.type !== "image" && (
<div className="absolute top-0 left-0 right-0 bottom-0 rounded-t-2xl flex items-center justify-center shadow rounded-md"> <div className="absolute top-0 left-0 right-0 bottom-0 rounded-t-2xl flex items-center justify-center shadow rounded-md">
<LinkIcon link={link} /> <LinkIcon link={link} />
</div> </div>
)}
</div> </div>
{link.preview !== "unavailable" && ( {link.preview !== "unavailable" && (

View File

@ -1,7 +1,8 @@
import React, { Dispatch, SetStateAction, useEffect } from "react"; import React, { Dispatch, SetStateAction, useEffect } from "react";
import useLocalSettingsStore from "@/store/localSettings"; import useLocalSettingsStore from "@/store/localSettings";
import { ViewMode } from "@/types/global"; import { ViewMode } from "@/types/global";
import { dropdownTriggerer } from "@/lib/client/utils";
import { useTranslation } from "next-i18next";
type Props = { type Props = {
viewMode: ViewMode; viewMode: ViewMode;
@ -9,53 +10,111 @@ type Props = {
}; };
export default function ViewDropdown({ viewMode, setViewMode }: Props) { export default function ViewDropdown({ viewMode, setViewMode }: Props) {
const { updateSettings } = useLocalSettingsStore(); const { settings, updateSettings } = useLocalSettingsStore((state) => state);
const { t } = useTranslation();
const onChangeViewMode = ( const onChangeViewMode = (
e: React.MouseEvent<HTMLButtonElement>, e: React.MouseEvent<HTMLButtonElement>,
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(() => { useEffect(() => {
updateSettings({ viewMode }); updateSettings({ viewMode });
}, [viewMode]); }, [viewMode, updateSettings]);
return ( return (
<div className="p-1 flex flex-row gap-1 border border-neutral-content rounded-[0.625rem]"> <div className="dropdown dropdown-bottom dropdown-end">
<div
tabIndex={0}
role="button"
onMouseDown={dropdownTriggerer}
className="btn btn-sm btn-square btn-ghost border-none"
>
{viewMode === ViewMode.Card ? (
<i className="bi-grid w-4 h-4 text-neutral"></i>
) : viewMode === ViewMode.Masonry ? (
<i className="bi-columns-gap w-4 h-4 text-neutral"></i>
) : (
<i className="bi-view-stacked w-4 h-4 text-neutral"></i>
)}
</div>
<ul className="dropdown-content z-[30] menu shadow bg-base-200 min-w-52 border border-neutral-content rounded-xl mt-1">
<p className="mb-1 text-sm text-neutral">{t("view")}</p>
<div
className="p-1 flex w-full justify-between gap-1 border border-neutral-content rounded-[0.625rem]"
tabIndex={0}
role="button"
>
<button <button
onClick={(e) => onChangeViewMode(e, ViewMode.Card)} onClick={(e) => onChangeViewMode(e, ViewMode.Card)}
className={`btn btn-square btn-sm btn-ghost ${ className={`btn btn-square btn-sm btn-ghost ${
viewMode == ViewMode.Card viewMode === ViewMode.Card
? "bg-primary/20 hover:bg-primary/20" ? "bg-primary/20 hover:bg-primary/20"
: "hover:bg-neutral/20" : "hover:bg-neutral/20"
}`} }`}
> >
<i className="bi-grid w-4 h-4 text-neutral"></i> <i className="bi-grid text-lg text-neutral"></i>
</button> </button>
<button <button
onClick={(e) => onChangeViewMode(e, ViewMode.Masonry)} onClick={(e) => onChangeViewMode(e, ViewMode.Masonry)}
className={`btn btn-square btn-sm btn-ghost ${ className={`btn btn-square btn-sm btn-ghost ${
viewMode == ViewMode.Masonry viewMode === ViewMode.Masonry
? "bg-primary/20 hover:bg-primary/20" ? "bg-primary/20 hover:bg-primary/20"
: "hover:bg-neutral/20" : "hover:bg-neutral/20"
}`} }`}
> >
<i className="bi bi-columns-gap w-4 h-4 text-neutral"></i> <i className="bi-columns-gap text-lg text-neutral"></i>
</button> </button>
<button <button
onClick={(e) => onChangeViewMode(e, ViewMode.List)} onClick={(e) => onChangeViewMode(e, ViewMode.List)}
className={`btn btn-square btn-sm btn-ghost ${ className={`btn btn-square btn-sm btn-ghost ${
viewMode == ViewMode.List viewMode === ViewMode.List
? "bg-primary/20 hover:bg-primary/20" ? "bg-primary/20 hover:bg-primary/20"
: "hover:bg-neutral/20" : "hover:bg-neutral/20"
}`} }`}
> >
<i className="bi bi-view-stacked w-4 h-4 text-neutral"></i> <i className="bi-view-stacked text-lg text-neutral"></i>
</button> </button>
</div> </div>
<p className="my-1 text-sm text-neutral">{t("show")}</p>
{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]) => (
<li key={key}>
<label
className="label cursor-pointer flex justify-start"
tabIndex={0}
role="button"
>
<input
type="checkbox"
className="checkbox checkbox-primary"
checked={value}
onChange={() =>
toggleShowSetting(key as keyof typeof settings.show)
}
/>
<span className="label-text whitespace-nowrap">{t(key)}</span>
</label>
</li>
))}
</ul>
</div>
); );
} }

View File

@ -11,8 +11,8 @@ export const icons: ReadonlyArray<IconEntry> = iconData.map((entry) => ({
Icon: Icons[entry.pascal_name as keyof typeof Icons] as Icons.Icon, Icon: Icons[entry.pascal_name as keyof typeof Icons] as Icons.Icon,
})); }));
if (process.env.NODE_ENV === "development") { // if (process.env.NODE_ENV === "development") {
console.log(`${icons.length} icons`); // console.log(`${icons.length} icons`);
} // }
export const iconCount = Intl.NumberFormat("en-US").format(icons.length * 6); export const iconCount = Intl.NumberFormat("en-US").format(icons.length * 6);

View File

@ -380,5 +380,10 @@
"duotone": "Duotone", "duotone": "Duotone",
"light_icon": "Light", "light_icon": "Light",
"search": "Search", "search": "Search",
"set_custom_icon": "Set Custom Icon" "set_custom_icon": "Set Custom Icon",
"view": "View",
"show": "Show",
"image": "Image",
"icon": "Icon",
"date": "Date"
} }

View File

@ -2,14 +2,24 @@ import { Sort } from "@/types/global";
import { create } from "zustand"; import { create } from "zustand";
type LocalSettings = { type LocalSettings = {
theme?: string; theme: string;
viewMode?: string; viewMode: string;
show: {
link: boolean;
title: boolean;
description: boolean;
image: boolean;
tags: boolean;
icon: boolean;
collection: boolean;
date: boolean;
};
sortBy?: Sort; sortBy?: Sort;
}; };
type LocalSettingsStore = { type LocalSettingsStore = {
settings: LocalSettings; settings: LocalSettings;
updateSettings: (settings: LocalSettings) => void; updateSettings: (settings: Partial<LocalSettings>) => void;
setSettings: () => void; setSettings: () => void;
}; };
@ -17,50 +27,84 @@ const useLocalSettingsStore = create<LocalSettingsStore>((set) => ({
settings: { settings: {
theme: "", theme: "",
viewMode: "", viewMode: "",
show: {
link: true,
title: true,
description: true,
image: true,
tags: true,
icon: true,
collection: true,
date: true,
},
sortBy: Sort.DateNewestFirst, sortBy: Sort.DateNewestFirst,
}, },
updateSettings: async (newSettings) => { updateSettings: (newSettings) => {
if ( const { theme, viewMode, sortBy, show } = newSettings;
newSettings.theme !== undefined &&
newSettings.theme !== localStorage.getItem("theme")
) {
localStorage.setItem("theme", newSettings.theme);
const localTheme = localStorage.getItem("theme") || ""; if (theme !== undefined && theme !== localStorage.getItem("theme")) {
localStorage.setItem("theme", theme);
document.querySelector("html")?.setAttribute("data-theme", localTheme); document.querySelector("html")?.setAttribute("data-theme", theme);
} }
if ( if (
newSettings.viewMode !== undefined && viewMode !== undefined &&
newSettings.viewMode !== localStorage.getItem("viewMode") viewMode !== localStorage.getItem("viewMode")
) { ) {
localStorage.setItem("viewMode", newSettings.viewMode); localStorage.setItem("viewMode", viewMode);
// const localTheme = localStorage.getItem("viewMode") || "";
} }
if ( if (sortBy !== undefined) {
newSettings.sortBy !== undefined && localStorage.setItem("sortBy", sortBy.toString());
newSettings.sortBy !== Number(localStorage.getItem("sortBy"))
) {
localStorage.setItem("sortBy", newSettings.sortBy.toString());
} }
set((state) => ({ settings: { ...state.settings, ...newSettings } })); const currentShowString = localStorage.getItem("show");
}, const newShowString = show ? JSON.stringify(show) : currentShowString;
setSettings: async () => {
if (!localStorage.getItem("theme")) {
localStorage.setItem("theme", "dark");
}
const localTheme = localStorage.getItem("theme") || ""; if (newShowString !== currentShowString) {
localStorage.setItem("show", newShowString || "");
}
set((state) => ({ 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);
}, },
})); }));