aboutsummaryrefslogtreecommitdiffstats
path: root/ui/src/components
diff options
context:
space:
mode:
authorEllie Huxtable <ellie@elliehuxtable.com>2024-04-17 14:06:05 +0100
committerGitHub <noreply@github.com>2024-04-17 14:06:05 +0100
commitcb19925011d889c513e1bbedc446e399597e38a0 (patch)
tree7ad9e42013e15957805f2cdf563ce8b3e0c770f5 /ui/src/components
parentchore(deps): bump debian (#1947) (diff)
downloadatuin-cb19925011d889c513e1bbedc446e399597e38a0.zip
feat(gui): work on home page, sort state (#1956)
1. Start on a home page, can sort onboarding/etc from there 2. Introduce zustand for state management. It's nice! Did a production build and clicked around for a while. Memory usage seems nice and chill.
Diffstat (limited to 'ui/src/components')
-rw-r--r--ui/src/components/Drawer.tsx2
-rw-r--r--ui/src/components/HistoryList.tsx131
-rw-r--r--ui/src/components/dotfiles/Aliases.tsx37
-rw-r--r--ui/src/components/history/Stats.tsx56
4 files changed, 91 insertions, 135 deletions
diff --git a/ui/src/components/Drawer.tsx b/ui/src/components/Drawer.tsx
index 65bb5ab4..91753624 100644
--- a/ui/src/components/Drawer.tsx
+++ b/ui/src/components/Drawer.tsx
@@ -1,5 +1,3 @@
-import * as React from "react";
-
import { Drawer as VDrawer } from "vaul";
export default function Drawer({
diff --git a/ui/src/components/HistoryList.tsx b/ui/src/components/HistoryList.tsx
index b31a4be4..9616ecf0 100644
--- a/ui/src/components/HistoryList.tsx
+++ b/ui/src/components/HistoryList.tsx
@@ -1,75 +1,88 @@
-import { DateTime } from 'luxon';
-import { ChevronRightIcon } from '@heroicons/react/20/solid'
+import { ChevronRightIcon } from "@heroicons/react/20/solid";
-function msToTime(ms) {
- let milliseconds = (ms).toFixed(1);
- let seconds = (ms / 1000).toFixed(1);
- let minutes = (ms / (1000 * 60)).toFixed(1);
- let hours = (ms / (1000 * 60 * 60)).toFixed(1);
- let days = (ms / (1000 * 60 * 60 * 24)).toFixed(1);
+// @ts-ignore
+import { DateTime } from "luxon";
+
+function msToTime(ms: number) {
+ let milliseconds = parseInt(ms.toFixed(1));
+ let seconds = parseInt((ms / 1000).toFixed(1));
+ let minutes = parseInt((ms / (1000 * 60)).toFixed(1));
+ let hours = parseInt((ms / (1000 * 60 * 60)).toFixed(1));
+ let days = parseInt((ms / (1000 * 60 * 60 * 24)).toFixed(1));
if (milliseconds < 1000) return milliseconds + "ms";
else if (seconds < 60) return seconds + "s";
else if (minutes < 60) return minutes + "m";
else if (hours < 24) return hours + "hr";
- else return days + " Days"
+ else return days + " Days";
}
-export default function HistoryList(props){
+export default function HistoryList(props: any) {
return (
+ <ul
+ role="list"
+ className="divide-y divide-gray-100 overflow-hidden bg-white shadow-sm ring-1 ring-gray-900/5"
+ >
+ {props.history.map((h: any) => (
+ <li
+ key={h.id}
+ className="relative flex justify-between gap-x-6 px-4 py-5 hover:bg-gray-50 sm:px-6"
+ >
+ <div className="flex min-w-0 gap-x-4">
+ <div className="flex flex-col justify-center">
+ <p className="flex text-xs text-gray-500 justify-center">
+ {DateTime.fromMillis(h.timestamp / 1000000).toLocaleString(
+ DateTime.TIME_WITH_SECONDS,
+ )}
+ </p>
+ <p className="flex text-xs mt-1 text-gray-400 justify-center">
+ {DateTime.fromMillis(h.timestamp / 1000000).toLocaleString(
+ DateTime.DATE_SHORT,
+ )}
+ </p>
+ </div>
+ <div className="min-w-0 flex-col justify-center">
+ <pre className="whitespace-pre-wrap">
+ <code className="text-sm">{h.command}</code>
+ </pre>
+ <p className="mt-1 flex text-xs leading-5 text-gray-500">
+ <span className="relative truncate ">{h.user}</span>
- <ul
- role="list"
- className="divide-y divide-gray-100 overflow-hidden bg-white shadow-sm ring-1 ring-gray-900/5"
- >
- {props.history.map((h) => (
- <li key={h.id} className="relative flex justify-between gap-x-6 px-4 py-5 hover:bg-gray-50 sm:px-6">
- <div className="flex min-w-0 gap-x-4">
- <div className="flex flex-col justify-center">
- <p className="flex text-xs text-gray-500 justify-center">{ DateTime.fromMillis(h.timestamp / 1000000).toLocaleString(DateTime.TIME_WITH_SECONDS)}</p>
- <p className="flex text-xs mt-1 text-gray-400 justify-center">{ DateTime.fromMillis(h.timestamp / 1000000).toLocaleString(DateTime.DATE_SHORT)}</p>
- </div>
- <div className="min-w-0 flex-col justify-center">
- <pre className="whitespace-pre-wrap"><code className="text-sm">{h.command}</code></pre>
- <p className="mt-1 flex text-xs leading-5 text-gray-500">
- <span className="relative truncate ">
- {h.user}
- </span>
-
- <span>&nbsp;on&nbsp;</span>
+ <span>&nbsp;on&nbsp;</span>
- <span className="relative truncate ">
- {h.host}
- </span>
+ <span className="relative truncate ">{h.host}</span>
- <span>&nbsp;in&nbsp;</span>
+ <span>&nbsp;in&nbsp;</span>
- <span className="relative truncate ">
- {h.cwd}
- </span>
- </p>
- </div>
- </div>
- <div className="flex shrink-0 items-center gap-x-4">
- <div className="hidden sm:flex sm:flex-col sm:items-end">
- <p className="text-sm leading-6 text-gray-900">{h.exit}</p>
- {h.duration ? (
- <p className="mt-1 text-xs leading-5 text-gray-500">
- <time dateTime={h.duration}>{msToTime(h.duration / 1000000)}</time>
- </p>
- ) : (
- <div className="mt-1 flex items-center gap-x-1.5">
- <div className="flex-none rounded-full bg-emerald-500/20 p-1">
- <div className="h-1.5 w-1.5 rounded-full bg-emerald-500" />
- </div>
- <p className="text-xs leading-5 text-gray-500">Online</p>
- </div>
- )}
- </div>
- <ChevronRightIcon className="h-5 w-5 flex-none text-gray-400" aria-hidden="true" />
+ <span className="relative truncate ">{h.cwd}</span>
+ </p>
+ </div>
+ </div>
+ <div className="flex shrink-0 items-center gap-x-4">
+ <div className="hidden sm:flex sm:flex-col sm:items-end">
+ <p className="text-sm leading-6 text-gray-900">{h.exit}</p>
+ {h.duration ? (
+ <p className="mt-1 text-xs leading-5 text-gray-500">
+ <time dateTime={h.duration}>
+ {msToTime(h.duration / 1000000)}
+ </time>
+ </p>
+ ) : (
+ <div className="mt-1 flex items-center gap-x-1.5">
+ <div className="flex-none rounded-full bg-emerald-500/20 p-1">
+ <div className="h-1.5 w-1.5 rounded-full bg-emerald-500" />
</div>
- </li>
- ))}
- </ul>
+ <p className="text-xs leading-5 text-gray-500">Online</p>
+ </div>
+ )}
+ </div>
+ <ChevronRightIcon
+ className="h-5 w-5 flex-none text-gray-400"
+ aria-hidden="true"
+ />
+ </div>
+ </li>
+ ))}
+ </ul>
);
}
diff --git a/ui/src/components/dotfiles/Aliases.tsx b/ui/src/components/dotfiles/Aliases.tsx
index 4854e6b5..61fd001c 100644
--- a/ui/src/components/dotfiles/Aliases.tsx
+++ b/ui/src/components/dotfiles/Aliases.tsx
@@ -1,4 +1,4 @@
-import React, { useEffect, useState } from "react";
+import { useEffect, useState } from "react";
import DataTable from "@/components/ui/data-table";
import { Button } from "@/components/ui/button";
@@ -8,34 +8,21 @@ import {
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
- DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
+import { ColumnDef } from "@tanstack/react-table";
+
import { invoke } from "@tauri-apps/api/core";
import Drawer from "@/components/Drawer";
-function loadAliases(
- setAliases: React.Dispatch<React.SetStateAction<never[]>>,
-) {
- invoke("aliases").then((aliases: any) => {
- setAliases(aliases);
- });
-}
-
-type Alias = {
- name: string;
- value: string;
-};
+import { Alias } from "@/state/models";
+import { useStore } from "@/state/store";
-function deleteAlias(
- name: string,
- setAliases: React.Dispatch<React.SetStateAction<never[]>>,
-) {
+function deleteAlias(name: string, refreshAliases: () => void) {
invoke("delete_alias", { name: name })
.then(() => {
- console.log("Deleted alias");
- loadAliases(setAliases);
+ refreshAliases();
})
.catch(() => {
console.error("Failed to delete alias");
@@ -101,7 +88,9 @@ function AddAlias({ onAdd: onAdd }: { onAdd?: () => void }) {
}
export default function Aliases() {
- let [aliases, setAliases] = useState([]);
+ const aliases = useStore((state) => state.aliases);
+ const refreshAliases = useStore((state) => state.refreshAliases);
+
let [aliasDrawerOpen, setAliasDrawerOpen] = useState(false);
const columns: ColumnDef<Alias>[] = [
@@ -129,7 +118,7 @@ export default function Aliases() {
<DropdownMenuContent align="end">
<DropdownMenuLabel>Actions</DropdownMenuLabel>
<DropdownMenuItem
- onClick={() => deleteAlias(alias.name, setAliases)}
+ onClick={() => deleteAlias(alias.name, refreshAliases)}
>
Delete
</DropdownMenuItem>
@@ -141,7 +130,7 @@ export default function Aliases() {
];
useEffect(() => {
- loadAliases(setAliases);
+ refreshAliases();
}, []);
return (
@@ -172,7 +161,7 @@ export default function Aliases() {
>
<AddAlias
onAdd={() => {
- loadAliases(setAliases);
+ refreshAliases();
setAliasDrawerOpen(false);
}}
/>
diff --git a/ui/src/components/history/Stats.tsx b/ui/src/components/history/Stats.tsx
index afd9ed89..ce92ac04 100644
--- a/ui/src/components/history/Stats.tsx
+++ b/ui/src/components/history/Stats.tsx
@@ -5,29 +5,18 @@ import PacmanLoader from "react-spinners/PacmanLoader";
import {
BarChart,
Bar,
- Rectangle,
XAxis,
YAxis,
- CartesianGrid,
Tooltip,
- Legend,
ResponsiveContainer,
} from "recharts";
-const tabs = [
- { name: "Daily", href: "#", current: true },
- { name: "Weekly", href: "#", current: false },
- { name: "Monthly", href: "#", current: false },
-];
-
-function classNames(...classes) {
- return classes.filter(Boolean).join(" ");
-}
-
function renderLoading() {
- <div className="flex items-center justify-center h-full">
- <PacmanLoader color="#26bd65" />
- </div>;
+ return (
+ <div className="flex items-center justify-center h-full">
+ <PacmanLoader color="#26bd65" />
+ </div>
+ );
}
export default function Stats() {
@@ -77,7 +66,7 @@ export default function Stats() {
<div className="flex flex-col">
<div className="flexfull">
<dl className="grid grid-cols-1 sm:grid-cols-4 w-full">
- {stats.map((item) => (
+ {stats.map((item: any) => (
<div
key={item.name}
className="overflow-hidden bg-white px-4 py-5 shadow sm:p-6"
@@ -94,39 +83,6 @@ export default function Stats() {
</div>
<div className="flex flex-col h-54 py-4 pl-5">
- <div className="sm:hidden">
- {/* Use an "onChange" listener to redirect the user to the selected tab URL. */}
- <select
- id="tabs"
- name="tabs"
- className="block w-full rounded-md border-gray-300 focus:border-green-500 focus:ring-green-500"
- defaultValue={tabs.find((tab) => tab.current).name}
- >
- {tabs.map((tab) => (
- <option key={tab.name}>{tab.name}</option>
- ))}
- </select>
- </div>
- <div className="hidden sm:block">
- <nav className="flex space-x-4" aria-label="Tabs">
- {tabs.map((tab) => (
- <a
- key={tab.name}
- href={tab.href}
- className={classNames(
- tab.current
- ? "bg-gray-100 text-gray-700"
- : "text-gray-500 hover:text-gray-700",
- "rounded-md px-3 py-2 text-sm font-medium",
- )}
- aria-current={tab.current ? "page" : undefined}
- >
- {tab.name}
- </a>
- ))}
- </nav>
- </div>
-
<div className="flex flex-col h-48 pt-5 pr-5">
<ResponsiveContainer width="100%" height="100%">
<BarChart width={500} height={300} data={chart}>