mirror of
https://github.com/atuinsh/atuin.git
synced 2025-06-20 18:07:57 +02:00
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.
This commit is contained in:
parent
fcc0dc1bd5
commit
cb19925011
@ -11,7 +11,7 @@ use reqwest::{
|
|||||||
use atuin_common::{
|
use atuin_common::{
|
||||||
api::{
|
api::{
|
||||||
AddHistoryRequest, ChangePasswordRequest, CountResponse, DeleteHistoryRequest,
|
AddHistoryRequest, ChangePasswordRequest, CountResponse, DeleteHistoryRequest,
|
||||||
ErrorResponse, LoginRequest, LoginResponse, RegisterResponse, StatusResponse,
|
ErrorResponse, LoginRequest, LoginResponse, MeResponse, RegisterResponse, StatusResponse,
|
||||||
SyncHistoryResponse,
|
SyncHistoryResponse,
|
||||||
},
|
},
|
||||||
record::RecordStatus,
|
record::RecordStatus,
|
||||||
@ -234,6 +234,18 @@ impl<'a> Client<'a> {
|
|||||||
Ok(status)
|
Ok(status)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn me(&self) -> Result<MeResponse> {
|
||||||
|
let url = format!("{}/api/v0/me", self.sync_addr);
|
||||||
|
let url = Url::parse(url.as_str())?;
|
||||||
|
|
||||||
|
let resp = self.client.get(url).send().await?;
|
||||||
|
let resp = handle_resp_error(resp).await?;
|
||||||
|
|
||||||
|
let status = resp.json::<MeResponse>().await?;
|
||||||
|
|
||||||
|
Ok(status)
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn get_history(
|
pub async fn get_history(
|
||||||
&self,
|
&self,
|
||||||
sync_ts: OffsetDateTime,
|
sync_ts: OffsetDateTime,
|
||||||
|
@ -18,7 +18,7 @@ mod builder;
|
|||||||
pub mod store;
|
pub mod store;
|
||||||
|
|
||||||
const HISTORY_VERSION: &str = "v0";
|
const HISTORY_VERSION: &str = "v0";
|
||||||
const HISTORY_TAG: &str = "history";
|
pub const HISTORY_TAG: &str = "history";
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
|
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
|
||||||
pub struct HistoryId(pub String);
|
pub struct HistoryId(pub String);
|
||||||
|
@ -180,6 +180,16 @@ impl Store for SqliteStore {
|
|||||||
self.idx(host, tag, 0).await
|
self.idx(host, tag, 0).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn len_all(&self) -> Result<u64> {
|
||||||
|
let res: Result<(i64,), sqlx::Error> = sqlx::query_as("select count(*) from store")
|
||||||
|
.fetch_one(&self.pool)
|
||||||
|
.await;
|
||||||
|
match res {
|
||||||
|
Err(e) => Err(eyre!("failed to fetch local store len: {}", e)),
|
||||||
|
Ok(v) => Ok(v.0 as u64),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async fn len_tag(&self, tag: &str) -> Result<u64> {
|
async fn len_tag(&self, tag: &str) -> Result<u64> {
|
||||||
let res: Result<(i64,), sqlx::Error> =
|
let res: Result<(i64,), sqlx::Error> =
|
||||||
sqlx::query_as("select count(*) from store where tag=?1")
|
sqlx::query_as("select count(*) from store where tag=?1")
|
||||||
|
@ -25,6 +25,7 @@ pub trait Store {
|
|||||||
async fn delete(&self, id: RecordId) -> Result<()>;
|
async fn delete(&self, id: RecordId) -> Result<()>;
|
||||||
async fn delete_all(&self) -> Result<()>;
|
async fn delete_all(&self) -> Result<()>;
|
||||||
|
|
||||||
|
async fn len_all(&self) -> Result<u64>;
|
||||||
async fn len(&self, host: HostId, tag: &str) -> Result<u64>;
|
async fn len(&self, host: HostId, tag: &str) -> Result<u64>;
|
||||||
async fn len_tag(&self, tag: &str) -> Result<u64>;
|
async fn len_tag(&self, tag: &str) -> Result<u64>;
|
||||||
|
|
||||||
|
6
ui/backend/Cargo.lock
generated
6
ui/backend/Cargo.lock
generated
@ -212,7 +212,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "atuin-client"
|
name = "atuin-client"
|
||||||
version = "18.1.0"
|
version = "18.2.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"atuin-common",
|
"atuin-common",
|
||||||
@ -259,7 +259,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "atuin-common"
|
name = "atuin-common"
|
||||||
version = "18.1.0"
|
version = "18.2.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"eyre",
|
"eyre",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
@ -276,7 +276,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "atuin-dotfiles"
|
name = "atuin-dotfiles"
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"atuin-client",
|
"atuin-client",
|
||||||
"atuin-common",
|
"atuin-common",
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||||
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
use time::format_description::well_known::Rfc3339;
|
||||||
|
|
||||||
use atuin_client::settings::Settings;
|
use atuin_client::settings::Settings;
|
||||||
|
|
||||||
@ -9,9 +10,20 @@ mod db;
|
|||||||
mod dotfiles;
|
mod dotfiles;
|
||||||
mod store;
|
mod store;
|
||||||
|
|
||||||
|
use atuin_client::{
|
||||||
|
encryption, history::HISTORY_TAG, record::sqlite_store::SqliteStore, record::store::Store,
|
||||||
|
};
|
||||||
use db::{GlobalStats, HistoryDB, UIHistory};
|
use db::{GlobalStats, HistoryDB, UIHistory};
|
||||||
use dotfiles::aliases::aliases;
|
use dotfiles::aliases::aliases;
|
||||||
|
|
||||||
|
#[derive(Debug, serde::Serialize)]
|
||||||
|
struct HomeInfo {
|
||||||
|
pub username: String,
|
||||||
|
pub record_count: u64,
|
||||||
|
pub history_count: u64,
|
||||||
|
pub last_sync: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
async fn list() -> Result<Vec<UIHistory>, String> {
|
async fn list() -> Result<Vec<UIHistory>, String> {
|
||||||
let settings = Settings::new().map_err(|e| e.to_string())?;
|
let settings = Settings::new().map_err(|e| e.to_string())?;
|
||||||
@ -47,6 +59,54 @@ async fn global_stats() -> Result<GlobalStats, String> {
|
|||||||
Ok(stats)
|
Ok(stats)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
async fn home_info() -> Result<HomeInfo, String> {
|
||||||
|
let settings = Settings::new().map_err(|e| e.to_string())?;
|
||||||
|
let record_store_path = PathBuf::from(settings.record_store_path.as_str());
|
||||||
|
let sqlite_store = SqliteStore::new(record_store_path, settings.local_timeout)
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
let client = atuin_client::api_client::Client::new(
|
||||||
|
&settings.sync_address,
|
||||||
|
&settings.session_token,
|
||||||
|
settings.network_connect_timeout,
|
||||||
|
settings.network_timeout,
|
||||||
|
)
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
let session_path = settings.session_path.as_str();
|
||||||
|
let last_sync = Settings::last_sync()
|
||||||
|
.map_err(|e| e.to_string())?
|
||||||
|
.format(&Rfc3339)
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
let record_count = sqlite_store.len_all().await.map_err(|e| e.to_string())?;
|
||||||
|
let history_count = sqlite_store
|
||||||
|
.len_tag(HISTORY_TAG)
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
let info = if !PathBuf::from(session_path).exists() {
|
||||||
|
HomeInfo {
|
||||||
|
username: String::from(""),
|
||||||
|
last_sync: last_sync.to_string(),
|
||||||
|
record_count,
|
||||||
|
history_count,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let me = client.me().await.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
HomeInfo {
|
||||||
|
username: me.username,
|
||||||
|
last_sync: last_sync.to_string(),
|
||||||
|
record_count,
|
||||||
|
history_count,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(info)
|
||||||
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
tauri::Builder::default()
|
tauri::Builder::default()
|
||||||
.invoke_handler(tauri::generate_handler![
|
.invoke_handler(tauri::generate_handler![
|
||||||
@ -54,6 +114,7 @@ fn main() {
|
|||||||
search,
|
search,
|
||||||
global_stats,
|
global_stats,
|
||||||
aliases,
|
aliases,
|
||||||
|
home_info,
|
||||||
dotfiles::aliases::import_aliases,
|
dotfiles::aliases::import_aliases,
|
||||||
dotfiles::aliases::delete_alias,
|
dotfiles::aliases::delete_alias,
|
||||||
dotfiles::aliases::set_alias,
|
dotfiles::aliases::set_alias,
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
"class-variance-authority": "^0.7.0",
|
"class-variance-authority": "^0.7.0",
|
||||||
"clsx": "^2.1.0",
|
"clsx": "^2.1.0",
|
||||||
"core": "link:@tauri-apps/api/core",
|
"core": "link:@tauri-apps/api/core",
|
||||||
|
"date-fns": "^3.6.0",
|
||||||
"highlight.js": "^11.9.0",
|
"highlight.js": "^11.9.0",
|
||||||
"lucide-react": "^0.367.0",
|
"lucide-react": "^0.367.0",
|
||||||
"luxon": "^3.4.4",
|
"luxon": "^3.4.4",
|
||||||
@ -29,7 +30,8 @@
|
|||||||
"recharts": "^2.12.4",
|
"recharts": "^2.12.4",
|
||||||
"tailwind-merge": "^2.2.2",
|
"tailwind-merge": "^2.2.2",
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
"vaul": "^0.9.0"
|
"vaul": "^0.9.0",
|
||||||
|
"zustand": "^4.5.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tauri-apps/cli": "2.0.0-beta.2",
|
"@tauri-apps/cli": "2.0.0-beta.2",
|
||||||
|
38
ui/pnpm-lock.yaml
generated
38
ui/pnpm-lock.yaml
generated
@ -35,6 +35,9 @@ dependencies:
|
|||||||
core:
|
core:
|
||||||
specifier: link:@tauri-apps/api/core
|
specifier: link:@tauri-apps/api/core
|
||||||
version: link:@tauri-apps/api/core
|
version: link:@tauri-apps/api/core
|
||||||
|
date-fns:
|
||||||
|
specifier: ^3.6.0
|
||||||
|
version: 3.6.0
|
||||||
highlight.js:
|
highlight.js:
|
||||||
specifier: ^11.9.0
|
specifier: ^11.9.0
|
||||||
version: 11.9.0
|
version: 11.9.0
|
||||||
@ -65,6 +68,9 @@ dependencies:
|
|||||||
vaul:
|
vaul:
|
||||||
specifier: ^0.9.0
|
specifier: ^0.9.0
|
||||||
version: 0.9.0(@types/react-dom@18.2.24)(@types/react@18.2.74)(react-dom@18.2.0)(react@18.2.0)
|
version: 0.9.0(@types/react-dom@18.2.24)(@types/react@18.2.74)(react-dom@18.2.0)(react@18.2.0)
|
||||||
|
zustand:
|
||||||
|
specifier: ^4.5.2
|
||||||
|
version: 4.5.2(@types/react@18.2.74)(react@18.2.0)
|
||||||
|
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@tauri-apps/cli':
|
'@tauri-apps/cli':
|
||||||
@ -1786,6 +1792,10 @@ packages:
|
|||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/date-fns@3.6.0:
|
||||||
|
resolution: {integrity: sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/debug@4.3.4:
|
/debug@4.3.4:
|
||||||
resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
|
resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
|
||||||
engines: {node: '>=6.0'}
|
engines: {node: '>=6.0'}
|
||||||
@ -2679,6 +2689,14 @@ packages:
|
|||||||
tslib: 2.6.2
|
tslib: 2.6.2
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/use-sync-external-store@1.2.0(react@18.2.0):
|
||||||
|
resolution: {integrity: sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==}
|
||||||
|
peerDependencies:
|
||||||
|
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||||
|
dependencies:
|
||||||
|
react: 18.2.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/util-deprecate@1.0.2:
|
/util-deprecate@1.0.2:
|
||||||
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
|
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
|
||||||
|
|
||||||
@ -2798,3 +2816,23 @@ packages:
|
|||||||
resolution: {integrity: sha512-pIXzoImaqmfOrL7teGUBt/T7ZDnyeGBWyXQBvOVhLkWLN37GXv8NMLK406UY6dS51JfcQHsmcW5cJ441bHg6Lg==}
|
resolution: {integrity: sha512-pIXzoImaqmfOrL7teGUBt/T7ZDnyeGBWyXQBvOVhLkWLN37GXv8NMLK406UY6dS51JfcQHsmcW5cJ441bHg6Lg==}
|
||||||
engines: {node: '>= 14'}
|
engines: {node: '>= 14'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
/zustand@4.5.2(@types/react@18.2.74)(react@18.2.0):
|
||||||
|
resolution: {integrity: sha512-2cN1tPkDVkwCy5ickKrI7vijSjPksFRfqS6237NzT0vqSsztTNnQdHw9mmN7uBdk3gceVXU0a+21jFzFzAc9+g==}
|
||||||
|
engines: {node: '>=12.7.0'}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '>=16.8'
|
||||||
|
immer: '>=9.0.6'
|
||||||
|
react: '>=16.8'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
immer:
|
||||||
|
optional: true
|
||||||
|
react:
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
'@types/react': 18.2.74
|
||||||
|
react: 18.2.0
|
||||||
|
use-sync-external-store: 1.2.0(react@18.2.0)
|
||||||
|
dev: false
|
||||||
|
@ -1,7 +1,11 @@
|
|||||||
|
html {
|
||||||
|
overscroll-behavior: none;
|
||||||
|
}
|
||||||
|
|
||||||
.logo.vite:hover {
|
.logo.vite:hover {
|
||||||
filter: drop-shadow(0 0 2em #747bff);
|
filter: drop-shadow(0 0 2em #747bff);
|
||||||
}
|
}
|
||||||
|
|
||||||
.logo.react:hover {
|
.logo.react:hover {
|
||||||
filter: drop-shadow(0 0 2em #61dafb);
|
filter: drop-shadow(0 0 2em #61dafb);
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,9 @@
|
|||||||
import "./App.css";
|
import "./App.css";
|
||||||
|
|
||||||
import { Fragment, useState, useEffect, ReactElement } from "react";
|
import { useState, ReactElement } from "react";
|
||||||
import { Dialog, Transition } from "@headlessui/react";
|
|
||||||
import {
|
import {
|
||||||
Bars3Icon,
|
|
||||||
ChartPieIcon,
|
|
||||||
Cog6ToothIcon,
|
Cog6ToothIcon,
|
||||||
HomeIcon,
|
HomeIcon,
|
||||||
XMarkIcon,
|
|
||||||
MagnifyingGlassIcon,
|
|
||||||
ClockIcon,
|
ClockIcon,
|
||||||
WrenchScrewdriverIcon,
|
WrenchScrewdriverIcon,
|
||||||
} from "@heroicons/react/24/outline";
|
} from "@heroicons/react/24/outline";
|
||||||
@ -18,16 +13,20 @@ function classNames(...classes: any) {
|
|||||||
return classes.filter(Boolean).join(" ");
|
return classes.filter(Boolean).join(" ");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
import Home from "./pages/Home.tsx";
|
||||||
import History from "./pages/History.tsx";
|
import History from "./pages/History.tsx";
|
||||||
import Dotfiles from "./pages/Dotfiles.tsx";
|
import Dotfiles from "./pages/Dotfiles.tsx";
|
||||||
|
|
||||||
enum Section {
|
enum Section {
|
||||||
|
Home,
|
||||||
History,
|
History,
|
||||||
Dotfiles,
|
Dotfiles,
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderMain(section: Section): ReactElement {
|
function renderMain(section: Section): ReactElement {
|
||||||
switch (section) {
|
switch (section) {
|
||||||
|
case Section.Home:
|
||||||
|
return <Home />;
|
||||||
case Section.History:
|
case Section.History:
|
||||||
return <History />;
|
return <History />;
|
||||||
case Section.Dotfiles:
|
case Section.Dotfiles:
|
||||||
@ -39,9 +38,14 @@ function App() {
|
|||||||
// routers don't really work in Tauri. It's not a browser!
|
// routers don't really work in Tauri. It's not a browser!
|
||||||
// I think hashrouter may work, but I'd rather avoiding thinking of them as
|
// I think hashrouter may work, but I'd rather avoiding thinking of them as
|
||||||
// pages
|
// pages
|
||||||
const [section, setSection] = useState(Section.History);
|
const [section, setSection] = useState(Section.Home);
|
||||||
|
|
||||||
const navigation = [
|
const navigation = [
|
||||||
|
{
|
||||||
|
name: "Home",
|
||||||
|
icon: HomeIcon,
|
||||||
|
section: Section.Home,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "History",
|
name: "History",
|
||||||
icon: ClockIcon,
|
icon: ClockIcon,
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
import * as React from "react";
|
|
||||||
|
|
||||||
import { Drawer as VDrawer } from "vaul";
|
import { Drawer as VDrawer } from "vaul";
|
||||||
|
|
||||||
export default function Drawer({
|
export default function Drawer({
|
||||||
|
@ -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) {
|
// @ts-ignore
|
||||||
let milliseconds = (ms).toFixed(1);
|
import { DateTime } from "luxon";
|
||||||
let seconds = (ms / 1000).toFixed(1);
|
|
||||||
let minutes = (ms / (1000 * 60)).toFixed(1);
|
function msToTime(ms: number) {
|
||||||
let hours = (ms / (1000 * 60 * 60)).toFixed(1);
|
let milliseconds = parseInt(ms.toFixed(1));
|
||||||
let days = (ms / (1000 * 60 * 60 * 24)).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";
|
if (milliseconds < 1000) return milliseconds + "ms";
|
||||||
else if (seconds < 60) return seconds + "s";
|
else if (seconds < 60) return seconds + "s";
|
||||||
else if (minutes < 60) return minutes + "m";
|
else if (minutes < 60) return minutes + "m";
|
||||||
else if (hours < 24) return hours + "hr";
|
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 (
|
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
|
<span> on </span>
|
||||||
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> on </span>
|
<span className="relative truncate ">{h.host}</span>
|
||||||
|
|
||||||
<span className="relative truncate ">
|
<span> in </span>
|
||||||
{h.host}
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span> in </span>
|
<span className="relative truncate ">{h.cwd}</span>
|
||||||
|
</p>
|
||||||
<span className="relative truncate ">
|
</div>
|
||||||
{h.cwd}
|
</div>
|
||||||
</span>
|
<div className="flex shrink-0 items-center gap-x-4">
|
||||||
</p>
|
<div className="hidden sm:flex sm:flex-col sm:items-end">
|
||||||
</div>
|
<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>
|
</div>
|
||||||
<div className="flex shrink-0 items-center gap-x-4">
|
<p className="text-xs leading-5 text-gray-500">Online</p>
|
||||||
<div className="hidden sm:flex sm:flex-col sm:items-end">
|
</div>
|
||||||
<p className="text-sm leading-6 text-gray-900">{h.exit}</p>
|
)}
|
||||||
{h.duration ? (
|
</div>
|
||||||
<p className="mt-1 text-xs leading-5 text-gray-500">
|
<ChevronRightIcon
|
||||||
<time dateTime={h.duration}>{msToTime(h.duration / 1000000)}</time>
|
className="h-5 w-5 flex-none text-gray-400"
|
||||||
</p>
|
aria-hidden="true"
|
||||||
) : (
|
/>
|
||||||
<div className="mt-1 flex items-center gap-x-1.5">
|
</div>
|
||||||
<div className="flex-none rounded-full bg-emerald-500/20 p-1">
|
</li>
|
||||||
<div className="h-1.5 w-1.5 rounded-full bg-emerald-500" />
|
))}
|
||||||
</div>
|
</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>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
import DataTable from "@/components/ui/data-table";
|
import DataTable from "@/components/ui/data-table";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
@ -8,34 +8,21 @@ import {
|
|||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
DropdownMenuItem,
|
DropdownMenuItem,
|
||||||
DropdownMenuLabel,
|
DropdownMenuLabel,
|
||||||
DropdownMenuSeparator,
|
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from "@/components/ui/dropdown-menu";
|
} from "@/components/ui/dropdown-menu";
|
||||||
|
|
||||||
|
import { ColumnDef } from "@tanstack/react-table";
|
||||||
|
|
||||||
import { invoke } from "@tauri-apps/api/core";
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
import Drawer from "@/components/Drawer";
|
import Drawer from "@/components/Drawer";
|
||||||
|
|
||||||
function loadAliases(
|
import { Alias } from "@/state/models";
|
||||||
setAliases: React.Dispatch<React.SetStateAction<never[]>>,
|
import { useStore } from "@/state/store";
|
||||||
) {
|
|
||||||
invoke("aliases").then((aliases: any) => {
|
|
||||||
setAliases(aliases);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
type Alias = {
|
function deleteAlias(name: string, refreshAliases: () => void) {
|
||||||
name: string;
|
|
||||||
value: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
function deleteAlias(
|
|
||||||
name: string,
|
|
||||||
setAliases: React.Dispatch<React.SetStateAction<never[]>>,
|
|
||||||
) {
|
|
||||||
invoke("delete_alias", { name: name })
|
invoke("delete_alias", { name: name })
|
||||||
.then(() => {
|
.then(() => {
|
||||||
console.log("Deleted alias");
|
refreshAliases();
|
||||||
loadAliases(setAliases);
|
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
console.error("Failed to delete alias");
|
console.error("Failed to delete alias");
|
||||||
@ -101,7 +88,9 @@ function AddAlias({ onAdd: onAdd }: { onAdd?: () => void }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function Aliases() {
|
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);
|
let [aliasDrawerOpen, setAliasDrawerOpen] = useState(false);
|
||||||
|
|
||||||
const columns: ColumnDef<Alias>[] = [
|
const columns: ColumnDef<Alias>[] = [
|
||||||
@ -129,7 +118,7 @@ export default function Aliases() {
|
|||||||
<DropdownMenuContent align="end">
|
<DropdownMenuContent align="end">
|
||||||
<DropdownMenuLabel>Actions</DropdownMenuLabel>
|
<DropdownMenuLabel>Actions</DropdownMenuLabel>
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
onClick={() => deleteAlias(alias.name, setAliases)}
|
onClick={() => deleteAlias(alias.name, refreshAliases)}
|
||||||
>
|
>
|
||||||
Delete
|
Delete
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
@ -141,7 +130,7 @@ export default function Aliases() {
|
|||||||
];
|
];
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadAliases(setAliases);
|
refreshAliases();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -172,7 +161,7 @@ export default function Aliases() {
|
|||||||
>
|
>
|
||||||
<AddAlias
|
<AddAlias
|
||||||
onAdd={() => {
|
onAdd={() => {
|
||||||
loadAliases(setAliases);
|
refreshAliases();
|
||||||
setAliasDrawerOpen(false);
|
setAliasDrawerOpen(false);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -5,29 +5,18 @@ import PacmanLoader from "react-spinners/PacmanLoader";
|
|||||||
import {
|
import {
|
||||||
BarChart,
|
BarChart,
|
||||||
Bar,
|
Bar,
|
||||||
Rectangle,
|
|
||||||
XAxis,
|
XAxis,
|
||||||
YAxis,
|
YAxis,
|
||||||
CartesianGrid,
|
|
||||||
Tooltip,
|
Tooltip,
|
||||||
Legend,
|
|
||||||
ResponsiveContainer,
|
ResponsiveContainer,
|
||||||
} from "recharts";
|
} 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() {
|
function renderLoading() {
|
||||||
<div className="flex items-center justify-center h-full">
|
return (
|
||||||
<PacmanLoader color="#26bd65" />
|
<div className="flex items-center justify-center h-full">
|
||||||
</div>;
|
<PacmanLoader color="#26bd65" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Stats() {
|
export default function Stats() {
|
||||||
@ -77,7 +66,7 @@ export default function Stats() {
|
|||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<div className="flexfull">
|
<div className="flexfull">
|
||||||
<dl className="grid grid-cols-1 sm:grid-cols-4 w-full">
|
<dl className="grid grid-cols-1 sm:grid-cols-4 w-full">
|
||||||
{stats.map((item) => (
|
{stats.map((item: any) => (
|
||||||
<div
|
<div
|
||||||
key={item.name}
|
key={item.name}
|
||||||
className="overflow-hidden bg-white px-4 py-5 shadow sm:p-6"
|
className="overflow-hidden bg-white px-4 py-5 shadow sm:p-6"
|
||||||
@ -94,39 +83,6 @@ export default function Stats() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col h-54 py-4 pl-5">
|
<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">
|
<div className="flex flex-col h-48 pt-5 pr-5">
|
||||||
<ResponsiveContainer width="100%" height="100%">
|
<ResponsiveContainer width="100%" height="100%">
|
||||||
<BarChart width={500} height={300} data={chart}>
|
<BarChart width={500} height={300} data={chart}>
|
||||||
|
@ -1,12 +1,5 @@
|
|||||||
import { useState } from "react";
|
|
||||||
|
|
||||||
import { Cog6ToothIcon } from "@heroicons/react/24/outline";
|
|
||||||
|
|
||||||
import Aliases from "@/components/dotfiles/Aliases";
|
import Aliases from "@/components/dotfiles/Aliases";
|
||||||
|
|
||||||
import { Drawer } from "@/components/drawer";
|
|
||||||
import { invoke } from "@tauri-apps/api/core";
|
|
||||||
|
|
||||||
function Header() {
|
function Header() {
|
||||||
return (
|
return (
|
||||||
<div className="md:flex md:items-center md:justify-between">
|
<div className="md:flex md:items-center md:justify-between">
|
||||||
|
@ -1,40 +1,10 @@
|
|||||||
import { Fragment, useState, useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { Dialog, Transition } from "@headlessui/react";
|
|
||||||
import {
|
|
||||||
Bars3Icon,
|
|
||||||
ChartPieIcon,
|
|
||||||
Cog6ToothIcon,
|
|
||||||
HomeIcon,
|
|
||||||
XMarkIcon,
|
|
||||||
} from "@heroicons/react/24/outline";
|
|
||||||
|
|
||||||
import Logo from "../assets/logo-light.svg";
|
|
||||||
|
|
||||||
import { invoke } from "@tauri-apps/api/core";
|
|
||||||
|
|
||||||
import HistoryList from "@/components/HistoryList.tsx";
|
import HistoryList from "@/components/HistoryList.tsx";
|
||||||
import HistorySearch from "@/components/HistorySearch.tsx";
|
import HistorySearch from "@/components/HistorySearch.tsx";
|
||||||
import Stats from "@/components/history/Stats.tsx";
|
import Stats from "@/components/history/Stats.tsx";
|
||||||
import Drawer from "@/components/Drawer.tsx";
|
import Drawer from "@/components/Drawer.tsx";
|
||||||
|
import { useStore } from "@/state/store";
|
||||||
function refreshHistory(
|
|
||||||
setHistory: React.Dispatch<React.SetStateAction<never[]>>,
|
|
||||||
query: String | null,
|
|
||||||
) {
|
|
||||||
if (query) {
|
|
||||||
invoke("search", { query: query })
|
|
||||||
.then((res: any[]) => {
|
|
||||||
setHistory(res);
|
|
||||||
})
|
|
||||||
.catch((e) => {
|
|
||||||
console.log(e);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
invoke("list").then((h: any[]) => {
|
|
||||||
setHistory(h);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function Header() {
|
function Header() {
|
||||||
return (
|
return (
|
||||||
@ -44,7 +14,7 @@ function Header() {
|
|||||||
Shell History
|
Shell History
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-4 flex md:ml-4 md:mt-0">
|
<div className="flex">
|
||||||
<Drawer
|
<Drawer
|
||||||
width="70%"
|
width="70%"
|
||||||
trigger={
|
trigger={
|
||||||
@ -77,10 +47,11 @@ function Header() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function Search() {
|
export default function Search() {
|
||||||
let [history, setHistory] = useState([]);
|
const history = useStore((state) => state.shellHistory);
|
||||||
|
const refreshHistory = useStore((state) => state.refreshShellHistory);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
refreshHistory(setHistory, null);
|
refreshHistory();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -93,8 +64,8 @@ export default function Search() {
|
|||||||
|
|
||||||
<div className="flex h-16 shrink-0 items-center gap-x-4 border-b border-t border-gray-200 bg-white px-4 shadow-sm sm:gap-x-6 sm:px-6 lg:px-8">
|
<div className="flex h-16 shrink-0 items-center gap-x-4 border-b border-t border-gray-200 bg-white px-4 shadow-sm sm:gap-x-6 sm:px-6 lg:px-8">
|
||||||
<HistorySearch
|
<HistorySearch
|
||||||
refresh={(query: String | null) => {
|
refresh={(query?: string) => {
|
||||||
refreshHistory(setHistory, query);
|
refreshHistory(query);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
84
ui/src/pages/Home.tsx
Normal file
84
ui/src/pages/Home.tsx
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
import { useEffect } from "react";
|
||||||
|
import { formatRelative } from "date-fns";
|
||||||
|
|
||||||
|
import { useStore } from "@/state/store";
|
||||||
|
|
||||||
|
function Stats({ stats }: any) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<dl className="mt-5 grid grid-cols-1 gap-5 sm:grid-cols-3">
|
||||||
|
{stats.map((item: any) => (
|
||||||
|
<div
|
||||||
|
key={item.name}
|
||||||
|
className="overflow-hidden rounded-lg bg-white px-4 py-5 shadow sm:p-6"
|
||||||
|
>
|
||||||
|
<dt className="truncate text-sm font-medium text-gray-500">
|
||||||
|
{item.name}
|
||||||
|
</dt>
|
||||||
|
<dd className="mt-1 text-3xl font-semibold tracking-tight text-gray-900">
|
||||||
|
{item.stat}
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Header({ name }: any) {
|
||||||
|
let greeting = name && name.length > 0 ? "Hey, " + name + "!" : "Hey!";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="md:flex md:items-center md:justify-between">
|
||||||
|
<div className="min-w-0 flex-1">
|
||||||
|
<h2 className="text-2xl font-bold leading-7 text-gray-900 sm:truncate sm:text-3xl sm:tracking-tight">
|
||||||
|
{greeting}
|
||||||
|
</h2>
|
||||||
|
<h3 className="text-xl leading-7 text-gray-900 pt-4">
|
||||||
|
Welcome to Atuin.
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Home() {
|
||||||
|
const homeInfo = useStore((state) => state.homeInfo);
|
||||||
|
const refreshHomeInfo = useStore((state) => state.refreshHomeInfo);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
refreshHomeInfo();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (!homeInfo) {
|
||||||
|
return <div>Loading...</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="pl-60">
|
||||||
|
<div className="p-10">
|
||||||
|
<Header name={"Ellie"} />
|
||||||
|
|
||||||
|
<div className="pt-10">
|
||||||
|
<h2 className="text-xl font-bold">Sync</h2>
|
||||||
|
<Stats
|
||||||
|
stats={[
|
||||||
|
{
|
||||||
|
name: "Last Sync",
|
||||||
|
stat: formatRelative(homeInfo.lastSyncTime, new Date()),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Total history records",
|
||||||
|
stat: homeInfo.historyCount.toLocaleString(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Other records",
|
||||||
|
stat: homeInfo.recordCount - homeInfo.historyCount,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
34
ui/src/state/models.ts
Normal file
34
ui/src/state/models.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
export interface User {
|
||||||
|
username: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DefaultUser: User = {
|
||||||
|
username: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface HomeInfo {
|
||||||
|
historyCount: number;
|
||||||
|
recordCount: number;
|
||||||
|
lastSyncTime: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DefaultHomeInfo: HomeInfo = {
|
||||||
|
historyCount: 0,
|
||||||
|
recordCount: 0,
|
||||||
|
lastSyncTime: new Date(),
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface ShellHistory {
|
||||||
|
id: string;
|
||||||
|
timestamp: number;
|
||||||
|
command: string;
|
||||||
|
user: string;
|
||||||
|
host: string;
|
||||||
|
cwd: string;
|
||||||
|
duration: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Alias {
|
||||||
|
name: string;
|
||||||
|
value: string;
|
||||||
|
}
|
72
ui/src/state/store.ts
Normal file
72
ui/src/state/store.ts
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
import { create } from "zustand";
|
||||||
|
import { parseISO } from "date-fns";
|
||||||
|
|
||||||
|
import {
|
||||||
|
User,
|
||||||
|
DefaultUser,
|
||||||
|
HomeInfo,
|
||||||
|
DefaultHomeInfo,
|
||||||
|
Alias,
|
||||||
|
ShellHistory,
|
||||||
|
} from "./models";
|
||||||
|
|
||||||
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
|
|
||||||
|
// I'll probs want to slice this up at some point, but for now a
|
||||||
|
// big blobby lump of state is fine.
|
||||||
|
// Totally just hoping that structure will be emergent in the future.
|
||||||
|
interface AtuinState {
|
||||||
|
user: User;
|
||||||
|
homeInfo: HomeInfo;
|
||||||
|
aliases: Alias[];
|
||||||
|
shellHistory: ShellHistory[];
|
||||||
|
|
||||||
|
refreshHomeInfo: () => void;
|
||||||
|
refreshAliases: () => void;
|
||||||
|
refreshShellHistory: (query?: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useStore = create<AtuinState>()((set) => ({
|
||||||
|
user: DefaultUser,
|
||||||
|
homeInfo: DefaultHomeInfo,
|
||||||
|
aliases: [],
|
||||||
|
shellHistory: [],
|
||||||
|
|
||||||
|
refreshAliases: () => {
|
||||||
|
invoke("aliases").then((aliases: any) => {
|
||||||
|
set({ aliases: aliases });
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
refreshShellHistory: (query?: string) => {
|
||||||
|
if (query) {
|
||||||
|
invoke("search", { query: query })
|
||||||
|
.then((res: any) => {
|
||||||
|
set({ shellHistory: res });
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
console.log(e);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
invoke("list").then((res: any) => {
|
||||||
|
set({ shellHistory: res });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
refreshHomeInfo: () => {
|
||||||
|
invoke("home_info")
|
||||||
|
.then((res: any) => {
|
||||||
|
set({
|
||||||
|
homeInfo: {
|
||||||
|
historyCount: res.history_count,
|
||||||
|
recordCount: res.record_count,
|
||||||
|
lastSyncTime: parseISO(res.last_sync),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
console.log(e);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}));
|
Loading…
x
Reference in New Issue
Block a user