Refactored information widgets, improve widget-boxed style

Signed-off-by: Denis Papec <denis.papec@gmail.com>
pull/1603/head
Denis Papec 1 year ago
parent c5b6dcc1e0
commit cd5162e39c
No known key found for this signature in database
GPG Key ID: DE0912C69A47222C

@ -1,6 +1,8 @@
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import { useTranslation } from "next-i18next"; import { useTranslation } from "next-i18next";
import classNames from "classnames";
import Container from "../widget/container";
import Raw from "../widget/raw";
const textSizes = { const textSizes = {
"4xl": "text-4xl", "4xl": "text-4xl",
@ -28,15 +30,14 @@ export default function DateTime({ options }) {
}, [date, setDate, dateLocale, format]); }, [date, setDate, dateLocale, format]);
return ( return (
<div className={classNames( <Container options={options}>
"flex flex-col justify-center first:ml-0 ml-4", <Raw>
options?.styleBoxed === true && " mt-2 m: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", <div className="flex flex-row items-center grow justify-end">
)}> <span className={`text-theme-800 dark:text-theme-200 tabular-nums ${textSizes[textSize || "lg"]}`}>
<div className="flex flex-row items-center grow justify-end"> {date}
<span className={`text-theme-800 dark:text-theme-200 tabular-nums ${textSizes[textSize || "lg"]}`}> </span>
{date} </div>
</span> </Raw>
</div> </Container>
</div>
); );
} }

@ -1,23 +0,0 @@
import { useTranslation } from "react-i18next";
import { BiError } from "react-icons/bi";
import classNames from "classnames";
export default function Error({ options }) {
const { t } = useTranslation();
return (
<div className={classNames(
"flex flex-col justify-center first:ml-0 ml-4 mr-2",
options?.styleBoxed === true && " ml-4 mt-2 m: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",
)}>
<div className="flex flex-row items-center justify-end">
<div className="flex flex-col items-center">
<BiError className="w-8 h-8 text-theme-800 dark:text-theme-200" />
<div className="flex flex-col ml-3 text-left">
<span className="text-theme-800 dark:text-theme-200 text-sm">{t("widget.api_error")}</span>
</div>
</div>
</div>
</div>
);
}

@ -3,10 +3,15 @@ import { useContext } from "react";
import { FaMemory, FaRegClock, FaThermometerHalf } from "react-icons/fa"; import { FaMemory, FaRegClock, FaThermometerHalf } from "react-icons/fa";
import { FiCpu, FiHardDrive } from "react-icons/fi"; import { FiCpu, FiHardDrive } from "react-icons/fi";
import { useTranslation } from "next-i18next"; import { useTranslation } from "next-i18next";
import classNames from "classnames";
import UsageBar from "../resources/usage-bar"; import UsageBar from "../resources/usage-bar";
import Error from "../error"; import Error from "../widget/error";
import SingleResource from "../widget/single_resource";
import WidgetIcon from "../widget/widget_icon";
import ResourceValue from "../widget/resource_value";
import ResourceLabel from "../widget/resource_label";
import Resources from "../widget/resources";
import WidgetLabel from "../widget/widget_label";
import { SettingsContext } from "utils/contexts/settings"; import { SettingsContext } from "utils/contexts/settings";
@ -31,40 +36,33 @@ export default function Widget({ options }) {
} }
if (!data) { if (!data) {
return ( return <Resources options={options}>
<div className={classNames( <SingleResource>
"flex flex-col max-w:full sm:basis-auto self-center grow-0 flex-wrap ml-4", <WidgetIcon icon={FiCpu} />
options?.styleBoxed === true && " mb-0 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", <ResourceLabel>{t("glances.wait")}</ResourceLabel>
)}> <UsageBar percent="0" />
<div className="flex flex-row self-center flex-wrap justify-between"> </SingleResource>
<div className="flex-none flex flex-row items-center mr-3 py-1.5"> <SingleResource>
<FiCpu className="text-theme-800 dark:text-theme-200 w-5 h-5" /> <WidgetIcon icon={FaMemory} />
<div className="flex flex-col ml-3 text-left min-w-[85px]"> <ResourceLabel>{t("glances.wait")}</ResourceLabel>
<div className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between"> <UsageBar percent="0" />
<div className="pl-0.5 text-xs"> </SingleResource>
{t("glances.wait")} {options.cputemp &&
</div> <SingleResource>
</div> <WidgetIcon icon={FaThermometerHalf} />
<UsageBar percent="0" /> <ResourceLabel>{t("glances.wait")}</ResourceLabel>
</div> <UsageBar percent="0" />
</div> </SingleResource>
<div className="flex-none flex flex-row items-center mr-3 py-1.5"> }
<FaMemory className="text-theme-800 dark:text-theme-200 w-5 h-5" /> {options.uptime &&
<div className="flex flex-col ml-3 text-left min-w-[85px]"> <SingleResource>
<div className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between"> <WidgetIcon icon={FaRegClock} />
<div className="pl-0.5 text-xs"> <ResourceLabel>{t("glances.wait")}</ResourceLabel>
{t("glances.wait")} <UsageBar percent="0" />
</div> </SingleResource>
</div> }
<UsageBar percent="0" /> {options.label && <WidgetLabel label={options.label} />}
</div> </Resources>;
</div>
</div>
{options.label && (
<div className="ml-6 pt-1 text-center text-theme-800 dark:text-theme-200 text-xs">{options.label}</div>
)}
</div>
);
} }
const unit = options.units === "imperial" ? "fahrenheit" : "celsius"; const unit = options.units === "imperial" ? "fahrenheit" : "celsius";
@ -94,134 +92,80 @@ export default function Widget({ options }) {
} }
return ( return (
<a href={options.url} target={settings.target ?? "_blank"} className={classNames( <Resources options={options} target={settings.target ?? "_blank"}>
"flex flex-col max-w:full sm:basis-auto self-center grow-0 flex-wrap", <SingleResource>
options?.styleBoxed === true && " mb-0 mt-2 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", <WidgetIcon icon={FiCpu} />
)}> <ResourceValue>{t("common.number", {
<div className="flex flex-row self-center flex-wrap justify-between"> value: data.cpu.total,
<div className="flex-none flex flex-row items-center mr-3 py-1.5"> style: "unit",
<FiCpu className="text-theme-800 dark:text-theme-200 w-5 h-5" /> unit: "percent",
<div className="flex flex-col ml-3 text-left min-w-[85px]"> maximumFractionDigits: 0,
<div className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between"> })}</ResourceValue>
<div className="pl-0.5"> <ResourceLabel>{t("glances.cpu")}</ResourceLabel>
{t("common.number", { <ResourceValue>{t("common.number", {
value: data.cpu.total, value: data.load.min15,
style: "unit", style: "unit",
unit: "percent", unit: "percent",
maximumFractionDigits: 0, maximumFractionDigits: 0,
})} })}</ResourceValue>
</div> <ResourceLabel>{t("glances.load")}</ResourceLabel>
<div className="pr-1">{t("glances.cpu")}</div> <UsageBar percent={data.cpu.total} />
</div> </SingleResource>
{options.expanded && ( <SingleResource>
<span className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between"> <WidgetIcon icon={FaMemory} />
<div className="pl-0.5 pr-1"> <ResourceValue>{t("common.bytes", {
{t("common.number", { value: data.mem.free,
value: data.load.min15, maximumFractionDigits: 1,
style: "unit", binary: true,
unit: "percent", })}</ResourceValue>
maximumFractionDigits: 0, <ResourceLabel>{t("glances.free")}</ResourceLabel>
})} <ResourceValue>{t("common.bytes", {
</div> value: data.mem.total,
<div className="pr-1">{t("glances.load")}</div> maximumFractionDigits: 1,
</span> binary: true,
)} })}</ResourceValue>
<UsageBar percent={data.cpu.total} /> <ResourceLabel>{t("glances.total")}</ResourceLabel>
</div> <UsageBar percent={data.mem.percent} />
</div> </SingleResource>
<div className="flex-none flex flex-row items-center mr-3 py-1.5"> {disks.map((disk) => (
<FaMemory className="text-theme-800 dark:text-theme-200 w-5 h-5" /> <SingleResource key={disk.mnt_point}>
<div className="flex flex-col ml-3 text-left min-w-[85px]"> <WidgetIcon icon={FiHardDrive} />
<div className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between"> <ResourceValue>{t("common.bytes", { value: disk.free })}</ResourceValue>
<div className="pl-0.5"> <ResourceLabel>{t("glances.free")}</ResourceLabel>
{t("common.bytes", { <ResourceValue>{t("common.bytes", { value: disk.size })}</ResourceValue>
value: data.mem.free, <ResourceLabel>{t("glances.total")}</ResourceLabel>
maximumFractionDigits: 1, <UsageBar percent={disk.percent} />
binary: true, </SingleResource>
})} ))}
</div> {options.cputemp && mainTemp > 0 &&
<div className="pr-1">{t("glances.free")}</div> <SingleResource>
</div> <WidgetIcon icon={FaThermometerHalf} />
{options.expanded && ( <ResourceValue>{t("common.number", {
<span className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between"> value: mainTemp,
<div className="pl-0.5 pr-1"> maximumFractionDigits: 1,
{t("common.bytes", { style: "unit",
value: data.mem.total, unit
maximumFractionDigits: 1, })}</ResourceValue>
binary: true, <ResourceLabel>{t("glances.temp")}</ResourceLabel>
})} <ResourceValue>{t("common.number", {
</div> value: maxTemp,
<div className="pr-1">{t("glances.total")}</div> maximumFractionDigits: 1,
</span> style: "unit",
)} unit
<UsageBar percent={data.mem.percent} /> })}</ResourceValue>
</div> <ResourceLabel>{t("glances.warn")}</ResourceLabel>
</div> <UsageBar percent={tempPercent} />
{disks.map((disk) => ( </SingleResource>
<div key={disk.mnt_point} className="flex-none flex flex-row items-center mr-3 py-1.5"> }
<FiHardDrive className="text-theme-800 dark:text-theme-200 w-5 h-5" /> {options.uptime && data.uptime &&
<div className="flex flex-col ml-3 text-left min-w-[85px]"> <SingleResource>
<span className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between"> <WidgetIcon icon={FaRegClock} />
<div className="pl-0.5">{t("common.bytes", { value: disk.free })}</div> <ResourceValue>{data.uptime.replace(" days,", t("glances.days")).replace(/:\d\d:\d\d$/g, t("glances.hours"))}</ResourceValue>
<div className="pr-1">{t("glances.free")}</div> <ResourceLabel>{t("glances.uptime")}</ResourceLabel>
</span> <UsageBar percent={Math.round((new Date().getSeconds() / 60) * 100)} />
{options.expanded && ( </SingleResource>
<span className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between"> }
<div className="pl-0.5 pr-1">{t("common.bytes", { value: disk.size })}</div> {options.label && <WidgetLabel label={options.label} />}
<div className="pr-1">{t("glances.total")}</div> </Resources>
</span>
)}
<UsageBar percent={disk.percent} />
</div>
</div>))}
{options.cputemp && mainTemp > 0 &&
(<div className="flex-none flex flex-row items-center mr-3 py-1.5">
<FaThermometerHalf className="text-theme-800 dark:text-theme-200 w-5 h-5" />
<div className="flex flex-col ml-3 text-left min-w-[85px]">
<span className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between">
<div className="pl-0.5">
{t("common.number", {
value: mainTemp,
maximumFractionDigits: 1,
style: "unit",
unit
})}
</div>
<div className="pr-1">{t("glances.temp")}</div>
</span>
{options.expanded && (
<span className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between">
<div className="pl-0.5 pr-1">
{t("common.number", {
value: maxTemp,
maximumFractionDigits: 1,
style: "unit",
unit
})}
</div>
<div className="pr-1">{t("glances.warn")}</div>
</span>
)}
<UsageBar percent={tempPercent} />
</div>
</div>)}
{options.uptime && data.uptime &&
(<div className="flex-none flex flex-row items-center mr-3 py-1.5">
<FaRegClock className="text-theme-800 dark:text-theme-200 w-5 h-5" />
<div className="flex flex-col ml-3 text-left min-w-[85px]">
<span className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between">
<div className="pl-0.5">
{data.uptime.replace(" days,", t("glances.days")).replace(/:\d\d:\d\d$/g, t("glances.hours"))}
</div>
<div className="pr-1">{t("glances.uptime")}</div>
</span>
<UsageBar percent={Math.round((new Date().getSeconds() / 60) * 100)} />
</div>
</div>)}
</div>
{options.label && (
<div className="pt-1 text-center text-theme-800 dark:text-theme-200 text-xs">{options.label}</div>
)}
</a>
); );
} }

@ -1,4 +1,5 @@
import classNames from "classnames"; import Container from "../widget/container";
import Raw from "../widget/raw";
const textSizes = { const textSizes = {
"4xl": "text-4xl", "4xl": "text-4xl",
@ -13,15 +14,12 @@ const textSizes = {
export default function Greeting({ options }) { export default function Greeting({ options }) {
if (options.text) { if (options.text) {
return ( return <Container options={options}>
<div className={classNames( <Raw>
"flex flex-row items-center justify-start",
options?.styleBoxed === true && " ml-4 mt-2 m: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",
)}>
<span className={`text-theme-800 dark:text-theme-200 mr-3 ${textSizes[options.text_size || "xl"]}`}> <span className={`text-theme-800 dark:text-theme-200 mr-3 ${textSizes[options.text_size || "xl"]}`}>
{options.text} {options.text}
</span> </span>
</div> </Raw>
); </Container>;
} }
} }

@ -1,8 +1,9 @@
import useSWR from "swr"; import useSWR from "swr";
import { useTranslation } from "next-i18next"; import { useTranslation } from "next-i18next";
import classNames from "classnames";
import Error from "../error"; import Error from "../widget/error";
import Container from "../widget/container";
import Raw from "../widget/raw";
import Node from "./node"; import Node from "./node";
@ -20,7 +21,7 @@ export default function Widget({ options }) {
used: 0, used: 0,
total: 0, total: 0,
free: 0, free: 0,
precent: 0 percent: 0
} }
}; };
@ -35,11 +36,8 @@ export default function Widget({ options }) {
} }
if (!data) { if (!data) {
return ( return <Container options={options}>
<div className={classNames( <Raw>
"flex flex-col max-w:full sm:basis-auto self-center grow-0 flex-wrap",
options?.styleBoxed === true && " ml-4 mt-2 m: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",
)}>
<div className="flex flex-row self-center flex-wrap justify-between"> <div className="flex flex-row self-center flex-wrap justify-between">
{cluster.show && {cluster.show &&
<Node type="cluster" key="cluster" options={options.cluster} data={defaultData} /> <Node type="cluster" key="cluster" options={options.cluster} data={defaultData} />
@ -48,15 +46,12 @@ export default function Widget({ options }) {
<Node type="node" key="nodes" options={options.nodes} data={defaultData} /> <Node type="node" key="nodes" options={options.nodes} data={defaultData} />
} }
</div> </div>
</div> </Raw>
); </Container>;
} }
return ( return <Container options={options}>
<div className={classNames( <Raw>
"flex flex-col max-w:full sm:basis-auto self-center grow-0 flex-wrap",
options?.styleBoxed === true && " ml-4 mt-2 m: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",
)}>
<div className="flex flex-row self-center flex-wrap justify-between"> <div className="flex flex-row self-center flex-wrap justify-between">
{cluster.show && {cluster.show &&
<Node key="cluster" type="cluster" options={options.cluster} data={data.cluster} /> <Node key="cluster" type="cluster" options={options.cluster} data={data.cluster} />
@ -66,6 +61,6 @@ export default function Widget({ options }) {
<Node key={node.name} type="node" options={options.nodes} data={node} />) <Node key={node.name} type="node" options={options.nodes} data={node} />)
} }
</div> </div>
</div> </Raw>
); </Container>;
} }

