Compare commits

...
Sign in to create a new pull request.

5 commits
ci ... master

14 changed files with 1264 additions and 1214 deletions

574
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -9,17 +9,17 @@ edition = "2024"
[workspace.dependencies]
anyhow = "1.0.98"
clap = { version = "4.5.37", features = ["derive", "deprecated"] }
clap = { version = "4.5.38", features = ["derive", "deprecated"] }
directories = "6.0.0"
gdn = { path = "gdn" }
git2 = { version = "0.20.1", features = ["vendored-libgit2", "vendored-openssl"] }
jiff = "0.2.11"
git2 = { version = "0.20.2", features = ["vendored-libgit2", "vendored-openssl"] }
jiff = "0.2.14"
rand = "0.9.1"
serde = { version = "1.0.219", features = ["derive"] }
serde_json = "1.0.140"
tauri = { version = "2.5.1", features = [] }
tauri-build = { version = "2.2.0", features = [] }
tauri-plugin-opener = "2.2.6"
tauri-plugin-opener = "2.2.7"
[workspace.lints]
rust.unsafe_code = { level = "forbid", priority = 1 }

View file

@ -13,27 +13,27 @@
"dependencies": {
"@floating-ui/vue": "^1.1.6",
"@remixicon/vue": "^4.6.0",
"@tailwindcss/vite": "^4.1.4",
"@tailwindcss/vite": "^4.1.7",
"@tauri-apps/api": "^2.5.0",
"@tauri-apps/plugin-opener": "^2.2.6",
"@tauri-apps/plugin-opener": "^2.2.7",
"@vueuse/core": "^12.8.2",
"pinia": "^2.3.1",
"tailwindcss": "^4.1.4",
"vue": "^3.5.13",
"zod": "^3.24.3"
"tailwindcss": "^4.1.7",
"vue": "^3.5.15",
"zod": "^3.25.29"
},
"devDependencies": {
"@eslint/js": "^9.25.1",
"@eslint/js": "^9.27.0",
"@tauri-apps/cli": "^2.5.0",
"@types/node": "^22.15.2",
"@vitejs/plugin-vue": "^5.2.3",
"eslint": "^9.25.1",
"eslint-config-prettier": "^10.1.2",
"@types/node": "^22.15.21",
"@vitejs/plugin-vue": "^5.2.4",
"eslint": "^9.27.0",
"eslint-config-prettier": "^10.1.5",
"eslint-plugin-vue": "^9.33.0",
"typescript": "^5.8.3",
"typescript-eslint": "^8.31.0",
"vite": "^6.3.3",
"vite-plugin-vue-devtools": "^7.7.5",
"typescript-eslint": "^8.32.1",
"vite": "^6.3.5",
"vite-plugin-vue-devtools": "^7.7.6",
"vue-tsc": "^2.2.10"
}
}

View file

