diff options
| author | Ellie Huxtable <ellie@elliehuxtable.com> | 2024-05-06 08:11:47 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-05-06 08:11:47 +0100 |
| commit | 754ddeaa8d3e3e4f3efc93d5bb22c68c31bb5c36 (patch) | |
| tree | f48fb912c2be2d08855e97ff24b6919a115c3c4f /ui/src/components | |
| parent | chore(deps): bump serde_with from 3.7.0 to 3.8.1 (#2002) (diff) | |
| download | atuin-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.tsx | 126 | ||||
| -rw-r--r-- | ui/src/components/HistorySearch.tsx | 12 | ||||
| -rw-r--r-- | ui/src/components/dotfiles/Aliases.tsx | 2 | ||||
| -rw-r--r-- | ui/src/components/history/Stats.tsx | 67 |
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> on </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> on </span> - <span> in </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> in </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> ); } |