@ -3,8 +3,7 @@ import { FiAlertTriangle, FiCpu, FiServer } from "react-icons/fi";
import { SiKubernetes } from "react-icons/si"; import { SiKubernetes } from "react-icons/si";
import { useTranslation } from "next-i18next"; import { useTranslation } from "next-i18next";
import UsageBar from "./usage-bar"; import UsageBar from "../resources/usage-bar";
export default function Node({ type, options, data }) { export default function Node({ type, options, data }) {
const { t } = useTranslation(); const { t } = useTranslation();
@ -29,7 +28,7 @@ export default function Node({ type, options, data }) {
<div className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between"> <div className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between">
<div className="pl-0.5"> <div className="pl-0.5">
{t("common.number", { {t("common.number", {
value: data.cpu.percent, value: data?.cpu?.percent ?? 0,
style: "unit", style: "unit",
unit: "percent", unit: "percent",
maximumFractionDigits: 0 maximumFractionDigits: 0
@ -37,18 +36,18 @@ export default function Node({ type, options, data }) {
</div> </div>
<FiCpu className="text-theme-800 dark:text-theme-200 w-3 h-3" /> <FiCpu className="text-theme-800 dark:text-theme-200 w-3 h-3" />
</div> </div>
<UsageBar percent={data.cpu.percent} /> <UsageBar percent={data?.cpu?.percent ?? 0} />
<div className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between"> <div className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between">
<div className="pl-0.5"> <div className="pl-0.5">
{t("common.bytes", { {t("common.bytes", {
value: data.memory.free, value: data?.memory?.free ?? 0,
maximumFractionDigits: 0, maximumFractionDigits: 0,
binary: true binary: true
})} })}
</div> </div>
<FaMemory className="text-theme-800 dark:text-theme-200 w-3 h-3" /> <FaMemory className="text-theme-800 dark:text-theme-200 w-3 h-3" />
</div> </div>
<UsageBar percent={data.memory.percent} /> <UsageBar percent={data?.memory?.percent} />
{options.showLabel && ( {options.showLabel && (
<div className="pt-1 text-center text-theme-800 dark:text-theme-200 text-xs">{type === "cluster" ? options.label : data.name}</div> <div className="pt-1 text-center text-theme-800 dark:text-theme-200 text-xs">{type === "cluster" ? options.label : data.name}</div>
)} )}

@ -1,12 +0,0 @@
export default function UsageBar({ percent }) {
return (
<div className="mt-0.5 w-full bg-theme-800/30 rounded-full h-1 dark:bg-theme-200/20">
<div
className="bg-theme-800/70 h-1 rounded-full dark:bg-theme-200/50 transition-all duration-1000"
style={{
width: `${percent}%`,
}}
/>
</div>
);
}

@ -1,14 +1,13 @@
import classNames from "classnames"; import Container from "../widget/container";
import Raw from "../widget/raw";
import ResolvedIcon from "components/resolvedicon" import ResolvedIcon from "components/resolvedicon"
export default function Logo({ options }) { export default function Logo({ options }) {
return ( return (
<div className={classNames( <Container options={options}>
"w-12 h-12 flex flex-row items-center align-middle mr-3 self-center", <Raw>
options?.styleBoxed === true && " ml-4 mt-2 m: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", {options.icon ?
)}>
{options.icon ?
<ResolvedIcon icon={options.icon} width={48} height={48} /> : <ResolvedIcon icon={options.icon} width={48} height={48} /> :
// fallback to homepage logo // fallback to homepage logo
<svg <svg
@ -62,6 +61,7 @@ export default function Logo({ options }) {
</g> </g>
</svg> </svg>
} }
</div> </Raw>
</Container>
) )
} }

@ -1,7 +1,8 @@
import useSWR from "swr"; import useSWR from "swr";
import classNames from "classnames";
import Error from "../error"; import Error from "../widget/error";
import Container from "../widget/container";
import Raw from "../widget/raw";
import Node from "./node"; import Node from "./node";
@ -16,21 +17,15 @@ export default function Longhorn({ options }) {
} }
if (!data) { if (!data) {
return ( return <Container options={options}>
<div className={classNames( <Raw>
"flex flex-col max-w:full sm:basis-auto self-center grow-0 flex-wrap",
options?.styleBoxed === true && " ml-4 mt-2 m: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",
)}>
<div className="flex flex-row self-center flex-wrap justify-between" /> <div className="flex flex-row self-center flex-wrap justify-between" />
</div> </Raw>
); </Container>;
} }
return ( return <Container options={options}>
<div className={classNames( <Raw>
"flex flex-col max-w:full sm:basis-auto self-center grow-0 flex-wrap",
options?.styleBoxed === true && " ml-4 mt-2 m: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",
)}>
<div className="flex flex-row self-center flex-wrap justify-between"> <div className="flex flex-row self-center flex-wrap justify-between">
{data.nodes {data.nodes
.filter((node) => { .filter((node) => {
@ -51,6 +46,6 @@ export default function Longhorn({ options }) {
</div> </div>
)} )}
</div> </div>
</div> </Raw>
); </Container>;
} }

@ -1,32 +1,23 @@
import { FiHardDrive } from "react-icons/fi";
import { useTranslation } from "next-i18next"; import { useTranslation } from "next-i18next";
import { FaThermometerHalf } from "react-icons/fa";
import UsageBar from "../resources/usage-bar"; import UsageBar from "../resources/usage-bar";
import SingleResource from "../widget/single_resource";
import WidgetIcon from "../widget/widget_icon";
import ResourceValue from "../widget/resource_value";
import ResourceLabel from "../widget/resource_label";
import WidgetLabel from "../widget/widget_label";
export default function Node({ data, expanded, labels }) { export default function Node({ data, expanded, labels }) {
const { t } = useTranslation(); const { t } = useTranslation();
return ( return <SingleResource expanded={expanded}>
<> <WidgetIcon icon={FaThermometerHalf} />
<div className="flex-none flex flex-row items-center mr-3 py-1.5"> <ResourceValue>{t("common.bytes", { value: data.node.available })}</ResourceValue>
<FiHardDrive className="text-theme-800 dark:text-theme-200 w-5 h-5" /> <ResourceLabel>{t("resources.free")}</ResourceLabel>
<div className="flex flex-col ml-3 text-left min-w-[85px]"> <ResourceValue>{t("common.bytes", { value: data.node.maximum })}</ResourceValue>
<span className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between"> <ResourceLabel>{t("resources.total")}</ResourceLabel>
<div className="pl-0.5">{t("common.bytes", { value: data.node.available })}</div> <UsageBar percent={Math.round(((data.node.maximum - data.node.available) / data.node.maximum) * 100)} />
<div className="pr-1">{t("resources.free")}</div> { labels && <WidgetLabel label={data.node.id} /> }
</span> </SingleResource>
{expanded && (
<span className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between">
<div className="pl-0.5">{t("common.bytes", { value: data.node.maximum })}</div>
<div className="pr-1">{t("resources.total")}</div>
</span>
)}
<UsageBar percent={Math.round(((data.node.maximum - data.node.available) / data.node.maximum) * 100)} />
</div>
</div>
{labels && (
<div className="ml-6 pt-1 text-center text-theme-800 dark:text-theme-200 text-xs">{data.node.id}</div>
)}
</>
);
} }

@ -3,9 +3,13 @@ import { useState } from "react";
import { WiCloudDown } from "react-icons/wi"; import { WiCloudDown } from "react-icons/wi";
import { MdLocationDisabled, MdLocationSearching } from "react-icons/md"; import { MdLocationDisabled, MdLocationSearching } from "react-icons/md";
import { useTranslation } from "next-i18next"; import { useTranslation } from "next-i18next";
import classNames from "classnames";
import Error from "../error"; import Error from "../widget/error";
import Container from "../widget/container";
import ContainerButton from "../widget/container_button";
import WidgetIcon from "../widget/widget_icon";
import PrimaryText from "../widget/primary_text";
import SecondaryText from "../widget/secondary_text";
import Icon from "./icon"; import Icon from "./icon";
@ -21,50 +25,31 @@ function Widget({ options }) {
} }
if (!data) { if (!data) {
return ( return <Container options={options}>
<div className={classNames( <PrimaryText>{t("weather.updating")}</PrimaryText>
"flex flex-col justify-center first:ml-0 ml-4 mr-2", <SecondaryText>{t("weather.wait")}</SecondaryText>
options?.styleBoxed === true && " ml-4 mt-2 m: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", <WidgetIcon icon={WiCloudDown} size="l" />
)}> </Container>;
<div className="flex flex-row items-center justify-end">
<div className="flex flex-col items-center">
<WiCloudDown className="w-8 h-8 text-theme-800 dark:text-theme-200" />
</div>
<div className="flex flex-col ml-3 text-left">
<span className="text-theme-800 dark:text-theme-200 text-sm">{t("weather.updating")}</span>
<span className="text-theme-800 dark:text-theme-200 text-xs">{t("weather.wait")}</span>
</div>
</div>
</div>
);
} }
const unit = options.units === "metric" ? "celsius" : "fahrenheit"; const unit = options.units === "metric" ? "celsius" : "fahrenheit";
const timeOfDay = data.current_weather.time > data.daily.sunrise[0] && data.current_weather.time < data.daily.sunset[0] ? "day" : "night"; const weatherInfo = {
condition: data.current_weather.weathercode,
timeOfDay: data.current_weather.time > data.daily.sunrise[0] && data.current_weather.time < data.daily.sunset[0] ? "day" : "night"
};
return ( return <Container options={options}>
<div className={classNames( <PrimaryText>
"flex flex-col justify-center first:ml-0 ml-4 mr-2", {options.label && `${options.label}, `}
options?.styleBoxed === true && " ml-4 mt-2 m: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", {t("common.number", {
)}> value: data.current_weather.temperature,
<div className="flex flex-row items-center justify-end"> style: "unit",
<div className="flex flex-col items-center"> unit,
<Icon condition={data.current_weather.weathercode} timeOfDay={timeOfDay} /> })}
</div> </PrimaryText>
<div className="flex flex-col ml-3 text-left"> <SecondaryText>{t(`wmo.${data.current_weather.weathercode}-${weatherInfo.timeOfDay}`)}</SecondaryText>
<span className="text-theme-800 dark:text-theme-200 text-sm"> <WidgetIcon icon={Icon} size="xl" weatherInfo={weatherInfo} />
{options.label && `${options.label}, `} </Container>;
{t("common.number", {
value: data.current_weather.temperature,
style: "unit",
unit,
})}
</span>
<span className="text-theme-800 dark:text-theme-200 text-xs">{t(`wmo.${data.current_weather.weathercode}-${timeOfDay}`)}</span>
</div>
</div>
</div>
);
} }
export default function OpenMeteo({ options }) { export default function OpenMeteo({ options }) {
@ -99,29 +84,11 @@ export default function OpenMeteo({ options }) {
// if (!requesting && !location) requestLocation(); // if (!requesting && !location) requestLocation();
if (!location) { if (!location) {
return ( return <ContainerButton options={options} callback={requestLocation} >
<button <PrimaryText>{t("weather.current")}</PrimaryText>
type="button" <SecondaryText>{t("weather.allow")}</SecondaryText>
onClick={() => requestLocation()} <WidgetIcon icon={ requesting ? MdLocationSearching : MdLocationDisabled} size="m" pulse />
className={classNames( </ContainerButton>;
"flex flex-col justify-center first:ml-0 ml-4 mr-2",
options?.styleBoxed === true && " ml-4 mt-2 m: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",
)}>
<div className="flex flex-row items-center justify-end">
<div className="flex flex-col items-center">
{requesting ? (
<MdLocationSearching className="w-6 h-6 text-theme-800 dark:text-theme-200 animate-pulse" />
) : (
<MdLocationDisabled className="w-6 h-6 text-theme-800 dark:text-theme-200" />
)}
</div>
<div className="flex flex-col ml-3 text-left">
<span className="text-theme-800 dark:text-theme-200 text-sm">{t("weather.current")}</span>
<span className="text-theme-800 dark:text-theme-200 text-xs">{t("weather.allow")}</span>
</div>
</div>
</button>
);
} }
return <Widget options={{ ...location, ...options }} />; return <Widget options={{ ...location, ...options }} />;

@ -3,12 +3,17 @@ import { useState } from "react";
import { WiCloudDown } from "react-icons/wi"; import { WiCloudDown } from "react-icons/wi";
import { MdLocationDisabled, MdLocationSearching } from "react-icons/md"; import { MdLocationDisabled, MdLocationSearching } from "react-icons/md";
import { useTranslation } from "next-i18next"; import { useTranslation } from "next-i18next";
import classNames from "classnames";
import Error from "../error"; import Error from "../widget/error";
import Container from "../widget/container";
import ContainerButton from "../widget/container_button";
import PrimaryText from "../widget/primary_text";
import SecondaryText from "../widget/secondary_text";
import WidgetIcon from "../widget/widget_icon";
import Icon from "./icon"; import Icon from "./icon";
function Widget({ options }) { function Widget({ options }) {
const { t, i18n } = useTranslation(); const { t, i18n } = useTranslation();
@ -21,48 +26,26 @@ function Widget({ options }) {
} }
if (!data) { if (!data) {
return ( return <Container options={options}>
<div className={classNames( <PrimaryText>{t("weather.updating")}</PrimaryText>
"flex flex-col justify-center first:ml-auto ml-4 mr-2", <SecondaryText>{t("weather.wait")}</SecondaryText>
options?.styleBoxed === true && " ml-4 mt-2 m: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", <WidgetIcon icon={WiCloudDown} size="l" />
)}> </Container>;
<div className="flex flex-row items-center justify-end">
<div className="hidden sm:flex flex-col items-center">
<WiCloudDown className="w-8 h-8 text-theme-800 dark:text-theme-200" />
</div>
<div className="flex flex-col ml-3 text-left">
<span className="text-theme-800 dark:text-theme-200 text-sm">{t("weather.updating")}</span>
<span className="text-theme-800 dark:text-theme-200 text-xs">{t("weather.wait")}</span>
</div>
</div>
</div>
);
} }
const unit = options.units === "metric" ? "celsius" : "fahrenheit"; const unit = options.units === "metric" ? "celsius" : "fahrenheit";
return ( const weatherInfo = {
<div className={classNames( condition: data.weather[0].id,
"flex flex-col justify-center first:ml-auto ml-2 mr-2", timeOfDay: data.dt > data.sys.sunrise && data.dt < data.sys.sunset ? "day" : "night"
options?.styleBoxed === true && " ml-4 mt-2 m: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", };
)}>
<div className="flex flex-row items-center justify-end"> return <Container options={options}>
<div className="hidden sm:flex flex-col items-center"> <PrimaryText>{options.label && `${options.label}, `}</PrimaryText>
<Icon <PrimaryText>{t("common.number", { value: data.main.temp, style: "unit", unit })}</PrimaryText>
condition={data.weather[0].id} <SecondaryText>{data.weather[0].description}</SecondaryText>
timeOfDay={data.dt > data.sys.sunrise && data.dt < data.sys.sunset ? "day" : "night"} <WidgetIcon icon={Icon} size="xl" weatherInfo={weatherInfo} />
/> </Container>;
</div>
<div className="flex flex-col ml-3 text-left">
<span className="text-theme-800 dark:text-theme-200 text-sm">
{options.label && `${options.label}, `}
{t("common.number", { value: data.main.temp, style: "unit", unit })}
</span>
<span className="text-theme-800 dark:text-theme-200 text-xs">{data.weather[0].description}</span>
</div>
</div>
</div>
);
} }
export default function OpenWeatherMap({ options }) { export default function OpenWeatherMap({ options }) {
@ -94,33 +77,12 @@ export default function OpenWeatherMap({ options }) {
} }
}; };
// if (!requesting && !location) requestLocation();
if (!location) { if (!location) {
return ( return <ContainerButton options={options} callback={requestLocation} >
<button <PrimaryText>{t("weather.current")}</PrimaryText>
type="button" <SecondaryText>{t("weather.allow")}</SecondaryText>
onClick={() => requestLocation()} <WidgetIcon icon={requesting ? MdLocationSearching : MdLocationDisabled} size="m" pulse />
className={classNames( </ContainerButton>;
"flex flex-col justify-center first:ml-auto ml-4 mr-2",
options?.styleBoxed === true && " ml-4 mt-2 m: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",
)}
>
<div className="flex flex-row items-center justify-end">
<div className="hidden sm:flex flex-col items-center">
{requesting ? (
<MdLocationSearching className="w-6 h-6 text-theme-800 dark:text-theme-200 animate-pulse" />
) : (
<MdLocationDisabled className="w-6 h-6 text-theme-800 dark:text-theme-200" />
)}
</div>
<div className="flex flex-col ml-3 text-left">
<span className="text-theme-800 dark:text-theme-200 text-sm">{t("weather.current")}</span>
<span className="text-theme-800 dark:text-theme-200 text-xs">{t("weather.allow")}</span>
</div>
</div>
</button>
);
} }
return <Widget options={{ ...location, ...options }} />; return <Widget options={{ ...location, ...options }} />;

@ -1,8 +1,13 @@
import useSWR from "swr"; import useSWR from "swr";
import { FiCpu } from "react-icons/fi"; import { FiCpu } from "react-icons/fi";
import { BiError } from "react-icons/bi";
import { useTranslation } from "next-i18next"; import { useTranslation } from "next-i18next";
import SingleResource from "../widget/single_resource";
import WidgetIcon from "../widget/widget_icon";
import ResourceValue from "../widget/resource_value";
import ResourceLabel from "../widget/resource_label";
import Error from "../widget/error";
import UsageBar from "./usage-bar"; import UsageBar from "./usage-bar";
export default function Cpu({ expanded }) { export default function Cpu({ expanded }) {
@ -13,67 +18,38 @@ export default function Cpu({ expanded }) {
}); });
if (error || data?.error) { if (error || data?.error) {
return ( return <Error />
<div className="flex-none flex flex-row items-center mr-3 py-1.5">
<BiError className="text-theme-800 dark:text-theme-200 w-5 h-5" />
<div className="flex flex-col ml-3 text-left">
<span className="text-theme-800 dark:text-theme-200 text-xs">{t("widget.api_error")}</span>
</div>
</div>
);
} }
if (!data) { if (!data) {
return ( return <SingleResource expanded={expanded}>
<div className="flex-none flex flex-row items-center mr-3 py-1.5 animate-pulse"> <WidgetIcon icon={FiCpu} />
<FiCpu className="text-theme-800 dark:text-theme-200 w-5 h-5" /> <ResourceValue>-</ResourceValue>
<div className="flex flex-col ml-3 text-left min-w-[85px]"> <ResourceLabel>{t("resources.cpu")}</ResourceLabel>
<div className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between"> <ResourceValue>-</ResourceValue>
<div className="pl-0.5 pr-1">-</div> <ResourceLabel>{t("resources.load")}</ResourceLabel>
<div className="pr-1">{t("resources.cpu")}</div> <UsageBar percent={0} />
</div> </SingleResource>
{expanded && (
<div className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between">
<div className="pl-0.5 pr-1">-</div>
<div className="pr-1">{t("resources.load")}</div>
</div>
)}
<UsageBar percent={0} />
</div>
</div>
);
} }
const percent = data.cpu.usage; return <SingleResource expanded={expanded}>
<WidgetIcon icon={FiCpu} />
return ( <ResourceValue>
<div className="flex-none flex flex-row items-center mr-3 py-1.5"> {t("common.number", {
<FiCpu className="text-theme-800 dark:text-theme-200 w-5 h-5" /> value: data.cpu.usage,
<div className="flex flex-col ml-3 text-left min-w-[85px]"> style: "unit",
<div className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between"> unit: "percent",
<div className="pl-0.5 pr-1"> maximumFractionDigits: 0,
{t("common.number", { })}
value: data.cpu.usage, </ResourceValue>
style: "unit", <ResourceLabel>{t("resources.cpu")}</ResourceLabel>
unit: "percent", <ResourceValue>
maximumFractionDigits: 0, {t("common.number", {
})} value: data.cpu.load,
</div> maximumFractionDigits: 2,
<div className="pr-1">{t("resources.cpu")}</div> })}
</div> </ResourceValue>
{expanded && ( <ResourceLabel>{t("resources.load")}</ResourceLabel>
<div className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between"> <UsageBar percent={data.cpu.usage} />
<div className="pl-0.5 pr-1"> </SingleResource>
{t("common.number", {
value: data.cpu.load,
maximumFractionDigits: 2,
})}
</div>
<div className="pr-1">{t("resources.load")}</div>
</div>
)}
<UsageBar percent={percent} />
</div>
</div>
);
} }

@ -1,8 +1,13 @@
import useSWR from "swr"; import useSWR from "swr";
import { FaThermometerHalf } from "react-icons/fa"; import { FaThermometerHalf } from "react-icons/fa";
import { BiError } from "react-icons/bi";
import { useTranslation } from "next-i18next"; import { useTranslation } from "next-i18next";
import SingleResource from "../widget/single_resource";
import WidgetIcon from "../widget/widget_icon";
import ResourceValue from "../widget/resource_value";
import ResourceLabel from "../widget/resource_label";
import Error from "../widget/error";
import UsageBar from "./usage-bar"; import UsageBar from "./usage-bar";
function convertToFahrenheit(t) { function convertToFahrenheit(t) {
@ -17,34 +22,17 @@ export default function CpuTemp({ expanded, units }) {
}); });
if (error || data?.error) { if (error || data?.error) {
return ( return <Error />
<div className="flex-none flex flex-row items-center mr-3 py-1.5">
<BiError className="text-theme-800 dark:text-theme-200 w-5 h-5" />
<div className="flex flex-col ml-3 text-left">
<span className="text-theme-800 dark:text-theme-200 text-xs">{t("widget.api_error")}</span>
</div>
</div>
);
} }
if (!data || !data.cputemp) { if (!data || !data.cputemp) {
return ( return <SingleResource expanded={expanded}>
<div className="flex-none flex flex-row items-center mr-3 py-1.5 animate-pulse"> <WidgetIcon icon={FaThermometerHalf} />
<FaThermometerHalf className="text-theme-800 dark:text-theme-200 w-5 h-5" /> <ResourceValue>-</ResourceValue>
<div className="flex flex-col ml-3 text-left min-w-[85px]"> <ResourceLabel>{t("resources.temp")}</ResourceLabel>
<span className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between"> <ResourceValue>-</ResourceValue>
<div className="pl-0.5">-</div> <ResourceLabel>{t("resources.max")}</ResourceLabel>
<div className="pr-1">{t("resources.temp")}</div> </SingleResource>
</span>
{expanded && (
<span className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between">
<div className="pl-0.5">-</div>
<div className="pr-1">{t("resources.max")}</div>
</span>
)}
</div>
</div>
);
} }
let mainTemp = data.cputemp.main; let mainTemp = data.cputemp.main;
@ -54,38 +42,27 @@ export default function CpuTemp({ expanded, units }) {
const unit = units === "imperial" ? "fahrenheit" : "celsius"; const unit = units === "imperial" ? "fahrenheit" : "celsius";
mainTemp = (unit === "celsius") ? mainTemp : convertToFahrenheit(mainTemp); mainTemp = (unit === "celsius") ? mainTemp : convertToFahrenheit(mainTemp);
const maxTemp = (unit === "celsius") ? data.cputemp.max : convertToFahrenheit(data.cputemp.max); const maxTemp = (unit === "celsius") ? data.cputemp.max : convertToFahrenheit(data.cputemp.max);
const percent = Math.round((mainTemp / maxTemp) * 100);
return ( return <SingleResource expanded={expanded}>
<div className="flex-none flex flex-row items-center mr-3 py-1.5"> <WidgetIcon icon={FaThermometerHalf} />
<FaThermometerHalf className="text-theme-800 dark:text-theme-200 w-5 h-5" /> <ResourceValue>
<div className="flex flex-col ml-3 text-left min-w-[85px]"> {t("common.number", {
<span className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between"> value: mainTemp,
<div className="pl-0.5"> maximumFractionDigits: 1,
{t("common.number", { style: "unit",
value: mainTemp, unit
maximumFractionDigits: 1, })}
style: "unit", </ResourceValue>
unit <ResourceLabel>{t("resources.temp")}</ResourceLabel>
})} <ResourceValue>
</div> {t("common.number", {
<div className="pr-1">{t("resources.temp")}</div> value: maxTemp,
</span> maximumFractionDigits: 1,
{expanded && ( style: "unit",
<span className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between"> unit
<div className="pl-0.5"> })}
{t("common.number", { </ResourceValue>
value: maxTemp, <ResourceLabel>{t("resources.max")}</ResourceLabel>
maximumFractionDigits: 1, <UsageBar percent={Math.round((mainTemp / maxTemp) * 100)} />
style: "unit", </SingleResource>;
unit
})}
</div>
<div className="pr-1">{t("resources.max")}</div>
</span>
)}
<UsageBar percent={percent} />
</div>
</div>
);
} }

@ -1,8 +1,13 @@
import useSWR from "swr"; import useSWR from "swr";
import { FiHardDrive } from "react-icons/fi"; import { FiHardDrive } from "react-icons/fi";
import { BiError } from "react-icons/bi";
import { useTranslation } from "next-i18next"; import { useTranslation } from "next-i18next";
import SingleResource from "../widget/single_resource";
import WidgetIcon from "../widget/widget_icon";
import ResourceValue from "../widget/resource_value";
import ResourceLabel from "../widget/resource_label";
import Error from "../widget/error";
import UsageBar from "./usage-bar"; import UsageBar from "./usage-bar";
export default function Disk({ options, expanded }) { export default function Disk({ options, expanded }) {
@ -13,56 +18,29 @@ export default function Disk({ options, expanded }) {
}); });
if (error || data?.error) { if (error || data?.error) {
return ( return <Error options={options} />
<div className="flex-none flex flex-row items-center mr-3 py-1.5">
<BiError className="text-theme-800 dark:text-theme-200 w-5 h-5" />
<div className="flex flex-col ml-3 text-left">
<span className="text-theme-800 dark:text-theme-200 text-xs">{t("widget.api_error")}</span>
</div>
</div>
);
} }
if (!data) { if (!data) {
return ( return <SingleResource expanded={expanded}>
<div className="flex-none flex flex-row items-center mr-3 py-1.5 animate-pulse"> <WidgetIcon icon={FiHardDrive} />
<FiHardDrive className="text-theme-800 dark:text-theme-200 w-5 h-5" /> <ResourceValue>-</ResourceValue>
<div className="flex flex-col ml-3 text-left min-w-[85px]"> <ResourceLabel>{t("resources.free")}</ResourceLabel>
<span className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between"> <ResourceValue>-</ResourceValue>
<div className="pl-0.5 pr-1">-</div> <ResourceLabel>{t("resources.total")}</ResourceLabel>
<div className="pr-1">{t("resources.free")}</div> <UsageBar percent={0} />
</span> </SingleResource>;
{expanded && (
<span className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between">
<div className="pl-0.5 pr-1">-</div>
<div className="pr-1">{t("resources.total")}</div>
</span>
)}
<UsageBar percent={0} />
</div>
</div>
);
} }
// data.drive.used not accurate? // data.drive.used not accurate?
const percent = Math.round(((data.drive.size - data.drive.available) / data.drive.size) * 100); const percent = Math.round(((data.drive.size - data.drive.available) / data.drive.size) * 100);
return ( return <SingleResource expanded={expanded}>
<div className="flex-none flex flex-row items-center mr-3 py-1.5"> <WidgetIcon icon={FiHardDrive} />
<FiHardDrive className="text-theme-800 dark:text-theme-200 w-5 h-5" /> <ResourceValue>{t("common.bytes", { value: data.drive.available })}</ResourceValue>
<div className="flex flex-col ml-3 text-left min-w-[85px]"> <ResourceLabel>{t("resources.free")}</ResourceLabel>
<span className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between"> <ResourceValue>{t("common.bytes", { value: data.drive.size })}</ResourceValue>
<div className="pl-0.5 pr-1">{t("common.bytes", { value: data.drive.available })}</div> <ResourceLabel>{t("resources.total")}</ResourceLabel>
<div className="pr-1">{t("resources.free")}</div> <UsageBar percent={percent} />
</span> </SingleResource>;
{expanded && (
<span className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between">
<div className="pl-0.5 pr-1">{t("common.bytes", { value: data.drive.size })}</div>
<div className="pr-1">{t("resources.total")}</div>
</span>
)}
<UsageBar percent={percent} />
</div>
</div>
);
} }

@ -1,8 +1,13 @@
import useSWR from "swr"; import useSWR from "swr";
import { FaMemory } from "react-icons/fa"; import { FaMemory } from "react-icons/fa";
import { BiError } from "react-icons/bi";
import { useTranslation } from "next-i18next"; import { useTranslation } from "next-i18next";
import SingleResource from "../widget/single_resource";
import WidgetIcon from "../widget/widget_icon";
import ResourceValue from "../widget/resource_value";
import ResourceLabel from "../widget/resource_label";
import Error from "../widget/error";
import UsageBar from "./usage-bar"; import UsageBar from "./usage-bar";
export default function Memory({ expanded }) { export default function Memory({ expanded }) {
@ -13,63 +18,34 @@ export default function Memory({ expanded }) {
}); });
if (error || data?.error) { if (error || data?.error) {
return ( return <Error />
<div className="flex-none flex flex-row items-center mr-3 py-1.5">
<BiError className="text-theme-800 dark:text-theme-200 w-5 h-5" />
<div className="flex flex-col ml-3 text-left">
<span className="text-theme-800 dark:text-theme-200 text-xs">{t("widget.api_error")}</span>
</div>
</div>
);
} }
if (!data) { if (!data) {
return ( return <SingleResource expanded={expanded}>
<div className="flex-none flex flex-row items-center mr-3 py-1.5 animate-pulse"> <WidgetIcon icon={FaMemory} />
<FaMemory className="text-theme-800 dark:text-theme-200 w-5 h-5" /> <ResourceValue>-</ResourceValue>
<div className="flex flex-col ml-3 text-left min-w-[85px]"> <ResourceLabel>{t("resources.free")}</ResourceLabel>
<span className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between"> <ResourceValue>-</ResourceValue>
<div className="pl-0.5 pr-1">-</div> <ResourceLabel>{t("resources.total")}</ResourceLabel>
<div className="pr-1">{t("resources.free")}</div> <UsageBar percent={0} />
</span> </SingleResource>;
{expanded && (
<span className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between">
<div className="pl-0.5 pr-1">-</div>
<div className="pr-1">{t("resources.total")}</div>
</span>
)}
<UsageBar percent={0} />
</div>
</div>
);
} }
const percent = Math.round((data.memory.active / data.memory.total) * 100); const percent = Math.round((data.memory.active / data.memory.total) * 100);
return ( return <SingleResource expanded={expanded}>
<div className="flex-none flex flex-row items-center mr-3 py-1.5"> <WidgetIcon icon={FaMemory} />
<FaMemory className="text-theme-800 dark:text-theme-200 w-5 h-5" /> <ResourceValue>{t("common.bytes", { value: data.memory.available, maximumFractionDigits: 1, binary: true })}</ResourceValue>
<div className="flex flex-col ml-3 text-left min-w-[85px]"> <ResourceLabel>{t("resources.free")}</ResourceLabel>
<span className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between"> <ResourceValue>
<div className="pl-0.5 pr-1"> {t("common.bytes", {
{t("common.bytes", { value: data.memory.available, maximumFractionDigits: 1, binary: true })} value: data.memory.total,
</div> maximumFractionDigits: 1,
<div className="pr-1">{t("resources.free")}</div> binary: true,
</span> })}
{expanded && ( </ResourceValue>
<span className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between"> <ResourceLabel>{t("resources.total")}</ResourceLabel>
<div className="pl-0.5 pr-1"> <UsageBar percent={percent} />
{t("common.bytes", { </SingleResource>;
value: data.memory.total,
maximumFractionDigits: 1,
binary: true,
})}
</div>
<div className="pr-1">{t("resources.total")}</div>
</span>
)}
<UsageBar percent={percent} />
</div>
</div>
);
} }