@ -4,18 +4,22 @@ use gdn::ids::NoteId;
use tauri::{AppHandle, Emitter, State};
use crate::{
store::Store,
state::AppState,
types::{EventNotesStoreUpdate, Note},
};
// API methods are sorted alphabetically.
fn update_if_required(store: &mut Store, app: &AppHandle) {
if let Some(store_id) = store.needs_update() {
let payload = EventNotesStoreUpdate { store_id };
app.emit("notes_store_update", payload).unwrap();
store.update();
fn update_if_required(state: &mut AppState, app: &AppHandle) {
let store_id = state.store.id();
if state.store_last_id == Some(store_id) {
// No update necessary if the id hasn't changed
return;
}
let payload = EventNotesStoreUpdate { store_id };
app.emit("notes_store_update", payload).unwrap();
state.store_last_id = Some(store_id)
}
#[tauri::command]
@ -24,10 +28,12 @@ pub fn note_child_add(
child_id: NoteId,
child_position: isize,
app: AppHandle,
store: State<'_, Arc<Mutex<Store>>>,
state: State<'_, Arc<Mutex<AppState>>>,
) {
let mut guard = store.lock().unwrap();
guard.add_child_at_position(id, child_id, child_position);
let mut guard = state.lock().unwrap();
guard
.store
.add_child_at_position(id, child_id, child_position);
update_if_required(&mut guard, &app);
}
@ -39,10 +45,12 @@ pub fn note_child_move(
to_id: NoteId,
to_position: isize,
app: AppHandle,
store: State<'_, Arc<Mutex<Store>>>,
state: State<'_, Arc<Mutex<AppState>>>,
) {
let mut guard = store.lock().unwrap();
guard.move_child_by_id_to_position(child_id, from_id, from_iteration, to_id, to_position);
let mut guard = state.lock().unwrap();
guard
.store
.move_child_by_id_to_position(child_id, from_id, from_iteration, to_id, to_position);
update_if_required(&mut guard, &app);
}
@ -52,10 +60,12 @@ pub fn note_child_remove(
child_id: NoteId,
child_iteration: usize,
app: AppHandle,
store: State<'_, Arc<Mutex<Store>>>,
state: State<'_, Arc<Mutex<AppState>>>,
) {
let mut guard = store.lock().unwrap();
guard.remove_child_by_id(id, child_id, child_iteration);
let mut guard = state.lock().unwrap();
guard
.store
.remove_child_by_id(id, child_id, child_iteration);
update_if_required(&mut guard, &app);
}
@ -64,32 +74,33 @@ pub fn note_children_set(
id: NoteId,
children: Vec<NoteId>,
app: AppHandle,
store: State<'_, Arc<Mutex<Store>>>,
state: State<'_, Arc<Mutex<AppState>>>,
) {
let mut guard = store.lock().unwrap();
guard.set_children(id, children);
let mut guard = state.lock().unwrap();
guard.store.set_children(id, children);
update_if_required(&mut guard, &app);
}
#[tauri::command]
pub fn note_create(text: String, app: AppHandle, store: State<'_, Arc<Mutex<Store>>>) -> Note {
let mut guard = store.lock().unwrap();
let id = guard.create(text);
pub fn note_create(text: String, app: AppHandle, state: State<'_, Arc<Mutex<AppState>>>) -> Note {
let mut guard = state.lock().unwrap();
let id = guard.store.create(text);
let note = guard.store.get(id).unwrap().into();
update_if_required(&mut guard, &app);
guard.get(id).unwrap()
note
}
#[tauri::command]
pub fn note_delete(id: NoteId, app: AppHandle, store: State<'_, Arc<Mutex<Store>>>) {
let mut guard = store.lock().unwrap();
guard.delete(id);
pub fn note_delete(id: NoteId, app: AppHandle, state: State<'_, Arc<Mutex<AppState>>>) {
let mut guard = state.lock().unwrap();
guard.store.delete(id);
update_if_required(&mut guard, &app);
}
#[tauri::command]
pub fn note_get(id: NoteId, store: State<'_, Arc<Mutex<Store>>>) -> Option<Note> {
let guard = store.lock().unwrap();
guard.get(id)
pub fn note_get(id: NoteId, state: State<'_, Arc<Mutex<AppState>>>) -> Option<Note> {
let guard = state.lock().unwrap();
guard.store.get(id).map(|it| it.into())
}
#[tauri::command]
@ -97,16 +108,16 @@ pub fn note_text_set(
id: NoteId,
text: String,
app: AppHandle,
store: State<'_, Arc<Mutex<Store>>>,
state: State<'_, Arc<Mutex<AppState>>>,
) {
let mut guard = store.lock().unwrap();
guard.set_text(id, text);
let mut guard = state.lock().unwrap();
guard.store.set_text(id, text);
update_if_required(&mut guard, &app);
}
#[tauri::command]
pub fn notes_clear(app: AppHandle, store: State<'_, Arc<Mutex<Store>>>) {
let mut guard = store.lock().unwrap();
guard.clear();
pub fn notes_clear(app: AppHandle, state: State<'_, Arc<Mutex<AppState>>>) {
let mut guard = state.lock().unwrap();
guard.store.clear();
update_if_required(&mut guard, &app);
}

View file

@ -1,16 +1,21 @@
use serde_json as _; // Silence unused dependency warning
use std::sync::{Arc, Mutex};
use store::Store;
use crate::state::AppState;
mod api;
pub mod store;
mod state;
mod types;
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
let state = Arc::new(Mutex::new(AppState::new()));
// TODO Launch store loading task
tauri::Builder::default()
.plugin(tauri_plugin_opener::init())
.manage(Arc::new(Mutex::new(Store::new())))
.manage(state)
.invoke_handler(tauri::generate_handler![
api::note_child_add,
api::note_child_move,

View file

@ -0,0 +1,15 @@
use gdn::store::Store;
pub struct AppState {
pub store: Store,
pub store_last_id: Option<u64>,
}
impl AppState {
pub fn new() -> Self {
Self {
store: Store::new(),
store_last_id: None,
}
}
}

View file

@ -1,6 +1,6 @@
use std::collections::HashSet;
use gdn::ids::NoteId;
use gdn::{ids::NoteId, store::RichNote};
use serde::Serialize;
#[derive(Serialize)]
@ -12,6 +12,17 @@ pub struct Note {
pub parents: HashSet<NoteId>,
}
impl From<RichNote> for Note {
fn from(value: RichNote) -> Self {
Self {
id: value.id,
text: value.text,
children: value.children,
parents: value.parents,
}
}
}
////////////
// Events //
////////////

View file

@ -1,6 +1,6 @@
<template>
<div
class="flex items-center rounded-md bg-neutral-800 px-2 hover:bg-neutral-700 active:bg-neutral-500"
class="flex h-7 w-7 items-center justify-center rounded-md bg-neutral-800 hover:bg-neutral-700 active:bg-neutral-500"
>
<slot></slot>
</div>

View file

@ -1,4 +1,4 @@
import { z } from "zod";
import { z } from "zod/v4";
export type NodeId = z.infer<typeof NodeId>;
export const NodeId = z.string().startsWith("n").length(17);

View file

@ -1,6 +1,7 @@
pub mod data;
pub mod ids;
pub mod repo;
pub mod store;
pub const PROPER_NAME: &str = "GedächtNAS";
pub const TECHNICAL_NAME: &str = "gedaechtnas";

View file

@ -86,6 +86,7 @@ pub fn save(path: &Path, repo: Repo) -> anyhow::Result<Oid> {
let message = Zoned::now().to_string();
// TODO Check that the repo is actually based on this commit.
// TODO Check if there actually is a difference to the parent commit
let parent = match read_head(&repository)? {
None => None,
Some(parent) => Some(parent.peel_to_commit()?),

View file

@ -1,21 +1,45 @@
use std::collections::{HashMap, HashSet};
use gdn::ids::NoteId;
use crate::{
ids::NoteId,
repo::{Note, Repo},
};
use crate::types::Note;
#[derive(Debug)]
pub struct NoteInfo {
#[derive(Clone)]
pub struct RawNote {
pub text: String,
pub children: Vec<NoteId>,
}
/// A note store for testing.
impl RawNote {
pub fn load(note: Note) -> Self {
Self {
text: note.text,
children: note.children,
}
}
pub fn save(self, id: NoteId) -> Note {
Note {
id,
text: self.text,
children: self.children,
}
}
}
#[derive(Clone)]
pub struct RichNote {
pub id: NoteId,
pub text: String,
pub children: Vec<NoteId>,
pub parents: HashSet<NoteId>,
}
#[derive(Default)]
pub struct Store {
last_id: u64,
curr_id: u64,
notes: HashMap<NoteId, NoteInfo>,
id: u64,
notes: HashMap<NoteId, RawNote>,
parents: HashMap<NoteId, HashMap<NoteId, usize>>,
}
@ -24,19 +48,36 @@ impl Store {
Self::default()
}
pub fn needs_update(&self) -> Option<u64> {
if self.last_id != self.curr_id {
Some(self.curr_id)
} else {
None
}
pub fn load(repo: Repo) -> Self {
let notes = repo
.notes
.into_iter()
.map(|note| (note.id, RawNote::load(note)))
.collect::<HashMap<_, _>>();
let mut result = Self {
notes,
..Self::default()
};
result.make_consistent_and_tick();
result
}
pub fn update(&mut self) {
self.last_id = self.curr_id;
pub fn save(&self) -> Repo {
let notes = self
.notes
.iter()
.map(|(id, note)| note.clone().save(*id))
.collect::<Vec<_>>();
Repo { notes }
}
pub fn get(&self, id: NoteId) -> Option<Note> {
pub fn id(&self) -> u64 {
self.id
}
pub fn get(&self, id: NoteId) -> Option<RichNote> {
let info = self.notes.get(&id)?;
let parents = self
@ -45,7 +86,7 @@ impl Store {
.map(|ps| ps.keys().copied().collect::<HashSet<_>>())
.unwrap_or_default();
Some(Note {
Some(RichNote {
id,
text: info.text.clone(),
children: info.children.clone(),
@ -54,7 +95,7 @@ impl Store {
}
fn tick(&mut self) {
self.curr_id += 1;
self.id += 1;
}
fn make_consistent_and_tick(&mut self) {
@ -82,18 +123,18 @@ impl Store {
pub fn create(&mut self, text: String) -> NoteId {
let id = NoteId::new();
let info = NoteInfo {
let note = RawNote {
text,
children: vec![],
};
self.notes.insert(id, info);
self.notes.insert(id, note);
self.make_consistent_and_tick();
id
}
pub fn delete(&mut self, id: NoteId) -> Option<NoteInfo> {
pub fn delete(&mut self, id: NoteId) -> Option<RawNote> {
let info = self.notes.remove(&id)?;
self.make_consistent_and_tick();
Some(info)

View file

@ -6,11 +6,6 @@ takes a short time to implement and represents an incremental improvement.
Once an idea has been implemented, it should be deleted from this file.
- cli: `repo list` command
- cli: `repo add` command
- cli: `repo rename` command
- cli: `repo remove` command
- cli: Single-letter aliases for commands
- Logging (`log` or `tracing`?)
- app: Make log available in UI, for debugging and testing
- app: Create repo from dropdown

1654
pnpm-lock.yaml generated

File diff suppressed because it is too large Load diff