Compare commits

..

5 commits
ci ... master

16 changed files with 1267 additions and 1271 deletions

View file

@ -18,11 +18,6 @@ jobs:
- name: Check out repo - name: Check out repo
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Update packages
run: |
sudo apt update
sudo apt upgrade
- name: Set up pnpm - name: Set up pnpm
uses: pnpm/action-setup@v4 uses: pnpm/action-setup@v4
with: with:
@ -34,7 +29,9 @@ jobs:
# https://github.com/tauri-apps/tauri-action?tab=readme-ov-file#usage # https://github.com/tauri-apps/tauri-action?tab=readme-ov-file#usage
# https://docs.github.com/en/actions/using-github-hosted-runners/using-github-hosted-runners/customizing-github-hosted-runners#installing-software-on-ubuntu-runners # https://docs.github.com/en/actions/using-github-hosted-runners/using-github-hosted-runners/customizing-github-hosted-runners#installing-software-on-ubuntu-runners
- name: Install tauri dependencies - name: Install tauri dependencies
run: sudo apt install libwebkit2gtk-4.1-dev run: |
sudo apt update
sudo apt install libwebkit2gtk-4.1-dev
- name: Build - name: Build
run: ./meta/build run: ./meta/build
@ -72,58 +69,12 @@ jobs:
target/release/gdn-cli.exe target/release/gdn-cli.exe
target/release/gdn-app.exe target/release/gdn-app.exe
build-android:
runs-on: ubuntu-latest
steps:
- name: Check out repo
uses: actions/checkout@v4
- name: Set up env vars
run: echo "NDK_HOME=$ANDROID_NDK_HOME" >> $GITHUB_ENV
- name: Update packages
run: |
sudo apt update
sudo apt upgrade
- name: Set up pnpm
uses: pnpm/action-setup@v4
with:
version: 10
# https://v2.tauri.app/start/prerequisites/#android
- name: Set up rust
run: |
rustup update
rustup target add aarch64-linux-android armv7-linux-androideabi i686-linux-android x86_64-linux-android
# https://v2.tauri.app/distribute/sign/android/
- name: Set up Android signing
run: |
cd gdn-app/src-tauri/gen/android
echo "keyAlias=${{ secrets.ANDROID_KEY_ALIAS }}" > key.properties
echo "password=${{ secrets.ANDROID_KEY_PASSWORD }}" >> key.properties
base64 -d <<< "${{ secrets.ANDROID_KEY_BASE64 }}" > $RUNNER_TEMP/keystore.jks
echo "storeFile=$RUNNER_TEMP/keystore.jks" >> key.properties
- name: Build
run: |
export NDK_HOME="$ANDROID_NDK_HOME"
./meta/build-android
- name: Upload
uses: actions/upload-artifact@v4
with:
name: gdn-android
path: target/release/gdn-app.apk
release: release:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: ${{ startsWith(github.ref, 'refs/tags/v') }} if: ${{ startsWith(github.ref, 'refs/tags/v') }}
needs: needs:
- build-linux - build-linux
- build-windows - build-windows
- build-android
permissions: permissions:
contents: write contents: write
steps: steps:
@ -134,7 +85,6 @@ jobs:
run: | run: |
zip -r gdn-linux.zip gdn-linux zip -r gdn-linux.zip gdn-linux
zip -r gdn-windows.zip gdn-windows zip -r gdn-windows.zip gdn-windows
mv gdn-android/gdn-app.apk gdn-android.apk
- name: Create new release - name: Create new release
uses: softprops/action-gh-release@v2 uses: softprops/action-gh-release@v2
@ -143,4 +93,3 @@ jobs:
files: | files: |
gdn-linux.zip gdn-linux.zip
gdn-windows.zip gdn-windows.zip
gdn-android.apk

574
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