@ -1,4 +1,5 @@
import classNames from "classnames"; import Container from "../widget/container";
import Raw from "../widget/raw";
import Disk from "./disk"; import Disk from "./disk";
import Cpu from "./cpu"; import Cpu from "./cpu";
@ -8,11 +9,8 @@ import Uptime from "./uptime";
export default function Resources({ options }) { export default function Resources({ options }) {
const { expanded, units } = options; const { expanded, units } = options;
return ( return <Container options={options}>
<div className={classNames( <Raw>
"flex flex-col max-w:full sm:basis-auto self-center grow-0 flex-wrap",
options?.styleBoxed === true && " ml-4 mt-2 m: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",
)}>
<div className="flex flex-row self-center flex-wrap justify-between"> <div className="flex flex-row self-center flex-wrap justify-between">
{options.cpu && <Cpu expanded={expanded} />} {options.cpu && <Cpu expanded={expanded} />}
{options.memory && <Memory expanded={expanded} />} {options.memory && <Memory expanded={expanded} />}
@ -25,6 +23,6 @@ export default function Resources({ options }) {
{options.label && ( {options.label && (
<div className="ml-6 pt-1 text-center text-theme-800 dark:text-theme-200 text-xs">{options.label}</div> <div className="ml-6 pt-1 text-center text-theme-800 dark:text-theme-200 text-xs">{options.label}</div>
)} )}
</div> </Raw>
); </Container>;
} }

@ -1,8 +1,13 @@
import useSWR from "swr"; import useSWR from "swr";
import { FaRegClock } from "react-icons/fa"; import { FaRegClock } from "react-icons/fa";
import { BiError } from "react-icons/bi";
import { useTranslation } from "next-i18next"; import { useTranslation } from "next-i18next";
import SingleResource from "../widget/single_resource";
import WidgetIcon from "../widget/widget_icon";
import ResourceValue from "../widget/resource_value";
import ResourceLabel from "../widget/resource_label";
import Error from "../widget/error";
import UsageBar from "./usage-bar"; import UsageBar from "./usage-bar";
export default function Uptime() { export default function Uptime() {
@ -13,35 +18,22 @@ export default function Uptime() {
}); });
if (error || data?.error) { if (error || data?.error) {
return ( return <Error />
<div className="flex-none flex flex-row items-center mr-3 py-1.5">
<BiError className="text-theme-800 dark:text-theme-200 w-5 h-5" />
<div className="flex flex-col ml-3 text-left">
<span className="text-theme-800 dark:text-theme-200 text-xs">{t("widget.api_error")}</span>
</div>
</div>
);
} }
if (!data) { if (!data) {
return ( return <SingleResource>
<div className="flex-none flex flex-row items-center mr-3 py-1.5 animate-pulse"> <WidgetIcon icon={FaRegClock} />
<FaRegClock className="text-theme-800 dark:text-theme-200 w-5 h-5" /> <ResourceValue>-</ResourceValue>
<div className="flex flex-col ml-3 text-left min-w-[85px]"> <ResourceLabel>{t("resources.uptime")}</ResourceLabel>
<span className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between"> </SingleResource>;
<div className="pl-0.5">-</div>
<div className="pr-1">{t("resources.temp")}</div>
</span>
</div>
</div>
);
} }
const mo = Math.floor(data.uptime / (3600 * 24 * 31)); const mo = Math.floor(data.uptime / (3600 * 24 * 31));
const d = Math.floor(data.uptime % (3600 * 24 * 31) / (3600 * 24)); const d = Math.floor(data.uptime % (3600 * 24 * 31) / (3600 * 24));
const h = Math.floor(data.uptime % (3600 * 24) / 3600); const h = Math.floor(data.uptime % (3600 * 24) / 3600);
const m = Math.floor(data.uptime % 3600 / 60); const m = Math.floor(data.uptime % 3600 / 60);
let uptime; let uptime;
if (mo > 0) uptime = `${mo}${t("resources.months")} ${d}${t("resources.days")}`; if (mo > 0) uptime = `${mo}${t("resources.months")} ${d}${t("resources.days")}`;
else if (d > 0) uptime = `${d}${t("resources.days")} ${h}${t("resources.hours")}`; else if (d > 0) uptime = `${d}${t("resources.days")} ${h}${t("resources.hours")}`;
@ -49,18 +41,10 @@ export default function Uptime() {
const percent = Math.round((new Date().getSeconds() / 60) * 100); const percent = Math.round((new Date().getSeconds() / 60) * 100);
return ( return <SingleResource>
<div className="flex-none flex flex-row items-center mr-3 py-1.5"> <WidgetIcon icon={FaRegClock} />
<FaRegClock className="text-theme-800 dark:text-theme-200 w-5 h-5" /> <ResourceValue>{uptime}</ResourceValue>
<div className="flex flex-col ml-3 text-left min-w-[85px]"> <ResourceLabel>{t("resources.uptime")}</ResourceLabel>
<span className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between"> <UsageBar percent={percent} />
<div className="pl-0.5"> </SingleResource>;
{uptime}
</div>
<div className="pr-1">{t("resources.uptime")}</div>
</span>
<UsageBar percent={percent} />
</div>
</div>
);
} }

