fix: rewrite state system

This commit is contained in:
Artemy Egorov 2024-07-30 15:36:11 +03:00
parent 7bb18fd062
commit febafc94df
12 changed files with 243 additions and 158 deletions

4
src-tauri/Cargo.lock generated
View file

@ -501,9 +501,9 @@ dependencies = [
[[package]] [[package]]
name = "dalet" name = "dalet"
version = "1.0.0-pre2" version = "1.0.0-pre3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7a4c96bc370794e34c34ed93abe3f04140b9909bc80b942b98a9abd7b8c248d" checksum = "c629ef0fc95fddd843a73e72de3f509665a73f51323f7b6c1b7946eb8937b660"
dependencies = [ dependencies = [
"serde", "serde",
] ]

View file

@ -25,7 +25,7 @@ tauri = { version = "1", features = [
] } ] }
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
serde_json = "1" serde_json = "1"
dalet = "1.0.0-pre2" dalet = "1.0.0-pre3"
reqwest = "0.12.5" reqwest = "0.12.5"
[features] [features]

View file

@ -1,118 +1,79 @@
// Prevents additional console window on Windows in release, DO NOT REMOVE!! // Prevents additional console window on Windows in release, DO NOT REMOVE!!
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
use std::{ use std::fs::{self};
fs::{self},
sync::Mutex,
};
use dalet::{Argument, Body, Tag};
mod types; mod types;
mod utils; mod utils;
use tauri::Manager; use tauri::async_runtime::Mutex;
use types::{TabType, VigiError, VigiState}; use types::{VigiError, VigiJsState, VigiState};
use utils::{read_or_create_jsonl, read_or_create_number}; use utils::{read_or_create_jsonl, read_or_create_number};
// Learn more about Tauri commands at https://tauri.app/v1/guides/features/command // Learn more about Tauri commands at https://tauri.app/v1/guides/features/command
#[tauri::command] #[tauri::command]
async fn process_input( async fn update_input(
input: String, input: String,
state: tauri::State<'_, Mutex<VigiState>>, state: tauri::State<'_, Mutex<VigiState>>,
) -> Result<Vec<Tag>, VigiError> {
// TODO: Implement mime type, language, protocol or search detection
// TODO: Implement text links parsing
println!("Processing: {}", input);
match reqwest::get(input.clone()).await {
Ok(res) => match res.text().await {
Ok(res) => {
update_tab(state, TabType::Text, res.clone(), input.clone())?;
Ok(vec![Tag::new(0, Body::Text(res), Argument::Null)])
}
Err(_) => Err(VigiError::Parse),
},
Err(_) => Err(VigiError::Network),
}
}
#[tauri::command]
fn get_state(state: tauri::State<Mutex<VigiState>>) -> VigiState {
(*state.lock().unwrap()).clone()
}
#[tauri::command]
fn select_tab(state: tauri::State<Mutex<VigiState>>, index: usize) -> Result<(), VigiError> {
match state.lock() {
Ok(mut state) => {
state.update_current_tab_index(index)?;
Ok(())
}
Err(_) => Err(VigiError::StateLock),
}
}
#[tauri::command]
fn add_tab(state: tauri::State<Mutex<VigiState>>) -> Result<(), VigiError> {
match state.lock() {
Ok(mut state) => {
state.add_tab()?;
Ok(())
}
Err(_) => Err(VigiError::StateLock),
}
}
#[tauri::command]
fn remove_tab(state: tauri::State<Mutex<VigiState>>, index: usize) -> Result<(), VigiError> {
match state.lock() {
Ok(mut state) => {
state.remove_tab(index)?;
Ok(())
}
Err(_) => Err(VigiError::StateLock),
}
}
fn update_tab(
state: tauri::State<Mutex<VigiState>>,
tab_type: TabType,
tab_title: String,
tab_url: String,
) -> Result<(), VigiError> { ) -> Result<(), VigiError> {
match state.lock() { state.lock().await.update_input(input).await
Ok(mut state) => { }
state.update_tab(tab_type, tab_title, tab_url)?;
Ok(()) #[tauri::command]
} async fn get_js_state(state: tauri::State<'_, Mutex<VigiState>>) -> Result<VigiJsState, VigiError> {
Err(_) => Err(VigiError::StateLock), Ok(state.lock().await.get_js_state())
} }
#[tauri::command]
async fn select_tab(
index: usize,
state: tauri::State<'_, Mutex<VigiState>>,
) -> Result<(), VigiError> {
state.lock().await.select_tab(index).await
}
#[tauri::command]
async fn load_tab(state: tauri::State<'_, Mutex<VigiState>>) -> Result<(), VigiError> {
state.lock().await.load_tab().await
}
#[tauri::command]
async fn add_tab(state: tauri::State<'_, Mutex<VigiState>>) -> Result<(), VigiError> {
state.lock().await.add_tab()
}
#[tauri::command]
async fn remove_tab(
state: tauri::State<'_, Mutex<VigiState>>,
index: usize,
) -> Result<(), VigiError> {
state.lock().await.remove_tab(index)
} }
fn main() { fn main() {
tauri::Builder::default() tauri::Builder::default()
.manage(Mutex::new(VigiState::null())) .manage(Mutex::new(VigiState::null()))
.setup(setup_handler)
.invoke_handler(tauri::generate_handler![ .invoke_handler(tauri::generate_handler![
process_input, update_input,
get_state, get_js_state,
select_tab, select_tab,
load_tab,
add_tab, add_tab,
remove_tab remove_tab,
setup
]) ])
.run(tauri::generate_context!()) .run(tauri::generate_context!())
.expect("error while running tauri application"); .expect("error while running tauri application");
} }
fn setup_handler(app: &mut tauri::App) -> Result<(), Box<dyn std::error::Error + 'static>> { #[tauri::command]
async fn setup(
state: tauri::State<'_, Mutex<VigiState>>,
app_handle: tauri::AppHandle,
) -> Result<(), VigiError> {
println!("---Setup---"); println!("---Setup---");
let app_handle = app.handle(); let mut state = state.lock().await;
let state = app.state::<Mutex<VigiState>>();
let mut state = state.lock().unwrap();
let config_dir = app_handle let config_dir = app_handle
.path_resolver() .path_resolver()
@ -138,7 +99,7 @@ fn setup_handler(app: &mut tauri::App) -> Result<(), Box<dyn std::error::Error +
// check if config/favorites.jsonl exists // check if config/favorites.jsonl exists
if !config_dir.exists() { if !config_dir.exists() {
println!(" Creating config dir"); println!(" Creating config dir");
fs::create_dir_all(&config_dir)?; fs::create_dir_all(&config_dir).map_err(|_| VigiError::Config)?;
} }
state.favorites_tabs_path = config_dir.join("favorites.jsonl"); state.favorites_tabs_path = config_dir.join("favorites.jsonl");
@ -147,7 +108,7 @@ fn setup_handler(app: &mut tauri::App) -> Result<(), Box<dyn std::error::Error +
println!("Checking local data dir"); println!("Checking local data dir");
if !local_data_dir.exists() { if !local_data_dir.exists() {
println!(" Creating local data dir"); println!(" Creating local data dir");
fs::create_dir_all(&local_data_dir)?; fs::create_dir_all(&local_data_dir).map_err(|_| VigiError::Config)?;
} }
state.local_tabs_path = local_data_dir.join("tabs.jsonl"); state.local_tabs_path = local_data_dir.join("tabs.jsonl");
@ -159,6 +120,8 @@ fn setup_handler(app: &mut tauri::App) -> Result<(), Box<dyn std::error::Error +
state.current_tab_index_path = local_data_dir.join("current_tab_index"); state.current_tab_index_path = local_data_dir.join("current_tab_index");
state.current_tab_index = read_or_create_number(&state.current_tab_index_path); state.current_tab_index = read_or_create_number(&state.current_tab_index_path);
state.update_top_bar_input();
println!("---Setup done---"); println!("---Setup done---");
Ok(()) Ok(())

View file

@ -1,3 +1,4 @@
use dalet::{Argument, Body, Tag};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::{fs, path::PathBuf}; use std::{fs, path::PathBuf};
@ -9,9 +10,11 @@ pub enum VigiError {
Parse, Parse,
StateLock, StateLock,
StateUpdate, StateUpdate,
Filesystem,
Config,
} }
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Debug, Clone)]
pub struct VigiState { pub struct VigiState {
pub tabs_id_counter_path: PathBuf, pub tabs_id_counter_path: PathBuf,
pub current_tab_index_path: PathBuf, pub current_tab_index_path: PathBuf,
@ -20,10 +23,25 @@ pub struct VigiState {
pub cache_dir: PathBuf, pub cache_dir: PathBuf,
// Persistent
pub tabs_id_counter: usize, pub tabs_id_counter: usize,
pub current_tab_index: usize, pub current_tab_index: usize,
pub tabs: Vec<Tab>, pub tabs: Vec<Tab>,
pub favorites_tabs: Vec<Tab>, pub favorites_tabs: Vec<Tab>,
// Temporary
pub top_bar_input: String,
pub current_data: Vec<Tag>,
}
#[derive(Serialize, Debug, Clone)]
pub struct VigiJsState {
pub current_tab_index: usize,
pub tabs: Vec<Tab>,
pub favorites_tabs: Vec<Tab>,
pub top_bar_input: String,
pub current_data: Vec<Tag>,
} }
impl VigiState { impl VigiState {
@ -39,16 +57,24 @@ impl VigiState {
current_tab_index: 0, current_tab_index: 0,
tabs: Vec::new(), tabs: Vec::new(),
favorites_tabs: Vec::new(), favorites_tabs: Vec::new(),
top_bar_input: "".to_string(),
current_data: Vec::new(),
} }
} }
pub fn update_current_tab_index(&mut self, new_index: usize) -> Result<(), VigiError> { pub async fn select_tab(&mut self, new_index: usize) -> Result<(), VigiError> {
self.current_tab_index = new_index; self.current_tab_index = new_index;
self.write_current_tab_index()?; self.write_current_tab_index()?;
self.update_top_bar_input();
Ok(()) Ok(())
} }
pub fn update_top_bar_input(&mut self) {
self.top_bar_input = self.tabs[self.current_tab_index].url.clone();
}
fn write_current_tab_index(&mut self) -> Result<(), VigiError> { fn write_current_tab_index(&mut self) -> Result<(), VigiError> {
fs::write( fs::write(
&self.current_tab_index_path, &self.current_tab_index_path,
@ -62,7 +88,61 @@ impl VigiState {
.map_err(|_| VigiError::StateUpdate) .map_err(|_| VigiError::StateUpdate)
} }
pub fn update_tab( async fn process_input(&mut self) -> Result<(), VigiError> {
// TODO: Implement mime type, language, protocol or search detection
// TODO: Implement text links parsing
println!("process_input {{\n \"{}\"", self.top_bar_input);
let result = match self.top_bar_input.as_str() {
"" => {
self.update_tab(TabType::HomePage, "Home".to_owned(), "".to_owned())?;
self.current_data = vec![Tag::new(
0,
Body::Text("Type something in the address bar".to_owned()),
Argument::Null,
)];
Ok(())
}
input => match reqwest::get(input).await {
Ok(res) => match res.text().await {
Ok(res) => {
let mut truncated = res.clone();
truncated.truncate(50);
self.update_tab(TabType::Text, truncated, input.to_owned())?;
self.current_data = vec![Tag::new(0, Body::Text(res), Argument::Null)];
Ok(())
}
Err(_) => Err(VigiError::Parse),
},
Err(_) => Err(VigiError::Network),
},
};
if result.is_ok() {
println!(" Ok\n}}");
} else {
println!(" Err\n}}");
}
result
}
pub async fn update_input(&mut self, input: String) -> Result<(), VigiError> {
self.top_bar_input = input;
self.process_input().await
}
pub async fn load_tab(&mut self) -> Result<(), VigiError> {
self.process_input().await
}
fn update_tab(
&mut self, &mut self,
tab_type: TabType, tab_type: TabType,
tab_title: String, tab_title: String,
@ -84,7 +164,7 @@ impl VigiState {
self.tabs_id_counter += 1; self.tabs_id_counter += 1;
self.tabs.push(Tab::new( self.tabs.push(Tab::new(
TabType::HomePage, TabType::HomePage,
"New tab".to_string(), "Home".to_string(),
"".to_string(), "".to_string(),
self.tabs_id_counter, self.tabs_id_counter,
)); ));
@ -95,6 +175,8 @@ impl VigiState {
self.current_tab_index = self.tabs.len() - 1; self.current_tab_index = self.tabs.len() - 1;
self.write_current_tab_index()?; self.write_current_tab_index()?;
self.update_top_bar_input();
Ok(()) Ok(())
} }
@ -112,14 +194,25 @@ impl VigiState {
Ok(()) Ok(())
} }
pub fn get_js_state(&self) -> VigiJsState {
VigiJsState {
current_tab_index: self.current_tab_index,
tabs: self.tabs.clone(),
favorites_tabs: self.favorites_tabs.clone(),
top_bar_input: self.top_bar_input.clone(),
current_data: self.current_data.clone(),
}
}
} }
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Tab { pub struct Tab {
ty: TabType, pub ty: TabType,
title: String, pub title: String,
url: String, pub url: String,
id: usize, pub id: usize,
} }
impl Tab { impl Tab {

View file

@ -11,7 +11,7 @@
} }
body { body {
@apply bg-vigi-90 text-vigi-10; @apply bg-vigi-90 text-vigi-10 cursor-default;
user-select: none; user-select: none;
} }
@ -31,7 +31,7 @@ body {
} }
.browser-window { .browser-window {
@apply grow shadow-inner overflow-auto select-text; @apply grow shadow-inner overflow-auto select-text cursor-auto;
} }
.window-controls { .window-controls {
@ -48,7 +48,7 @@ body {
@apply active:bg-vigi-100; @apply active:bg-vigi-100;
} }
.open-tabs { .tabs-category {
@apply flex justify-between mt-2 mx-2; @apply flex justify-between mt-2 mx-2;
} }

View file

@ -2,13 +2,24 @@
import type { Root } from "@txtdot/dalet"; import type { Root } from "@txtdot/dalet";
import Block from "./Block.svelte"; import Block from "./Block.svelte";
import Renderer from "./DaletlRenderer/Renderer.svelte"; import Renderer from "./DaletlRenderer/Renderer.svelte";
import { isLoading, state } from "$lib/stores";
import type { VigiState } from "$lib/types";
export let data: Root; let loading = false;
export let isLoading = false;
let data: Root;
state.subscribe((st) => {
data = (st as VigiState).current_data;
});
isLoading.subscribe((val) => {
loading = val;
});
</script> </script>
<Block className="browser-window"> <Block className="browser-window">
{#if isLoading} {#if loading}
<div>Loading...</div> <div>Loading...</div>
{:else} {:else}
<Renderer {data} /> <Renderer {data} />

View file

@ -29,7 +29,7 @@
{#if collapsed} {#if collapsed}
<WindowControls /> <WindowControls />
<div class="open-tabs"> <div class="tabs-category">
Open tabs Open tabs
<Button onClick={addTab}> <Button onClick={addTab}>
<Add /> <Add />

View file

@ -4,21 +4,21 @@
import Reload from "$lib/icons/Reload.svelte"; import Reload from "$lib/icons/Reload.svelte";
import SidebarLeft from "$lib/icons/SidebarLeft.svelte"; import SidebarLeft from "$lib/icons/SidebarLeft.svelte";
import SidebarRight from "$lib/icons/SidebarRight.svelte"; import SidebarRight from "$lib/icons/SidebarRight.svelte";
import { topBarInput } from "$lib/stores"; import { state } from "$lib/stores";
import { updateInput } from "$lib/utils";
import Block from "./Block.svelte"; import Block from "./Block.svelte";
import Button from "./Button.svelte"; import Button from "./Button.svelte";
export let onBack = () => {}; export let onBack = () => {};
export let onForward = () => {}; export let onForward = () => {};
export let onInput = () => {};
export let sidebarOpen = true; export let sidebarOpen = true;
let currentInput = ""; let currentInput = "";
let input = ""; let input = "";
topBarInput.subscribe((val) => { state.subscribe((val) => {
input = val; input = val.top_bar_input;
currentInput = decodeURIComponent(input); currentInput = decodeURIComponent(input);
}); });
@ -36,7 +36,7 @@
</Button> </Button>
<Button onClick={onBack}><ArrowLeft /></Button> <Button onClick={onBack}><ArrowLeft /></Button>
<Button onClick={onForward}><ArrowRight /></Button> <Button onClick={onForward}><ArrowRight /></Button>
<Button onClick={onInput}><Reload /></Button> <Button onClick={() => updateInput(input)}><Reload /></Button>
</Block> </Block>
<input <input
@ -47,8 +47,7 @@
bind:this={iEl} bind:this={iEl}
on:keypress={(e) => { on:keypress={(e) => {
if (e.key === "Enter") { if (e.key === "Enter") {
topBarInput.set(currentInput); updateInput(currentInput);
onInput();
} }
}} }}
on:focus={() => { on:focus={() => {

View file

@ -1,6 +1,12 @@
import { writable, type Writable } from "svelte/store"; import { writable, type Writable } from "svelte/store";
import type { VigiState } from "./types"; import type { VigiState } from "./types";
export const topBarInput: Writable<string> = writable(""); export const state: Writable<VigiState> = writable({
current_tab_index: 0,
tabs: [{ id: 0, ty: "HomePage", title: "Home", url: "" }],
favorites_tabs: [],
top_bar_input: "",
current_data: [],
});
export const state: Writable<VigiState> = writable(); export const isLoading: Writable<boolean> = writable(false);

View file

@ -1,7 +1,12 @@
import type { Tag } from "@txtdot/dalet";
export interface VigiState { export interface VigiState {
current_tab_index: number; current_tab_index: number;
tabs: StateTab[]; tabs: StateTab[];
favorites_tabs: StateTab[]; favorites_tabs: StateTab[];
top_bar_input: string;
current_data: Tag[];
} }
type TabType = "HomePage" | "Text"; type TabType = "HomePage" | "Text";

View file

@ -1,32 +1,52 @@
import { invoke } from "@tauri-apps/api"; import { invoke } from "@tauri-apps/api";
import { state, topBarInput } from "./stores"; import { isLoading, state } from "./stores";
import type { StateTab, VigiState } from "./types"; import type { VigiState } from "./types";
export function updateVigiState() { export async function updateVigiState() {
invoke("get_state") try {
.then((r) => { let st = await invoke("get_js_state");
let st = r as VigiState; state.set(st as VigiState);
} catch (e) {
console.log(e);
}
}
state.set(st); export async function updateInput(input: string) {
isLoading.set(true);
topBarInput.set(st.tabs[st.current_tab_index].url); try {
}) await invoke("update_input", { input });
.catch((err) => console.log(err)); } catch (e) {
console.log(e);
} finally {
await updateVigiState();
isLoading.set(false);
}
} }
export async function addTab() { export async function addTab() {
await invoke("add_tab"); await invoke("add_tab");
await updateVigiState();
updateVigiState(); await loadTab();
} }
export async function selectTab(index: number) { export async function selectTab(index: number) {
await invoke("select_tab", { index }); await invoke("select_tab", { index });
await updateVigiState();
updateVigiState(); await loadTab();
} }
export async function removeTab(index: number) { export async function removeTab(index: number) {
await invoke("remove_tab", { index }); await invoke("remove_tab", { index });
updateVigiState(); await updateVigiState();
await loadTab();
}
export async function loadTab() {
isLoading.set(true);
await invoke("load_tab");
await updateVigiState();
isLoading.set(false);
} }

View file

@ -4,43 +4,31 @@
import TopBar from "$lib/components/TopBar.svelte"; import TopBar from "$lib/components/TopBar.svelte";
import Sidebar from "$lib/components/Sidebar.svelte"; import Sidebar from "$lib/components/Sidebar.svelte";
import BrowserWindow from "$lib/components/BrowserWindow.svelte"; import BrowserWindow from "$lib/components/BrowserWindow.svelte";
import type { Root } from "@txtdot/dalet";
import { invoke } from "@tauri-apps/api/tauri"; import { invoke } from "@tauri-apps/api/tauri";
import { topBarInput } from "$lib/stores"; import { loadTab, updateVigiState } from "$lib/utils";
import { updateVigiState } from "$lib/utils"; import { isLoading } from "$lib/stores";
let sidebarOpen = true; let sidebarOpen = true;
let isLoading = false; (async () => {
isLoading.set(true);
let data: Root = []; await invoke("setup");
await updateVigiState();
updateVigiState(); await loadTab();
isLoading.set(false);
})();
document.addEventListener("keypress", (e: KeyboardEvent) => { document.addEventListener("keypress", (e: KeyboardEvent) => {
const formElements = ["INPUT", "TEXTAREA", "SELECT", "OPTION"]; if (
if (formElements.includes((e.target as Element).tagName)) { ["INPUT", "TEXTAREA", "SELECT", "OPTION"].includes(
(e.target as Element).tagName
)
) {
return; return;
} }
if (e.key === "q") sidebarOpen = !sidebarOpen; if (e.key === "q") sidebarOpen = !sidebarOpen;
}); });
topBarInput.subscribe((input) => {
isLoading = true;
invoke("process_input", { input })
.then((res) => {
data = res as Root;
isLoading = false;
})
.catch((err) => {
data = [{ id: 0, body: "Error: " + err, argument: null }];
isLoading = false;
})
.finally(() => {
updateVigiState();
});
});
</script> </script>
<div <div
@ -51,6 +39,6 @@
<div class="main-window"> <div class="main-window">
<TopBar bind:sidebarOpen /> <TopBar bind:sidebarOpen />
<BrowserWindow {data} bind:isLoading /> <BrowserWindow />
</div> </div>
</div> </div>