add favicons component
This commit is contained in:
parent
e730a0ceb0
commit
8a25f25e3c
77
src/components/favicons/group.jsx
Normal file
77
src/components/favicons/group.jsx
Normal file
@ -0,0 +1,77 @@
|
||||
import { useRef, useEffect } from "react";
|
||||
import classNames from "classnames";
|
||||
import { Disclosure, Transition } from "@headlessui/react";
|
||||
import { MdKeyboardArrowDown } from "react-icons/md";
|
||||
|
||||
import ErrorBoundary from "components/errorboundry";
|
||||
import List from "components/favicons/list";
|
||||
import ResolvedIcon from "components/resolvedicon";
|
||||
|
||||
export default function FaviconsGroup({ favicons, layout, disableCollapse, groupsInitiallyCollapsed }) {
|
||||
const panel = useRef();
|
||||
console.log('+++++')
|
||||
console.log(favicons)
|
||||
useEffect(() => {
|
||||
if (layout?.initiallyCollapsed ?? groupsInitiallyCollapsed) panel.current.style.height = `0`;
|
||||
}, [layout, groupsInitiallyCollapsed]);
|
||||
|
||||
return (
|
||||
<div
|
||||
key={favicons.name}
|
||||
className={classNames(
|
||||
"favicon-group",
|
||||
layout?.style === "row" ? "basis-full" : "basis-full md:basis-1/4 lg:basis-1/5 xl:basis-1/6",
|
||||
layout?.header === false ? "flex-1 px-1 -my-1 overflow-hidden" : "flex-1 p-1 overflow-hidden",
|
||||
)}
|
||||
>
|
||||
<Disclosure defaultOpen={!(layout?.initiallyCollapsed ?? groupsInitiallyCollapsed) ?? true}>
|
||||
{({ open }) => (
|
||||
<>
|
||||
{layout?.header !== false && (
|
||||
<Disclosure.Button disabled={disableCollapse} className="flex w-full select-none items-center group">
|
||||
{layout?.icon && (
|
||||
<div className="flex-shrink-0 mr-2 w-7 h-7 favicon-group-icon">
|
||||
<ResolvedIcon icon={layout.icon} />
|
||||
</div>
|
||||
)}
|
||||
<h2 className="text-theme-800 dark:text-theme-300 text-xl font-medium favicon-group-name">
|
||||
{favicons.name}
|
||||
</h2>
|
||||
<MdKeyboardArrowDown
|
||||
className={classNames(
|
||||
disableCollapse ? "hidden" : "",
|
||||
"transition-all opacity-0 group-hover:opacity-100 ml-auto text-theme-800 dark:text-theme-300 text-xl",
|
||||
open ? "" : "rotate-180",
|
||||
)}
|
||||
/>
|
||||
</Disclosure.Button>
|
||||
)}
|
||||
<Transition
|
||||
// Otherwise the transition group does display: none and cancels animation
|
||||
className="!block"
|
||||
unmount={false}
|
||||
beforeLeave={() => {
|
||||
panel.current.style.height = `${panel.current.scrollHeight}px`;
|
||||
setTimeout(() => {
|
||||
panel.current.style.height = `0`;
|
||||
}, 1);
|
||||
}}
|
||||
beforeEnter={() => {
|
||||
panel.current.style.height = `0px`;
|
||||
setTimeout(() => {
|
||||
panel.current.style.height = `${panel.current.scrollHeight}px`;
|
||||
}, 1);
|
||||
}}
|
||||
>
|
||||
<Disclosure.Panel className="transition-all overflow-hidden duration-300 ease-out" ref={panel} static>
|
||||
<ErrorBoundary>
|
||||
<List favicons={favicons.bookmarks} layout={layout} />
|
||||
</ErrorBoundary>
|
||||
</Disclosure.Panel>
|
||||
</Transition>
|
||||
</>
|
||||
)}
|
||||
</Disclosure>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
33
src/components/favicons/item.jsx
Normal file
33
src/components/favicons/item.jsx
Normal file
@ -0,0 +1,33 @@
|
||||
import { useContext } from "react";
|
||||
import classNames from "classnames";
|
||||
|
||||
import { SettingsContext } from "utils/contexts/settings";
|
||||
import ResolvedIcon from "components/resolvedicon";
|
||||
|
||||
export default function Item({ favicon }) {
|
||||
const description = favicon.description ?? new URL(favicon.href).hostname;
|
||||
const { settings } = useContext(SettingsContext);
|
||||
|
||||
return (
|
||||
<li key={favicon.name} id={favicon.id} className="favicon" data-name={favicon.name}>
|
||||
<a
|
||||
href={favicon.href}
|
||||
title={favicon.name}
|
||||
rel="noreferrer"
|
||||
target={favicon.target ?? settings.target ?? "_blank"}
|
||||
className={classNames(
|
||||
settings.cardBlur !== undefined && `backdrop-blur${settings.cardBlur.length ? "-" : ""}${settings.cardBlur}`,
|
||||
"",
|
||||
)}
|
||||
>
|
||||
<div className="">
|
||||
{favicon.icon && (
|
||||
<ResolvedIcon icon={favicon.icon} alt={favicon.abbr} />
|
||||
)}
|
||||
{!favicon.icon && favicon.abbr}
|
||||
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
17
src/components/favicons/list.jsx
Normal file
17
src/components/favicons/list.jsx
Normal file
@ -0,0 +1,17 @@
|
||||
import classNames from "classnames";
|
||||
|
||||
import { columnMap } from "../../utils/layout/columns";
|
||||
|
||||
import Item from "components/favicons/item";
|
||||
|
||||
export default function List({ favicons, layout }) {
|
||||
return (
|
||||
<ul
|
||||
className="favicon-list"
|
||||
>
|
||||
{favicons.map((favicon) => (
|
||||
<Item key={`${favicon.name}-${favicon.href}`} favicon={favicon} />
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
5
src/pages/api/favicons.js
Normal file
5
src/pages/api/favicons.js
Normal file
@ -0,0 +1,5 @@
|
||||
import { faviconsResponse } from "utils/config/api-response";
|
||||
|
||||
export default async function handler(req, res) {
|
||||
res.send(await faviconsResponse());
|
||||
}
|
||||
@ -9,6 +9,7 @@ const configs = [
|
||||
"settings.yaml",
|
||||
"services.yaml",
|
||||
"bookmarks.yaml",
|
||||
"favicons.yaml",
|
||||
"widgets.yaml",
|
||||
"custom.css",
|
||||
"custom.js",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import checkAndCopyConfig from "utils/config/config";
|
||||
|
||||
const configs = ["docker.yaml", "settings.yaml", "services.yaml", "bookmarks.yaml"];
|
||||
const configs = ["docker.yaml", "favicons.yaml", "settings.yaml", "services.yaml", "bookmarks.yaml"];
|
||||
|
||||
export default async function handler(req, res) {
|
||||
const errors = configs.map((config) => checkAndCopyConfig(config)).filter((status) => status !== true);
|
||||
|
||||
@ -13,6 +13,7 @@ import { useRouter } from "next/router";
|
||||
import Tab, { slugifyAndEncode } from "components/tab";
|
||||
import ServicesGroup from "components/services/group";
|
||||
import BookmarksGroup from "components/bookmarks/group";
|
||||
import FaviconsGroup from "components/favicons/group";
|
||||
import Widget from "components/widgets/widget";
|
||||
import Revalidate from "components/toggles/revalidate";
|
||||
import createLogger from "utils/logger";
|
||||
@ -22,7 +23,7 @@ import { ColorContext } from "utils/contexts/color";
|
||||
import { ThemeContext } from "utils/contexts/theme";
|
||||
import { SettingsContext } from "utils/contexts/settings";
|
||||
import { TabContext } from "utils/contexts/tab";
|
||||
import { bookmarksResponse, servicesResponse, widgetsResponse } from "utils/config/api-response";
|
||||
import { bookmarksResponse, faviconsResponse, servicesResponse, widgetsResponse } from "utils/config/api-response";
|
||||
import ErrorBoundary from "components/errorboundry";
|
||||
import themes from "utils/styles/themes";
|
||||
import QuickLaunch from "components/quicklaunch";
|
||||
@ -49,12 +50,14 @@ export async function getStaticProps() {
|
||||
|
||||
const services = await servicesResponse();
|
||||
const bookmarks = await bookmarksResponse();
|
||||
const favicons = await faviconsResponse();
|
||||
const widgets = await widgetsResponse();
|
||||
|
||||
return {
|
||||
props: {
|
||||
initialSettings: settings,
|
||||
fallback: {
|
||||
"/api/favicons": favicons,
|
||||
"/api/services": services,
|
||||
"/api/bookmarks": bookmarks,
|
||||
"/api/widgets": widgets,
|
||||
@ -71,6 +74,7 @@ export async function getStaticProps() {
|
||||
props: {
|
||||
initialSettings: {},
|
||||
fallback: {
|
||||
"/api/favicons": [],
|
||||
"/api/services": [],
|
||||
"/api/bookmarks": [],
|
||||
"/api/widgets": [],
|
||||
@ -180,11 +184,13 @@ function Home({ initialSettings }) {
|
||||
|
||||
const { data: services } = useSWR("/api/services");
|
||||
const { data: bookmarks } = useSWR("/api/bookmarks");
|
||||
const { data: favicons } = useSWR("/api/favicons");
|
||||
const { data: widgets } = useSWR("/api/widgets");
|
||||
|
||||
const servicesAndBookmarks = [
|
||||
...services.map((sg) => sg.services).flat(),
|
||||
...bookmarks.map((bg) => bg.bookmarks).flat(),
|
||||
...favicons.map((fg) => fg.favicons).flat(),
|
||||
].filter((i) => i?.href);
|
||||
|
||||
useEffect(() => {
|
||||
@ -254,7 +260,7 @@ function Home({ initialSettings }) {
|
||||
const undefinedGroupFilter = (g) => settings.layout?.[g.name] === undefined;
|
||||
|
||||
const layoutGroups = Object.keys(settings.layout ?? {})
|
||||
.map((groupName) => services?.find((g) => g.name === groupName) ?? bookmarks?.find((b) => b.name === groupName))
|
||||
.map((groupName) => services?.find((g) => g.name === groupName) ?? favicons?.find((f) => f.name === groupName) ?? bookmarks?.find((b) => b.name === groupName) )
|
||||
.filter(tabGroupFilter);
|
||||
|
||||
if (!settings.layout && JSON.stringify(settings.layout) !== JSON.stringify(initialSettings.layout)) {
|
||||
@ -264,7 +270,10 @@ function Home({ initialSettings }) {
|
||||
|
||||
const serviceGroups = services?.filter(tabGroupFilter).filter(undefinedGroupFilter);
|
||||
const bookmarkGroups = bookmarks.filter(tabGroupFilter).filter(undefinedGroupFilter);
|
||||
|
||||
const faviconGroups = favicons.filter(tabGroupFilter).filter(undefinedGroupFilter);
|
||||
console.log(bookmarkGroups)
|
||||
console.log('------')
|
||||
console.log(faviconGroups)
|
||||
return (
|
||||
<>
|
||||
{tabs.length > 0 && (
|
||||
@ -300,6 +309,7 @@ function Home({ initialSettings }) {
|
||||
groupsInitiallyCollapsed={settings.groupsInitiallyCollapsed}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<BookmarksGroup
|
||||
key={group.name}
|
||||
bookmarks={group}
|
||||
@ -307,6 +317,14 @@ function Home({ initialSettings }) {
|
||||
disableCollapse={settings.disableCollapse}
|
||||
groupsInitiallyCollapsed={settings.groupsInitiallyCollapsed}
|
||||
/>
|
||||
<FaviconsGroup
|
||||
key={group.name}
|
||||
bookmarks={group}
|
||||
layout={settings.layout?.[group.name]}
|
||||
disableCollapse={settings.disableCollapse}
|
||||
groupsInitiallyCollapsed={settings.groupsInitiallyCollapsed}
|
||||
/>
|
||||
</>
|
||||
),
|
||||
)}
|
||||
</div>
|
||||
@ -339,6 +357,19 @@ function Home({ initialSettings }) {
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{faviconGroups?.length > 0 && (
|
||||
<div key="favicons" id="favicons" className="flex flex-wrap m-4 sm:m-8 sm:mt-4 items-start mb-2">
|
||||
{faviconGroups.map((group) => (
|
||||
<FaviconsGroup
|
||||
key={group.name}
|
||||
favicons={group}
|
||||
layout={settings.layout?.[group.name]}
|
||||
disableCollapse={settings.disableCollapse}
|
||||
groupsInitiallyCollapsed={settings.groupsInitiallyCollapsed}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}, [
|
||||
@ -346,6 +377,7 @@ function Home({ initialSettings }) {
|
||||
activeTab,
|
||||
services,
|
||||
bookmarks,
|
||||
favicons,
|
||||
settings.layout,
|
||||
settings.fiveColumns,
|
||||
settings.disableCollapse,
|
||||
|
||||
@ -25,6 +25,12 @@ body {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.favicon {
|
||||
display: inline-block;
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
|
||||
|
||||
.light {
|
||||
--bg-color: var(--color-50);
|
||||
--scrollbar-thumb: rgb(var(--color-300));
|
||||
|
||||
@ -24,6 +24,54 @@ function compareServices(service1, service2) {
|
||||
return service1.name.localeCompare(service2.name);
|
||||
}
|
||||
|
||||
|
||||
export async function faviconsResponse() {
|
||||
checkAndCopyConfig("favicons.yaml");
|
||||
|
||||
const faviconsYaml = path.join(CONF_DIR, "favicons.yaml");
|
||||
const rawFileContents = await fs.readFile(faviconsYaml, "utf8");
|
||||
const fileContents = substituteEnvironmentVars(rawFileContents);
|
||||
const favicons = yaml.load(fileContents);
|
||||
|
||||
if (!favicons) return [];
|
||||
|
||||
let initialSettings;
|
||||
|
||||
try {
|
||||
initialSettings = await getSettings();
|
||||
} catch (e) {
|
||||
console.error("Failed to load favicons.yaml, please check for errors");
|
||||
if (e) console.error(e.toString());
|
||||
initialSettings = {};
|
||||
}
|
||||
|
||||
// map easy to write YAML objects into easy to consume JS arrays
|
||||
const faviconsArray = favicons.map((group) => ({
|
||||
name: Object.keys(group)[0],
|
||||
bookmarks: group[Object.keys(group)[0]].map((entries) => ({
|
||||
name: Object.keys(entries)[0],
|
||||
...entries[Object.keys(entries)[0]][0],
|
||||
})),
|
||||
}));
|
||||
|
||||
const sortedGroups = [];
|
||||
const unsortedGroups = [];
|
||||
const definedLayouts = initialSettings.layout ? Object.keys(initialSettings.layout) : null;
|
||||
|
||||
faviconsArray.forEach((group) => {
|
||||
if (definedLayouts) {
|
||||
const layoutIndex = definedLayouts.findIndex((layout) => layout === group.name);
|
||||
if (layoutIndex > -1) sortedGroups[layoutIndex] = group;
|
||||
else unsortedGroups.push(group);
|
||||
} else {
|
||||
unsortedGroups.push(group);
|
||||
}
|
||||
});
|
||||
|
||||
return [...sortedGroups.filter((g) => g), ...unsortedGroups];
|
||||
}
|
||||
|
||||
|
||||
export async function bookmarksResponse() {
|
||||
checkAndCopyConfig("bookmarks.yaml");
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user