@ -1,10 +1,13 @@
import { useState, useEffect, Fragment } from "react"; import { useState, useEffect, useCallback, Fragment } from "react";
import { useTranslation } from "next-i18next"; import { useTranslation } from "next-i18next";
import { FiSearch } from "react-icons/fi"; import { FiSearch } from "react-icons/fi";
import { SiDuckduckgo, SiMicrosoftbing, SiGoogle, SiBaidu, SiBrave } from "react-icons/si"; import { SiDuckduckgo, SiMicrosoftbing, SiGoogle, SiBaidu, SiBrave } from "react-icons/si";
import { Listbox, Transition } from "@headlessui/react"; import { Listbox, Transition } from "@headlessui/react";
import classNames from "classnames"; import classNames from "classnames";
import ContainerForm from "../widget/container_form";
import Raw from "../widget/raw";
export const searchProviders = { export const searchProviders = {
google: { google: {
name: "Google", name: "Google",
@ -77,13 +80,8 @@ export default function Search({ options }) {
} }
}, [availableProviderIds]); }, [availableProviderIds]);
if (!availableProviderIds) { const submitCallback = useCallback(event => {
return null;
}
function handleSubmit(event) {
const q = encodeURIComponent(query); const q = encodeURIComponent(query);
const { url } = selectedProvider; const { url } = selectedProvider;
if (url) { if (url) {
window.open(`${url}${q}`, options.target || "_blank"); window.open(`${url}${q}`, options.target || "_blank");
@ -94,6 +92,10 @@ export default function Search({ options }) {
event.preventDefault(); event.preventDefault();
event.target.reset(); event.target.reset();
setQuery(""); setQuery("");
}, [options.target, options.url, query, selectedProvider]);
if (!availableProviderIds) {
return null;
} }
const onChangeProvider = (provider) => { const onChangeProvider = (provider) => {
@ -101,80 +103,79 @@ export default function Search({ options }) {
localStorage.setItem(localStorageKey, provider.name); localStorage.setItem(localStorageKey, provider.name);
} }
return ( return <ContainerForm options={options} callback={submitCallback} additionalClassNames="grow" >
<form className={classNames( <Raw>
"flex-col relative h-8 my-4 min-w-fit grow first:ml-0 ml-4", <div className="flex-col relative h-8 my-4 min-w-fit">
options?.styleBoxed === true && " h-14 ml-4 mt-4 m: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", <div className="flex absolute inset-y-0 left-0 items-center pl-3 pointer-events-none w-full text-theme-800 dark:text-white" />
)} onSubmit={handleSubmit}> <input
<div className="flex absolute inset-y-0 left-0 items-center pl-3 pointer-events-none w-full text-theme-800 dark:text-white" /> type="text"
<input className="
type="text" overflow-hidden w-full h-full rounded-md
className=" text-xs text-theme-900 dark:text-white
overflow-hidden w-full h-full rounded-md placeholder-theme-900 dark:placeholder-white/80
text-xs text-theme-900 dark:text-white bg-white/50 dark:bg-white/10
placeholder-theme-900 dark:placeholder-white/80 focus:ring-theme-500 dark:focus:ring-white/50
bg-white/50 dark:bg-white/10 focus:border-theme-500 dark:focus:border-white/50
focus:ring-theme-500 dark:focus:ring-white/50 border border-theme-300 dark:border-theme-200/50"
focus:border-theme-500 dark:focus:border-white/50 placeholder={t("search.placeholder")}
border border-theme-300 dark:border-theme-200/50" onChange={(s) => setQuery(s.currentTarget.value)}
placeholder={t("search.placeholder")} required
onChange={(s) => setQuery(s.currentTarget.value)} autoCapitalize="off"
required autoCorrect="off"
autoCapitalize="off" autoComplete="off"
autoCorrect="off" // eslint-disable-next-line jsx-a11y/no-autofocus
autoComplete="off" autoFocus={options.focus}
// eslint-disable-next-line jsx-a11y/no-autofocus />
autoFocus={options.focus} <Listbox as="div" value={selectedProvider} onChange={onChangeProvider} className="relative text-left" disabled={availableProviderIds?.length === 1}>
/> <div>
<Listbox as="div" value={selectedProvider} onChange={onChangeProvider} className="relative text-left" disabled={availableProviderIds?.length === 1}> <Listbox.Button
<div> className="
<Listbox.Button absolute right-0.5 bottom-0.5 rounded-r-md px-4 py-2 border-1
className=" text-white font-medium text-sm
absolute right-0.5 bottom-0.5 rounded-r-md px-4 py-2 border-1 bg-theme-600/40 dark:bg-white/10
text-white font-medium text-sm focus:ring-theme-500 dark:focus:ring-white/50"
bg-theme-600/40 dark:bg-white/10 >
focus:ring-theme-500 dark:focus:ring-white/50" <selectedProvider.icon className="text-white w-3 h-3" />
> <span className="sr-only">{t("search.search")}</span>
<selectedProvider.icon className="text-white w-3 h-3" /> </Listbox.Button>
<span className="sr-only">{t("search.search")}</span> </div>
</Listbox.Button> <Transition
</div> as={Fragment}
<Transition enter="transition ease-out duration-100"
as={Fragment} enterFrom="transform opacity-0 scale-95"
enter="transition ease-out duration-100" enterTo="transform opacity-100 scale-100"
enterFrom="transform opacity-0 scale-95" leave="transition ease-in duration-75"
enterTo="transform opacity-100 scale-100" leaveFrom="transform opacity-100 scale-100"
leave="transition ease-in duration-75" leaveTo="transform opacity-0 scale-95"
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
>
<Listbox.Options
className="absolute right-0 z-10 mt-1 origin-top-right rounded-md
bg-theme-100 dark:bg-theme-600 shadow-lg
ring-1 ring-black ring-opacity-5 focus:outline-none"
> >
<div className="flex flex-col"> <Listbox.Options
{availableProviderIds.map((providerId) => { className="absolute right-0 z-10 mt-1 origin-top-right rounded-md
const p = searchProviders[providerId]; bg-theme-100 dark:bg-theme-600 shadow-lg
return ( ring-1 ring-black ring-opacity-5 focus:outline-none"
<Listbox.Option key={providerId} value={p} as={Fragment}> >
{({ active }) => ( <div className="flex flex-col">
<li {availableProviderIds.map((providerId) => {
className={classNames( const p = searchProviders[providerId];
"rounded-md cursor-pointer", return (
active ? "bg-theme-600/10 dark:bg-white/10 dark:text-gray-900" : "dark:text-gray-100" <Listbox.Option key={providerId} value={p} as={Fragment}>
)} {({ active }) => (
> <li
<p.icon className="h-4 w-4 mx-4 my-2" /> className={classNames(
</li> "rounded-md cursor-pointer",
)} active ? "bg-theme-600/10 dark:bg-white/10 dark:text-gray-900" : "dark:text-gray-100"
</Listbox.Option> )}
); >
})} <p.icon className="h-4 w-4 mx-4 my-2" />
</div> </li>
</Listbox.Options> )}
</Transition> </Listbox.Option>
</Listbox> );
</form> })}
); </div>
</Listbox.Options>
</Transition>
</Listbox>
</div>
</Raw>
</ContainerForm>;
} }

@ -2,9 +2,12 @@ import { BiError, BiWifi, BiCheckCircle, BiXCircle, BiNetworkChart } from "react
import { MdSettingsEthernet } from "react-icons/md"; import { MdSettingsEthernet } from "react-icons/md";
import { useTranslation } from "next-i18next"; import { useTranslation } from "next-i18next";
import { SiUbiquiti } from "react-icons/si"; import { SiUbiquiti } from "react-icons/si";
import classNames from "classnames";
import Error from "../error"; import Error from "../widget/error";
import Container from "../widget/container";
import Raw from "../widget/raw";
import WidgetIcon from "../widget/widget_icon";
import PrimaryText from "../widget/primary_text";
import useWidgetAPI from "utils/proxy/use-widget-api"; import useWidgetAPI from "utils/proxy/use-widget-api";
@ -22,21 +25,10 @@ export default function Widget({ options }) {
const defaultSite = options.site ? statsData?.data.find(s => s.desc === options.site) : statsData?.data?.find(s => s.name === "default"); const defaultSite = options.site ? statsData?.data.find(s => s.desc === options.site) : statsData?.data?.find(s => s.name === "default");
if (!defaultSite) { if (!defaultSite) {
return ( return <Container options={options}>
<div className={classNames( <PrimaryText>{t("unifi.wait")}</PrimaryText>
"flex flex-col justify-center first:ml-0 ml-4", <WidgetIcon icon={SiUbiquiti} />
options?.styleBoxed === true && " ml-4 mt-2 m: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", </Container>;
)}>
<div className="flex flex-row items-center justify-end">
<div className="flex flex-col items-center">
<SiUbiquiti className="w-5 h-5 text-theme-800 dark:text-theme-200" />
</div>
<div className="flex flex-col ml-3 text-left">
<span className="text-theme-800 dark:text-theme-200 text-xs">{t("unifi.wait")}</span>
</div>
</div>
</div>
);
} }
const wan = defaultSite.health.find(h => h.subsystem === "wan"); const wan = defaultSite.health.find(h => h.subsystem === "wan");
@ -51,11 +43,9 @@ export default function Widget({ options }) {
const dataEmpty = !(wan.show || lan.show || wlan.show || uptime); const dataEmpty = !(wan.show || lan.show || wlan.show || uptime);
return ( return <Container options={options}>
<div className={classNames( <Raw>
"flex-none flex flex-row items-center mr-3 py-1.5", <div className="flex-none flex flex-row items-center mr-3 py-1.5">
options?.styleBoxed === true && " ml-4 mt-2 m: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",
)}>
<div className="flex flex-col"> <div className="flex flex-col">
<div className="flex flex-row ml-3 mb-0.5"> <div className="flex flex-row ml-3 mb-0.5">
<SiUbiquiti className="text-theme-800 dark:text-theme-200 w-3 h-3 mr-1" /> <SiUbiquiti className="text-theme-800 dark:text-theme-200 w-3 h-3 mr-1" />
@ -139,6 +129,7 @@ export default function Widget({ options }) {
</div> </div>
</div>} </div>}
</div> </div>
</div> </div>
); </Raw>
</Container>
} }

@ -3,9 +3,13 @@ import { useState } from "react";
import { WiCloudDown } from "react-icons/wi"; import { WiCloudDown } from "react-icons/wi";
import { MdLocationDisabled, MdLocationSearching } from "react-icons/md"; import { MdLocationDisabled, MdLocationSearching } from "react-icons/md";
import { useTranslation } from "next-i18next"; import { useTranslation } from "next-i18next";
import classNames from "classnames";
import Error from "../error"; import Error from "../widget/error";
import Container from "../widget/container";
import PrimaryText from "../widget/primary_text";
import SecondaryText from "../widget/secondary_text";
import WidgetIcon from "../widget/widget_icon";
import ContainerButton from "../widget/container_button";
import Icon from "./icon"; import Icon from "./icon";
@ -21,49 +25,31 @@ function Widget({ options }) {
} }
if (!data) { if (!data) {
return ( return <Container options={options}>
<div className={classNames( <PrimaryText>{t("weather.updating")}</PrimaryText>
"flex flex-col justify-center first:ml-0 ml-4 mr-2", <SecondaryText>{t("weather.wait")}</SecondaryText>
options?.styleBoxed === true && " ml-4 mt-2 m: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", <WidgetIcon icon={WiCloudDown} size="l" />
)}> </Container>;
<div className="flex flex-row items-center justify-end">
<div className="flex flex-col items-center">
<WiCloudDown className="w-8 h-8 text-theme-800 dark:text-theme-200" />
</div>
<div className="flex flex-col ml-3 text-left">
<span className="text-theme-800 dark:text-theme-200 text-sm">{t("weather.updating")}</span>
<span className="text-theme-800 dark:text-theme-200 text-xs">{t("weather.wait")}</span>
</div>
</div>
</div>
);
} }
const unit = options.units === "metric" ? "celsius" : "fahrenheit"; const unit = options.units === "metric" ? "celsius" : "fahrenheit";
const weatherInfo = {
condition: data.current.condition.code,
timeOfDay: data.current.is_day ? "day" : "night",
};
return ( return <Container options={options}>
<div className={classNames( <PrimaryText>
"flex flex-col justify-center first:ml-0 ml-4 mr-2", {options.label && `${options.label}, `}
options?.styleBoxed === true && " ml-4 mt-2 m: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", {t("common.number", {
)}> value: options.units === "metric" ? data.current.temp_c : data.current.temp_f,
<div className="flex flex-row items-center justify-end"> style: "unit",
<div className="flex flex-col items-center"> unit,
<Icon condition={data.current.condition.code} timeOfDay={data.current.is_day ? "day" : "night"} /> })}
</div> </PrimaryText>
<div className="flex flex-col ml-3 text-left"> <SecondaryText>{data.current.condition.text}</SecondaryText>
<span className="text-theme-800 dark:text-theme-200 text-sm"> <WidgetIcon icon={Icon} size="xl" weatherInfo={weatherInfo} />
{options.label && `${options.label}, `} </Container>;
{t("common.number", {
value: options.units === "metric" ? data.current.temp_c : data.current.temp_f,
style: "unit",
unit,
})}
</span>
<span className="text-theme-800 dark:text-theme-200 text-xs">{data.current.condition.text}</span>
</div>
</div>
</div>
);
} }
export default function WeatherApi({ options }) { export default function WeatherApi({ options }) {
@ -95,33 +81,12 @@ export default function WeatherApi({ options }) {
} }
}; };
// if (!requesting && !location) requestLocation();
if (!location) { if (!location) {
return ( return <ContainerButton options={options} callback={requestLocation} >
<button <PrimaryText>{t("weather.current")}</PrimaryText>
type="button" <SecondaryText>{t("weather.allow")}</SecondaryText>
onClick={() => requestLocation()} <WidgetIcon icon={requesting ? MdLocationSearching : MdLocationDisabled} size="m" pulse />
className={classNames( </ContainerButton>;
"flex flex-col justify-center first:ml-0 ml-4 mr-2",
options?.styleBoxed === true && " ml-4 mt-2 m: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",
)}
>
<div className="flex flex-row items-center justify-end">
<div className="flex flex-col items-center">
{requesting ? (
<MdLocationSearching className="w-6 h-6 text-theme-800 dark:text-theme-200 animate-pulse" />
) : (
<MdLocationDisabled className="w-6 h-6 text-theme-800 dark:text-theme-200" />
)}
</div>
<div className="flex flex-col ml-3 text-left">
<span className="text-theme-800 dark:text-theme-200 text-sm">{t("weather.current")}</span>
<span className="text-theme-800 dark:text-theme-200 text-xs">{t("weather.allow")}</span>
</div>
</div>
</button>
);
} }
return <Widget options={{ ...location, ...options }} />; return <Widget options={{ ...location, ...options }} />;

