Compare commits
5 commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7cdc03397b | |||
| 47356d359d | |||
| 46f5bc38b8 | |||
| 0e7e54bf71 | |||
| 3c42339291 |
16 changed files with 1267 additions and 1271 deletions
57
.github/workflows/build.yml
vendored
57
.github/workflows/build.yml
vendored
|
|
@ -18,11 +18,6 @@ jobs:
|
|||
- name: Check out repo
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Update packages
|
||||
run: |
|
||||
sudo apt update
|
||||
sudo apt upgrade
|
||||
|
||||
- name: Set up pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
|
|
@ -34,7 +29,9 @@ jobs:
|
|||
# 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
|
||||
- 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
|
||||
run: ./meta/build
|
||||
|
|
@ -72,58 +69,12 @@ jobs:
|
|||
target/release/gdn-cli.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:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
|
||||
needs:
|
||||
- build-linux
|
||||
- build-windows
|
||||
- build-android
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
|
|
@ -134,7 +85,6 @@ jobs:
|
|||
run: |
|
||||
zip -r gdn-linux.zip gdn-linux
|
||||
zip -r gdn-windows.zip gdn-windows
|
||||
mv gdn-android/gdn-app.apk gdn-android.apk
|
||||
|
||||
- name: Create new release
|
||||
uses: softprops/action-gh-release@v2
|
||||
|
|
@ -143,4 +93,3 @@ jobs:
|
|||
files: |
|
||||
gdn-linux.zip
|
||||
gdn-windows.zip
|
||||
gdn-android.apk
|
||||
|
|
|
|||
574
Cargo.lock
generated
574
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
15
gdn-app/src-tauri/src/state.rs
Normal file
15
gdn-app/src-tauri/src/state.rs
Normal 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 //
|
||||
////////////
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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()?),
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
5
ideas.md
5
ideas.md
|
|
@ -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
1654
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
|
|
@ -1,3 +0,0 @@
|
|||
[toolchain]
|
||||
# https://github.com/actions/runner-images/blob/main/images/ubuntu/Ubuntu2404-Readme.md#rust-tools
|
||||
channel = "1.86.0"
|
||||
Loading…
Add table
Add a link
Reference in a new issue