@ -4,18 +4,22 @@ use gdn::ids::NoteId;
use tauri::{AppHandle, Emitter, State}; use tauri::{AppHandle, Emitter, State};
use crate::{ use crate::{
store::Store, state::AppState,
types::{EventNotesStoreUpdate, Note}, types::{EventNotesStoreUpdate, Note},
}; };
// API methods are sorted alphabetically. // API methods are sorted alphabetically.
fn update_if_required(store: &mut Store, app: &AppHandle) { fn update_if_required(state: &mut AppState, app: &AppHandle) {
if let Some(store_id) = store.needs_update() { let store_id = state.store.id();
let payload = EventNotesStoreUpdate { store_id }; if state.store_last_id == Some(store_id) {
app.emit("notes_store_update", payload).unwrap(); // No update necessary if the id hasn't changed
store.update(); return;
} }
let payload = EventNotesStoreUpdate { store_id };
app.emit("notes_store_update", payload).unwrap();
state.store_last_id = Some(store_id)
} }
#[tauri::command] #[tauri::command]
@ -24,10 +28,12 @@ pub fn note_child_add(
child_id: NoteId, child_id: NoteId,
child_position: isize, child_position: isize,
app: AppHandle, app: AppHandle,
store: State<'_, Arc<Mutex<Store>>>, state: State<'_, Arc<Mutex<AppState>>>,
) { ) {
let mut guard = store.lock().unwrap(); let mut guard = state.lock().unwrap();
guard.add_child_at_position(id, child_id, child_position); guard
.store
.add_child_at_position(id, child_id, child_position);
update_if_required(&mut guard, &app); update_if_required(&mut guard, &app);
} }
@ -39,10 +45,12 @@ pub fn note_child_move(
to_id: NoteId, to_id: NoteId,
to_position: isize, to_position: isize,
app: AppHandle, app: AppHandle,
store: State<'_, Arc<Mutex<Store>>>, state: State<'_, Arc<Mutex<AppState>>>,
) { ) {
let mut guard = store.lock().unwrap(); let mut guard = state.lock().unwrap();
guard.move_child_by_id_to_position(child_id, from_id, from_iteration, to_id, to_position); guard
.store
.move_child_by_id_to_position(child_id, from_id, from_iteration, to_id, to_position);
update_if_required(&mut guard, &app); update_if_required(&mut guard, &app);
} }
@ -52,10 +60,12 @@ pub fn note_child_remove(
child_id: NoteId, child_id: NoteId,
child_iteration: usize, child_iteration: usize,
app: AppHandle, app: AppHandle,
store: State<'_, Arc<Mutex<Store>>>, state: State<'_, Arc<Mutex<AppState>>>,
) { ) {
let mut guard = store.lock().unwrap(); let mut guard = state.lock().unwrap();
guard.remove_child_by_id(id, child_id, child_iteration); guard
.store
.remove_child_by_id(id, child_id, child_iteration);
update_if_required(&mut guard, &app); update_if_required(&mut guard, &app);
} }
@ -64,32 +74,33 @@ pub fn note_children_set(
id: NoteId, id: NoteId,
children: Vec<NoteId>, children: Vec<NoteId>,
app: AppHandle, app: AppHandle,
store: State<'_, Arc<Mutex<Store>>>, state: State<'_, Arc<Mutex<AppState>>>,
) { ) {
let mut guard = store.lock().unwrap(); let mut guard = state.lock().unwrap();
guard.set_children(id, children); guard.store.set_children(id, children);
update_if_required(&mut guard, &app); update_if_required(&mut guard, &app);
} }
#[tauri::command] #[tauri::command]
pub fn note_create(text: String, app: AppHandle, store: State<'_, Arc<Mutex<Store>>>) -> Note { pub fn note_create(text: String, app: AppHandle, state: State<'_, Arc<Mutex<AppState>>>) -> Note {
let mut guard = store.lock().unwrap(); let mut guard = state.lock().unwrap();
let id = guard.create(text); let id = guard.store.create(text);
let note = guard.store.get(id).unwrap().into();
update_if_required(&mut guard, &app); update_if_required(&mut guard, &app);
guard.get(id).unwrap() note
} }
#[tauri::command] #[tauri::command]
pub fn note_delete(id: NoteId, app: AppHandle, store: State<'_, Arc<Mutex<Store>>>) { pub fn note_delete(id: NoteId, app: AppHandle, state: State<'_, Arc<Mutex<AppState>>>) {
let mut guard = store.lock().unwrap(); let mut guard = state.lock().unwrap();
guard.delete(id); guard.store.delete(id);
update_if_required(&mut guard, &app); update_if_required(&mut guard, &app);
} }
#[tauri::command] #[tauri::command]
pub fn note_get(id: NoteId, store: State<'_, Arc<Mutex<Store>>>) -> Option<Note> { pub fn note_get(id: NoteId, state: State<'_, Arc<Mutex<AppState>>>) -> Option<Note> {
let guard = store.lock().unwrap(); let guard = state.lock().unwrap();
guard.get(id) guard.store.get(id).map(|it| it.into())
} }
#[tauri::command] #[tauri::command]
@ -97,16 +108,16 @@ pub fn note_text_set(
id: NoteId, id: NoteId,
text: String, text: String,
app: AppHandle, app: AppHandle,
store: State<'_, Arc<Mutex<Store>>>, state: State<'_, Arc<Mutex<AppState>>>,
) { ) {
let mut guard = store.lock().unwrap(); let mut guard = state.lock().unwrap();
guard.set_text(id, text); guard.store.set_text(id, text);
update_if_required(&mut guard, &app); update_if_required(&mut guard, &app);
} }
#[tauri::command] #[tauri::command]
pub fn notes_clear(app: AppHandle, store: State<'_, Arc<Mutex<Store>>>) { pub fn notes_clear(app: AppHandle, state: State<'_, Arc<Mutex<AppState>>>) {
let mut guard = store.lock().unwrap(); let mut guard = state.lock().unwrap();
guard.clear(); guard.store.clear();
update_if_required(&mut guard, &app); 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 std::sync::{Arc, Mutex};
use store::Store; use crate::state::AppState;
mod api; mod api;
pub mod store; mod state;
mod types; mod types;
#[cfg_attr(mobile, tauri::mobile_entry_point)] #[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() { pub fn run() {
let state = Arc::new(Mutex::new(AppState::new()));
// TODO Launch store loading task
tauri::Builder::default() tauri::Builder::default()
.plugin(tauri_plugin_opener::init()) .plugin(tauri_plugin_opener::init())
.manage(Arc::new(Mutex::new(Store::new()))) .manage(state)
.invoke_handler(tauri::generate_handler![ .invoke_handler(tauri::generate_handler![
api::note_child_add, api::note_child_add,
api::note_child_move, 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 std::collections::HashSet;
use gdn::ids::NoteId; use gdn::{ids::NoteId, store::RichNote};
use serde::Serialize; use serde::Serialize;
#[derive(Serialize)] #[derive(Serialize)]
@ -12,6 +12,17 @@ pub struct Note {
pub parents: HashSet<NoteId>, 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 // // Events //
//////////// ////////////

View file

@ -1,6 +1,6 @@
<template> <template>
<div <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> <slot></slot>
</div> </div>

View file

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

View file

@ -1,6 +1,7 @@
pub mod data; pub mod data;
pub mod ids; pub mod ids;
pub mod repo; pub mod repo;
pub mod store;
pub const PROPER_NAME: &str = "GedächtNAS"; pub const PROPER_NAME: &str = "GedächtNAS";
pub const TECHNICAL_NAME: &str = "gedaechtnas"; 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(); let message = Zoned::now().to_string();
// TODO Check that the repo is actually based on this commit. // 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)? { let parent = match read_head(&repository)? {
None => None, None => None,
Some(parent) => Some(parent.peel_to_commit()?), Some(parent) => Some(parent.peel_to_commit()?),

View file

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

1654
pnpm-lock.yaml generated

File diff suppressed because it is too large Load diff

View file

@ -1,3 +0,0 @@
[toolchain]
# https://github.com/actions/runner-images/blob/main/images/ubuntu/Ubuntu2404-Readme.md#rust-tools
channel = "1.86.0"