aboutsummaryrefslogtreecommitdiffstats
path: root/ui/src/components
diff options
context:
space:
mode:
authorEllie Huxtable <ellie@elliehuxtable.com>2024-05-06 08:11:47 +0100
committerGitHub <noreply@github.com>2024-05-06 08:11:47 +0100
commit754ddeaa8d3e3e4f3efc93d5bb22c68c31bb5c36 (patch)
treef48fb912c2be2d08855e97ff24b6919a115c3c4f /ui/src/components
parentchore(deps): bump serde_with from 3.7.0 to 3.8.1 (#2002) (diff)
downloadatuin-754ddeaa8d3e3e4f3efc93d5bb22c68c31bb5c36.zip
feat(ui): scroll history infinitely (#1999)
* wip, history scrolls right! * wip * virtual scroll fucking worksssss * paging works :) * scroll search results now too
Diffstat (limited to '')
-rw-r--r--ui/src/components/HistoryList.tsx126
-rw-r--r--ui/src/components/HistorySearch.tsx12
-rw-r--r--ui/src/components/dotfiles/Aliases.tsx2
-rw-r--r--ui/src/components/history/Stats.tsx67
4 files changed, 139 insertions, 68 deletions
diff --git a/ui/src/components/HistoryList.tsx b/ui/src/components/HistoryList.tsx
index 9616ecf0..7cdeacd8 100644
--- a/ui/src/components/HistoryList.tsx
+++ b/ui/src/components/HistoryList.tsx
@@ -1,3 +1,4 @@
+import { useRef } from "react";
import { ChevronRightIcon } from "@heroicons/react/20/solid";
// @ts-ignore
@@ -19,70 +20,81 @@ function msToTime(ms: number) {
export default function HistoryList(props: any) {
return (
- <ul
+ <div
role="list"
- className="divide-y divide-gray-100 overflow-hidden bg-white shadow-sm ring-1 ring-gray-900/5"
+ className="divide-y divide-gray-100 bg-white shadow-sm ring-1 ring-gray-900/5 overflow-auto"
+ style={{
+ height: `${props.height}px`,
+ position: "relative",
+ }}
>
- {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>
+ {props.items.map((i: any) => {
+ let h = props.history[i.index];
- <span>&nbsp;on&nbsp;</span>
+ return (
+ <li
+ key={h.id}
+ className="relative flex justify-between gap-x-6 px-4 py-5 hover:bg-gray-50 sm:px-6"
+ style={{
+ position: "absolute",
+ top: 0,
+ left: 0,
+ width: "100%",
+ height: `${i.size}px`,
+ transform: `translateY(${i.start}px)`,
+ }}
+ >
+ <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 className="relative truncate ">{h.host}</span>
+ <span>&nbsp;on&nbsp;</span>
- <span>&nbsp;in&nbsp;</span>
+ <span className="relative truncate ">{h.host}</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>
+ <span>&nbsp;in&nbsp;</span>
+
+ <span className="relative truncate ">{h.cwd}</span>
</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>
+ </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 />
+ )}
+ </div>
+ <ChevronRightIcon
+ className="h-5 w-5 flex-none text-gray-400"
+ aria-hidden="true"
+ />
</div>
- <ChevronRightIcon
- className="h-5 w-5 flex-none text-gray-400"
- aria-hidden="true"
- />
- </div>
- </li>
- ))}
- </ul>
+ </li>
+ );
+ })}
+ </div>
);
}
diff --git a/ui/src/components/HistorySearch.tsx b/ui/src/components/HistorySearch.tsx
index 08bed2a8..b3c8492a 100644
--- a/ui/src/components/HistorySearch.tsx
+++ b/ui/src/components/HistorySearch.tsx
@@ -3,12 +3,12 @@ import { ArrowPathIcon } from "@heroicons/react/24/outline";
import { MagnifyingGlassIcon } from "@heroicons/react/20/solid";
interface HistorySearchProps {
- refresh: (query: string) => void;
+ query: string;
+ refresh: () => void;
+ setQuery: (query: string) => void;
}
export default function HistorySearch(props: HistorySearchProps) {
- let [searchQuery, setSearchQuery] = useState("");
-
return (
<div className="flex flex-1 gap-x-4 self-stretch lg:gap-x-6">
<form
@@ -35,8 +35,8 @@ export default function HistorySearch(props: HistorySearchProps) {
type="search"
name="search"
onChange={(query) => {
- setSearchQuery(query.target.value);
- props.refresh(query.target.value);
+ props.setQuery(query.target.value);
+ props.refresh();
}}
/>
</form>
@@ -45,7 +45,7 @@ export default function HistorySearch(props: HistorySearchProps) {
type="button"
className="-m-2.5 p-2.5 text-gray-400 hover:text-gray-500"
onClick={() => {
- props.refresh(searchQuery);
+ props.refresh();
}}
>
<ArrowPathIcon className="h-6 w-6" aria-hidden="true" />
diff --git a/ui/src/components/dotfiles/Aliases.tsx b/ui/src/components/dotfiles/Aliases.tsx
index 61fd001c..9af3e994 100644
--- a/ui/src/components/dotfiles/Aliases.tsx
+++ b/ui/src/components/dotfiles/Aliases.tsx
@@ -16,7 +16,7 @@ import { ColumnDef } from "@tanstack/react-table";
import { invoke } from "@tauri-apps/api/core";
import Drawer from "@/components/Drawer";
-import { Alias } from "@/state/models";
+import { Alias, inspectHistory } from "@/state/models";
import { useStore } from "@/state/store";
function deleteAlias(name: string, refreshAliases: () => void) {
diff --git a/ui/src/components/history/Stats.tsx b/ui/src/components/history/Stats.tsx
index ce92ac04..bc4e5c33 100644
--- a/ui/src/components/history/Stats.tsx
+++ b/ui/src/components/history/Stats.tsx
@@ -19,12 +19,60 @@ function renderLoading() {
);
}
+function TopTable({ stats }: any) {
+ console.log(stats);
+ return (
+ <div className="px-4 sm:px-6 lg:px-8">
+ <div className="flex items-center">
+ <div className="flex-auto">
+ <h1 className="text-base font-semibold">Top commands</h1>
+ </div>
+ </div>
+ <div className="mt-4 flow-root">
+ <div className="-mx-4 -my-2 overflow-x-auto sm:-mx-6 lg:-mx-8">
+ <div className="inline-block min-w-full py-2 align-middle">
+ <table className="min-w-full divide-y divide-gray-300">
+ <thead>
+ <tr>
+ <th
+ scope="col"
+ className="py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 sm:pl-6 lg:pl-8"
+ >
+ Command
+ </th>
+ <th
+ scope="col"
+ className="px-3 py-3.5 text-left text-sm font-semibold text-gray-900"
+ >
+ Count
+ </th>
+ </tr>
+ </thead>
+ <tbody className="divide-y divide-gray-200 bg-white">
+ {stats.map((stat) => (
+ <tr>
+ <td className="whitespace-nowrap py-4 pl-4 pr-3 text-sm font-medium text-gray-900 sm:pl-6 lg:pl-8">
+ {stat[0][0]}
+ </td>
+ <td className="whitespace-nowrap px-3 py-4 text-sm text-gray-500">
+ {stat[1]}
+ </td>
+ </tr>
+ ))}
+ </tbody>
+ </table>
+ </div>
+ </div>
+ </div>
+ </div>
+ );
+}
+
export default function Stats() {
const [stats, setStats]: any = useState([]);
+ const [top, setTop]: any = useState([]);
const [chart, setChart]: any = useState([]);
- console.log("Stats mounted");
-
useEffect(() => {
if (stats.length != 0) return;
@@ -38,6 +86,10 @@ export default function Stats() {
stat: s.total_history.toLocaleString(),
},
{
+ name: "Unique history",
+ stat: s.stats.unique_commands.toLocaleString(),
+ },
+ {
name: "Last 1d",
stat: s.last_1d.toLocaleString(),
},
@@ -52,20 +104,23 @@ export default function Stats() {
]);
setChart(s.daily);
+
+ setTop(s.stats);
})
.catch((e) => {
console.log(e);
});
}, []);
+ console.log(top);
if (stats.length == 0) {
return renderLoading();
}
return (
- <div className="flex flex-col">
+ <div className="flex flex-col overflow-y-scroll">
<div className="flexfull">
- <dl className="grid grid-cols-1 sm:grid-cols-4 w-full">
+ <dl className="grid grid-cols-1 sm:grid-cols-5 w-full">
{stats.map((item: any) => (
<div
key={item.name}
@@ -94,6 +149,10 @@ export default function Stats() {
</ResponsiveContainer>
</div>
</div>
+
+ <div>
+ <TopTable stats={top.top} />
+ </div>
</div>
);
}