@ -17,13 +17,13 @@ const widgetMappings = {
kubernetes: dynamic(() => import("components/widgets/kubernetes/kubernetes")), kubernetes: dynamic(() => import("components/widgets/kubernetes/kubernetes")),
}; };
export default function Widget({ widget }) { export default function Widget({ widget, style }) {
const InfoWidget = widgetMappings[widget.type]; const InfoWidget = widgetMappings[widget.type];
if (InfoWidget) { if (InfoWidget) {
return ( return (
<ErrorBoundary> <ErrorBoundary>
<InfoWidget options={widget.options} /> <InfoWidget options={{ ...widget.options, style }} />
</ErrorBoundary> </ErrorBoundary>
); );
} }

@ -0,0 +1,42 @@
import classNames from "classnames";
import WidgetIcon from "./widget_icon";
import PrimaryText from "./primary_text";
import SecondaryText from "./secondary_text";
import Raw from "./raw";
export function getAllClasses(options, additionalClassNames = '') {
return classNames(
"flex flex-col justify-center first:ml-0 ml-4 mr-2",
additionalClassNames,
options?.style === "boxedWidgets" && " ml-4 mt-2 m:mb-0 rounded-md shadow-md shadow-theme-900/10 dark:shadow-theme-900/20 bg-theme-100/20 dark:bg-white/5 p-2 pl-3",
);
}
export function getInnerBlock(children) {
// children won't be an array if it's Raw component
return Array.isArray(children) && <div className="flex flex-row items-center justify-end">
<div className="flex flex-col items-center">{children.find(child => child.type === WidgetIcon)}</div>
<div className="flex flex-col ml-3 text-left">
<span className="text-theme-800 dark:text-theme-200 text-sm">{children.find(child => child.type === PrimaryText)}</span>
<span className="text-theme-800 dark:text-theme-200 text-xs">{children.find(child => child.type === SecondaryText)}</span>
</div>
</div>;
}
export function getBottomBlock(children) {
if (children.type !== Raw) {
return children.find(child => child.type === Raw) || [];
}
return [children];
}
export default function Container({ children = [], options, additionalClassNames = '' }) {
return (
<div className={getAllClasses(options, additionalClassNames)}>
{getInnerBlock(children)}
{getBottomBlock(children)}
</div>
);
}

@ -0,0 +1,10 @@
import { getAllClasses, getInnerBlock, getBottomBlock } from "./container";
export default function ContainerButton ({ children = [], options, additionalClassNames = '', callback }) {
return (
<button type="button" onClick={callback} className={getAllClasses(options, additionalClassNames)}>
{getInnerBlock(children)}
{getBottomBlock(children)}
</button>
);
}

@ -0,0 +1,10 @@
import { getAllClasses, getInnerBlock, getBottomBlock } from "./container";
export default function ContainerForm ({ children = [], options, additionalClassNames = '', callback }) {
return (
<form type="button" onSubmit={callback} className={getAllClasses(options, additionalClassNames)}>
{getInnerBlock(children)}
{getBottomBlock(children)}
</form>
);
}

@ -0,0 +1,10 @@
import { getAllClasses, getInnerBlock, getBottomBlock } from "./container";
export default function ContainerLink ({ children = [], options, additionalClassNames = '', target }) {
return (
<a href={options.url} target={target} className={getAllClasses(options, additionalClassNames)}>
{getInnerBlock(children)}
{getBottomBlock(children)}
</a>
);
}

@ -0,0 +1,15 @@
import { useTranslation } from "react-i18next";
import { BiError } from "react-icons/bi";
import Container from "./container";
import PrimaryText from "./primary_text";
import WidgetIcon from "./widget_icon";
export default function Error({ options }) {
const { t } = useTranslation();
return <Container options={options}>
<PrimaryText>{t("widget.api_error")}</PrimaryText>
<WidgetIcon icon={BiError} size="l" />
</Container>;
}

@ -0,0 +1,5 @@
export default function PrimaryText({ children }) {
return (
<span className="text-theme-800 dark:text-theme-200 text-sm">{children}</span>
);
}

@ -0,0 +1,7 @@
export default function Raw({ children }) {
if (children.type === Raw) {
return [children];
}
return children;
}

@ -0,0 +1,5 @@
export default function ResourceLabel({ children }) {
return (
<div className="pr-1">{children}</div>
);
}

@ -0,0 +1,5 @@
export default function ResourceValue({ children }) {
return (
<div className="pl-0.5">{children}</div>
);
}

@ -0,0 +1,15 @@
import ContainerLink from "./container_link";
import SingleResource from "./single_resource";
import Raw from "./raw";
import WidgetLabel from "./widget_label";
export default function Resources({ options, children, target }) {
return <ContainerLink options={options} target={target}>
<Raw>
<div className="flex flex-row self-center flex-wrap justify-between">
{children.filter(child => child && child.type === SingleResource)}
</div>
{children.filter(child => child && child.type === WidgetLabel)}
</Raw>
</ContainerLink>;
}

@ -0,0 +1,5 @@
export default function SecondaryText({ children }) {
return (
<span className="text-theme-800 dark:text-theme-200 text-xs">{children}</span>
);
}

@ -0,0 +1,28 @@
import UsageBar from "../resources/usage-bar";
import WidgetIcon from "./widget_icon";
import ResourceValue from "./resource_value";
import ResourceLabel from "./resource_label";
import Raw from "./raw";
export default function SingleResource({ children, key, expanded = false }) {
const values = children.filter(child => child.type === ResourceValue);
const labels = children.filter(child => child.type === ResourceLabel);
return <div key={key} className="flex-none flex flex-row items-center mr-3 py-1.5">
{children.find(child => child.type === WidgetIcon)}
<div className="flex flex-col ml-3 text-left min-w-[85px]">
<div className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between">
{values.pop()}
{labels.pop()}
</div>
{ expanded && <div className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between">
{values.pop()}
{labels.pop()}
</div>
}
{children.find(child => child.type === UsageBar)}
</div>
{children.find(child => child.type === Raw)}
</div>;
}

@ -0,0 +1,18 @@
export default function WidgetIcon({ icon, size = "s", pulse = false, weatherInfo = {} }) {
const Icon = icon;
const { condition, timeOfDay } = weatherInfo;
let additionalClasses = "text-theme-800 dark:text-theme-200 ";
switch (size) {
case "m": additionalClasses += "w-6 h-6 "; break;
case "l": additionalClasses += "w-8 h-8 "; break;
case "xl": additionalClasses += "w-10 h-10 "; break;
default: additionalClasses += "w-5 h-5 ";
}
if (pulse) {
additionalClasses += "animate-pulse ";
}
return <Icon className={additionalClasses} condition={condition} timeOfDay={timeOfDay} />;
}

@ -0,0 +1,3 @@
export default function WidgetLabel({ label = "" }) {
return <div className="ml-6 pt-1 text-center text-theme-800 dark:text-theme-200 text-xs">{label}</div>
}

@ -160,6 +160,7 @@ const headerStyles = {
"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", "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", 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", 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 }) { function Home({ initialSettings }) {
@ -208,6 +209,7 @@ function Home({ initialSettings }) {
searchProvider = searchProviders[searchWidget.options?.provider]; searchProvider = searchProviders[searchWidget.options?.provider];
} }
} }
const headerStyle = initialSettings?.headerStyle || "underlined";
useEffect(() => { useEffect(() => {
function handleKeyDown(e) { function handleKeyDown(e) {
@ -256,7 +258,7 @@ function Home({ initialSettings }) {
<div <div
className={classNames( className={classNames(
"flex flex-row flex-wrap justify-between", "flex flex-row flex-wrap justify-between",
headerStyles[initialSettings.headerStyle || "underlined"] headerStyles[headerStyle]
)} )}
> >
<QuickLaunch <QuickLaunch
@ -272,14 +274,14 @@ function Home({ initialSettings }) {
{widgets {widgets
.filter((widget) => !rightAlignedWidgets.includes(widget.type)) .filter((widget) => !rightAlignedWidgets.includes(widget.type))
.map((widget, i) => ( .map((widget, i) => (
<Widget key={i} widget={widget} /> <Widget key={i} widget={widget} style={headerStyle} />
))} ))}
<div className="m-auto sm:ml-2 flex flex-wrap grow sm:basis-auto justify-between md:justify-end"> <div className="m-auto sm:ml-4 flex flex-wrap grow sm:basis-auto justify-between md:justify-end">
{widgets {widgets
.filter((widget) => rightAlignedWidgets.includes(widget.type)) .filter((widget) => rightAlignedWidgets.includes(widget.type))
.map((widget, i) => ( .map((widget, i) => (
<Widget key={i} widget={widget} /> <Widget key={i} widget={widget} style={headerStyle} />
))} ))}
</div> </div>
</> </>

Loading…
Cancel
Save