/* eslint-disable react/no-array-index-key */ import useSWR, { SWRConfig } from "swr"; import Head from "next/head"; import dynamic from "next/dynamic"; import classNames from "classnames"; import { useTranslation } from "next-i18next"; import { useEffect, useContext, useState, useMemo } from "react"; import { BiError } from "react-icons/bi"; import { serverSideTranslations } from "next-i18next/serverSideTranslations"; import FileContent from "components/filecontent"; import ServicesGroup from "components/services/group"; import BookmarksGroup from "components/bookmarks/group"; import Widget from "components/widgets/widget"; import Revalidate from "components/toggles/revalidate"; import createLogger from "utils/logger"; import useWindowFocus from "utils/hooks/window-focus"; import { getSettings } from "utils/config/config"; import { ColorContext } from "utils/contexts/color"; import { ThemeContext } from "utils/contexts/theme"; import { SettingsContext } from "utils/contexts/settings"; import { bookmarksResponse, servicesResponse, widgetsResponse } from "utils/config/api-response"; import ErrorBoundary from "components/errorboundry"; import themes from "utils/styles/themes"; import QuickLaunch from "components/quicklaunch"; import { getStoredProvider, searchProviders } from "components/widgets/search/search"; const ThemeToggle = dynamic(() => import("components/toggles/theme"), { ssr: false, }); const ColorToggle = dynamic(() => import("components/toggles/color"), { ssr: false, }); const Version = dynamic(() => import("components/version"), { ssr: false, }); const rightAlignedWidgets = ["weatherapi", "openweathermap", "weather", "openmeteo", "search", "datetime"]; export async function getStaticProps() { let logger; try { logger = createLogger("index"); const { providers, ...settings } = getSettings(); const services = await servicesResponse(); const bookmarks = await bookmarksResponse(); const widgets = await widgetsResponse(); return { props: { initialSettings: settings, fallback: { "/api/services": services, "/api/bookmarks": bookmarks, "/api/widgets": widgets, "/api/hash": false, }, ...(await serverSideTranslations(settings.language ?? "en")), }, }; } catch (e) { if (logger) { logger.error(e); } return { props: { initialSettings: {}, fallback: { "/api/services": [], "/api/bookmarks": [], "/api/widgets": [], "/api/hash": false, }, ...(await serverSideTranslations("en")), }, }; } } function Index({ initialSettings, fallback }) { const windowFocused = useWindowFocus(); const [stale, setStale] = useState(false); const { data: errorsData } = useSWR("/api/validate"); const { data: hashData, mutate: mutateHash } = useSWR("/api/hash"); useEffect(() => { if (windowFocused) { mutateHash(); } }, [windowFocused, mutateHash]); useEffect(() => { if (hashData) { if (typeof window !== "undefined") { const previousHash = localStorage.getItem("hash"); if (!previousHash) { localStorage.setItem("hash", hashData.hash); } if (previousHash && previousHash !== hashData.hash) { setStale(true); localStorage.setItem("hash", hashData.hash); fetch("/api/revalidate").then((res) => { if (res.ok) { window.location.reload(); } }); } } } }, [hashData]); if (stale) { return (
); } if (errorsData && errorsData.length > 0) { return (
{errorsData.map((error, i) => (
{error.config}
{error.reason}
{error.mark.snippet}
))}
); } return ( fetch(resource, init).then((res) => res.json()) }}> ); } const headerStyles = { boxed: "m-4 mb-0 sm:m-8 sm:mb-0 rounded-md shadow-md shadow-theme-900/10 dark:shadow-theme-900/20 bg-theme-100/20 dark:bg-white/5 p-3", underlined: "m-4 mb-0 sm:m-8 sm:mb-1 border-b-2 pb-4 border-theme-800 dark:border-theme-200/50", clean: "m-4 mb-0 sm:m-8 sm:mb-0", boxedWidgets: "m-4 mb-0 sm:m-8 sm:mb-0 sm:mt-1", }; function Home({ initialSettings }) { const { i18n } = useTranslation(); const { theme, setTheme } = useContext(ThemeContext); const { color, setColor } = useContext(ColorContext); const { settings, setSettings } = useContext(SettingsContext); useEffect(() => { setSettings(initialSettings); }, [initialSettings, setSettings]); const { data: services } = useSWR("/api/services"); const { data: bookmarks } = useSWR("/api/bookmarks"); const { data: widgets } = useSWR("/api/widgets"); const servicesAndBookmarks = [...services.map(sg => sg.services).flat(), ...bookmarks.map(bg => bg.bookmarks).flat()] useEffect(() => { if (settings.language) { i18n.changeLanguage(settings.language); } if (settings.theme && theme !== settings.theme) { setTheme(settings.theme); } if (settings.color && color !== settings.color) { setColor(settings.color); } }, [i18n, settings, color, setColor, theme, setTheme]); const [searching, setSearching] = useState(false); const [searchString, setSearchString] = useState(""); let searchProvider = null; const searchWidget = Object.values(widgets).find(w => w.type === "search"); if (searchWidget) { if (Array.isArray(searchWidget.options?.provider)) { // if search provider is a list, try to retrieve from localstorage, fall back to the first searchProvider = getStoredProvider() ?? searchProviders[searchWidget.options.provider[0]]; } else if (searchWidget.options?.provider === 'custom') { searchProvider = { url: searchWidget.options.url } } else { searchProvider = searchProviders[searchWidget.options?.provider]; } } const headerStyle = settings?.headerStyle || "underlined"; useEffect(() => { function handleKeyDown(e) { if (e.target.tagName === "BODY" || e.target.id === "inner_wrapper") { if (e.key.match(/(\w|\s)/g) && !(e.altKey || e.ctrlKey || e.metaKey || e.shiftKey || e.code === "Tab")) { setSearching(true); } else if (e.key === "Escape") { setSearchString(""); setSearching(false); } } } document.addEventListener('keydown', handleKeyDown); return function cleanup() { document.removeEventListener('keydown', handleKeyDown); } }) const servicesAndBookmarksGroups = useMemo(() => { const layoutGroups = settings.layout ? Object.keys(settings.layout).map( (groupName) => services?.find(g => g.name === groupName) ?? bookmarks?.find(b => b.name === groupName) ).filter(g => g) : []; const serviceGroups = services?.filter(group => settings.layout?.[group.name] === undefined); const bookmarkGroups = bookmarks.filter(group => settings.layout?.[group.name] === undefined); return <> {layoutGroups.length > 0 &&
{layoutGroups.map((group) => ( group.services ? () : () ) )}
} {serviceGroups?.length > 0 &&
{serviceGroups.map((group) => ( ))}
} {bookmarkGroups?.length > 0 &&
{bookmarkGroups.map((group) => ( ))}
} }, [ services, bookmarks, settings.layout, settings.fiveColumns, settings.disableCollapse ]); return ( <> {settings.title || "Homepage"} {settings.base && } {settings.favicon ? ( <> ) : ( <> )}
{widgets && ( <> {widgets .filter((widget) => !rightAlignedWidgets.includes(widget.type)) .map((widget, i) => ( ))}
{widgets .filter((widget) => rightAlignedWidgets.includes(widget.type)) .map((widget, i) => ( ))}
)}
{servicesAndBookmarksGroups}
); } export default function Wrapper({ initialSettings, fallback }) { const wrappedStyle = {}; let backgroundBlur = false; let backgroundSaturate = false; let backgroundBrightness = false; if (initialSettings && initialSettings.background) { let opacity = initialSettings.backgroundOpacity ?? 1; let backgroundImage = initialSettings.background; if (typeof initialSettings.background === 'object') { backgroundImage = initialSettings.background.image; backgroundBlur = initialSettings.background.blur !== undefined; backgroundSaturate = initialSettings.background.saturate !== undefined; backgroundBrightness = initialSettings.background.brightness !== undefined; if (initialSettings.background.opacity !== undefined) opacity = initialSettings.background.opacity / 100; } const opacityValue = 1 - opacity; wrappedStyle.backgroundImage = ` linear-gradient( rgb(var(--bg-color) / ${opacityValue}), rgb(var(--bg-color) / ${opacityValue}) ), url(${backgroundImage})`; wrappedStyle.backgroundPosition = "center"; wrappedStyle.backgroundSize = "cover"; } return (
); }