Add commit sha to footer. (#636)
* feat: added commit sha to footer. * added translation * added padding * updated styling
This commit is contained in:
+1
-1
@@ -11,7 +11,7 @@
|
|||||||
"lint:fix": "deno lint --fix src/",
|
"lint:fix": "deno lint --fix src/",
|
||||||
"format": "deno fmt src/",
|
"format": "deno fmt src/",
|
||||||
"dev": "deno task dev:ui",
|
"dev": "deno task dev:ui",
|
||||||
"dev:ui": "deno run -A npm:vite dev",
|
"dev:ui": "VITE_APP_VERSION=development deno run -A npm:vite dev",
|
||||||
"test": "deno run -A npm:vitest",
|
"test": "deno run -A npm:vitest",
|
||||||
"preview": "deno run -A npm:vite preview",
|
"preview": "deno run -A npm:vite preview",
|
||||||
"package": "gzipper c -i html,js,css,png,ico,svg,webmanifest,txt dist dist/output && tar -cvf dist/build.tar -C ./dist/output/ ."
|
"package": "gzipper c -i html,js,css,png,ico,svg,webmanifest,txt dist dist/output && tar -cvf dist/build.tar -C ./dist/output/ ."
|
||||||
|
|||||||
+119
-120
@@ -2,7 +2,7 @@ import { cn } from "@core/utils/cn.ts";
|
|||||||
import {
|
import {
|
||||||
CpuIcon,
|
CpuIcon,
|
||||||
Languages,
|
Languages,
|
||||||
LucideIcon,
|
type LucideIcon,
|
||||||
Palette,
|
Palette,
|
||||||
PenLine,
|
PenLine,
|
||||||
Search as SearchIcon,
|
Search as SearchIcon,
|
||||||
@@ -11,7 +11,7 @@ import {
|
|||||||
import BatteryStatus from "./BatteryStatus.tsx";
|
import BatteryStatus from "./BatteryStatus.tsx";
|
||||||
import { Subtle } from "./UI/Typography/Subtle.tsx";
|
import { Subtle } from "./UI/Typography/Subtle.tsx";
|
||||||
import { Avatar } from "./UI/Avatar.tsx";
|
import { Avatar } from "./UI/Avatar.tsx";
|
||||||
import { DeviceMetrics } from "./types.ts";
|
import type { DeviceMetrics } from "./types.ts";
|
||||||
import { Button } from "./UI/Button.tsx";
|
import { Button } from "./UI/Button.tsx";
|
||||||
import React, { Fragment } from "react";
|
import React, { Fragment } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
@@ -110,131 +110,130 @@ export const DeviceInfoPanel = ({
|
|||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-1">
|
<>
|
||||||
<div className="flex flex-col">
|
<div
|
||||||
<div
|
className={cn(
|
||||||
className={cn(
|
"flex items-center gap-3 p-1 flex-shrink-0",
|
||||||
"flex items-center gap-3 p-1",
|
isCollapsed && "justify-center",
|
||||||
isCollapsed && "justify-center",
|
)}
|
||||||
)}
|
>
|
||||||
>
|
<Avatar
|
||||||
<Avatar
|
text={user.shortName}
|
||||||
text={user.shortName}
|
className={cn("flex-shrink-0", isCollapsed && "")}
|
||||||
className={cn("flex-shrink-0", isCollapsed && "")}
|
size="sm"
|
||||||
size="sm"
|
/>
|
||||||
/>
|
{!isCollapsed && (
|
||||||
{!isCollapsed && (
|
<p
|
||||||
<p
|
className={cn(
|
||||||
|
"text-sm font-medium text-gray-800 dark:text-gray-200",
|
||||||
|
"transition-opacity duration-300 ease-in-out truncate",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{user.longName}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{!isCollapsed && (
|
||||||
|
<div className="my-2 h-px bg-gray-200 dark:bg-gray-700 flex-shrink-0">
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"flex flex-col gap-2 mt-1",
|
||||||
|
"transition-all duration-300 ease-in-out",
|
||||||
|
isCollapsed
|
||||||
|
? "opacity-0 max-w-0 h-0 invisible pointer-events-none"
|
||||||
|
: "opacity-100 max-w-xs h-auto visible",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{deviceInfoItems.map((item) => {
|
||||||
|
const IconComponent = item.icon;
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={item.id}
|
||||||
|
className="flex items-center gap-2.5 text-sm"
|
||||||
|
>
|
||||||
|
{IconComponent && (
|
||||||
|
<IconComponent
|
||||||
|
size={16}
|
||||||
|
className="text-gray-500 dark:text-gray-400 w-4 flex-shrink-0"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{item.customComponent}
|
||||||
|
{item.id !== "battery" && (
|
||||||
|
<Subtle className="text-gray-600 dark:text-gray-300">
|
||||||
|
{item.label}: {item.value}
|
||||||
|
</Subtle>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{!isCollapsed && (
|
||||||
|
<div className="my-2 h-px bg-gray-200 dark:bg-gray-700 flex-shrink-0">
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"flex flex-col gap-1 mt-1",
|
||||||
|
"transition-all duration-300 ease-in-out",
|
||||||
|
isCollapsed
|
||||||
|
? "opacity-0 max-w-0 h-0 invisible pointer-events-none"
|
||||||
|
: "opacity-100 max-w-xs visible",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{actionButtons.map((buttonItem) => {
|
||||||
|
const Icon = buttonItem.icon;
|
||||||
|
if (buttonItem.render) {
|
||||||
|
return (
|
||||||
|
<Fragment key={buttonItem.id}>
|
||||||
|
{buttonItem.render()}
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
key={buttonItem.id}
|
||||||
|
variant="ghost"
|
||||||
|
aria-label={buttonItem.label}
|
||||||
|
onClick={buttonItem.onClick}
|
||||||
className={cn(
|
className={cn(
|
||||||
"text-sm font-medium text-gray-800 dark:text-gray-200",
|
"group",
|
||||||
"transition-opacity duration-300 ease-in-out",
|
"flex w-full items-center justify-start text-sm p-1.5 rounded-md",
|
||||||
|
"gap-2.5",
|
||||||
|
"transition-colors duration-150",
|
||||||
|
!disableHover && "hover:bg-gray-100 dark:hover:bg-gray-700",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{user.longName}
|
<Icon
|
||||||
</p>
|
size={16}
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{!isCollapsed && (
|
|
||||||
<div className="my-2 h-px bg-gray-200 dark:bg-gray-700"></div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div
|
|
||||||
className={cn(
|
|
||||||
"flex flex-col gap-2 mt-1",
|
|
||||||
"transition-all duration-300 ease-in-out",
|
|
||||||
isCollapsed
|
|
||||||
? "opacity-0 max-w-0 h-0 invisible pointer-events-none"
|
|
||||||
: "opacity-100 max-w-xs h-auto visible",
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{deviceInfoItems.map((item) => {
|
|
||||||
const IconComponent = item.icon;
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
key={item.id}
|
|
||||||
className="flex items-center gap-2.5 text-sm"
|
|
||||||
>
|
|
||||||
{IconComponent && (
|
|
||||||
<IconComponent
|
|
||||||
size={16}
|
|
||||||
className="text-gray-500 dark:text-gray-400 w-4 flex-shrink-0"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{item.customComponent}
|
|
||||||
{item.id !== "battery" && (
|
|
||||||
<Subtle className="text-gray-600 dark:text-gray-300">
|
|
||||||
{item.label}: {item.value}
|
|
||||||
</Subtle>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{!isCollapsed && (
|
|
||||||
<div className="my-2 h-px bg-gray-200 dark:bg-gray-700"></div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div
|
|
||||||
className={cn(
|
|
||||||
"flex flex-col gap-1 mt-1",
|
|
||||||
"transition-all duration-300 ease-in-out",
|
|
||||||
isCollapsed
|
|
||||||
? "opacity-0 max-w-0 h-0 invisible pointer-events-none"
|
|
||||||
: "opacity-100 max-w-xs visible",
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{actionButtons.map((buttonItem) => {
|
|
||||||
const Icon = buttonItem.icon;
|
|
||||||
if (buttonItem.render) {
|
|
||||||
return (
|
|
||||||
<Fragment key={buttonItem.id}>
|
|
||||||
{buttonItem.render()}
|
|
||||||
</Fragment>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<Button
|
|
||||||
key={buttonItem.id}
|
|
||||||
variant="ghost"
|
|
||||||
aria-label={buttonItem.label}
|
|
||||||
onClick={buttonItem.onClick}
|
|
||||||
className={cn(
|
className={cn(
|
||||||
"group",
|
"flex-shrink-0 w-4",
|
||||||
"flex w-full items-center justify-start text-sm p-1.5 rounded-md",
|
"text-gray-500 dark:text-gray-400",
|
||||||
"gap-2.5",
|
|
||||||
"transition-colors duration-150",
|
"transition-colors duration-150",
|
||||||
!disableHover && "hover:bg-gray-100 dark:hover:bg-gray-700",
|
!disableHover &&
|
||||||
|
"group-hover:text-gray-700 dark:group-hover:text-gray-200",
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Subtle
|
||||||
|
className={cn(
|
||||||
|
"text-sm",
|
||||||
|
"text-gray-600 dark:text-gray-300",
|
||||||
|
"transition-colors duration-150",
|
||||||
|
!disableHover &&
|
||||||
|
"group-hover:text-gray-800 dark:group-hover:text-gray-100",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Icon
|
{buttonItem.label}
|
||||||
size={16}
|
</Subtle>
|
||||||
className={cn(
|
</Button>
|
||||||
"flex-shrink-0 w-4",
|
);
|
||||||
"text-gray-500 dark:text-gray-400",
|
})}
|
||||||
"transition-colors duration-150",
|
|
||||||
!disableHover &&
|
|
||||||
"group-hover:text-gray-700 dark:group-hover:text-gray-200",
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<Subtle
|
|
||||||
className={cn(
|
|
||||||
"text-sm",
|
|
||||||
"text-gray-600 dark:text-gray-300",
|
|
||||||
"transition-colors duration-150",
|
|
||||||
!disableHover &&
|
|
||||||
"group-hover:text-gray-800 dark:group-hover:text-gray-100",
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{buttonItem.label}
|
|
||||||
</Subtle>
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
{/* <Code>{import.meta.env.COMMIT_HASH}</Code> */}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ export const PageLayout = ({
|
|||||||
{rightBar && (
|
{rightBar && (
|
||||||
<aside
|
<aside
|
||||||
className={cn(
|
className={cn(
|
||||||
"w-48 lg:w-[270px] shrink-0 border-l border-slate-300 dark:border-slate-700 px-2 overflow-hidden",
|
"w-56 lg:w-[270px] text-balance shrink-0 border-l border-slate-300 dark:border-slate-700 px-2 overflow-hidden",
|
||||||
rightBarClassName,
|
rightBarClassName,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ export const Sidebar = ({ children }: SidebarProps) => {
|
|||||||
className={cn(
|
className={cn(
|
||||||
"relative border-slate-300 dark:border-slate-700",
|
"relative border-slate-300 dark:border-slate-700",
|
||||||
"transition-all duration-300 ease-in-out flex-shrink-0",
|
"transition-all duration-300 ease-in-out flex-shrink-0",
|
||||||
isCollapsed ? "w-24" : "w-46 lg:w-64",
|
isCollapsed ? "w-24" : "w-52 lg:w-64",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<CollapseToggleButton />
|
<CollapseToggleButton />
|
||||||
@@ -188,7 +188,7 @@ export const Sidebar = ({ children }: SidebarProps) => {
|
|||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="pt-4 border-t-[0.5px] bg-background-primary border-slate-300 dark:border-slate-700 flex-shrink-0">
|
<div className=" pt-4 border-t-[0.5px] bg-background-primary border-slate-300 dark:border-slate-700 h-full flex-1">
|
||||||
{myNode === undefined
|
{myNode === undefined
|
||||||
? (
|
? (
|
||||||
<div className="flex flex-col items-center justify-center py-6">
|
<div className="flex flex-col items-center justify-center py-6">
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
import { cn } from "@core/utils/cn.ts";
|
import { cn } from "@core/utils/cn.ts";
|
||||||
import { Trans } from "react-i18next";
|
import { Trans, useTranslation } from "react-i18next";
|
||||||
|
|
||||||
type FooterProps = {
|
type FooterProps = {
|
||||||
className?: string;
|
className?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const Footer = ({ className, ...props }: FooterProps) => {
|
const Footer = ({ className, ...props }: FooterProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<footer
|
<footer
|
||||||
className={cn(
|
className={cn(
|
||||||
@@ -14,7 +16,14 @@ const Footer = ({ className, ...props }: FooterProps) => {
|
|||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<p>
|
<div className="justify-start px-2">
|
||||||
|
<span className="font-semibold text-gray-500/40 dark:text-gray-400/40">
|
||||||
|
{t("footer.commitSha", {
|
||||||
|
sha: String(import.meta.env.VITE_COMMIT_HASH)?.toUpperCase(),
|
||||||
|
})}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<p className="ml-auto mr-auto text-gray-500 dark:text-gray-400">
|
||||||
<Trans
|
<Trans
|
||||||
i18nKey="footer.text"
|
i18nKey="footer.text"
|
||||||
components={[
|
components={[
|
||||||
|
|||||||
@@ -52,13 +52,13 @@ export const SidebarButton = ({
|
|||||||
|
|
||||||
<span
|
<span
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex flex-wrap justify-start text-left text-wrap break-all",
|
"flex flex-wrap justify-start text-left text-balance break-all",
|
||||||
"min-w-0",
|
"min-w-0",
|
||||||
"px-1",
|
"px-1",
|
||||||
"transition-all duration-300 ease-in-out",
|
"transition-all duration-300 ease-in-out",
|
||||||
isButtonCollapsed
|
isButtonCollapsed
|
||||||
? "opacity-0 max-w-0 invisible w-0 overflow-hidden"
|
? "opacity-0 max-w-0 invisible w-0 overflow-hidden"
|
||||||
: "opacity-100 max-w-full visible flex-1 whitespace-normal",
|
: "opacity-100 max-w-full visible whitespace-normal",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{label}
|
{label}
|
||||||
|
|||||||
@@ -157,6 +157,7 @@
|
|||||||
"changeTheme": "Change Color Scheme"
|
"changeTheme": "Change Color Scheme"
|
||||||
},
|
},
|
||||||
"footer": {
|
"footer": {
|
||||||
"text": "Powered by <0>▲ Vercel</0> | Meshtastic® is a registered trademark of Meshtastic LLC. | <1>Legal Information</1>"
|
"text": "Powered by <0>▲ Vercel</0> | Meshtastic® is a registered trademark of Meshtastic LLC. | <1>Legal Information</1>",
|
||||||
|
"commitSha": "Commit SHA: {{sha}}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user