From: Olaf Wintermann Date: Sun, 31 May 2026 11:54:35 +0000 (+0200) Subject: implement new_note X-Git-Url: https://uap-core.de/gitweb/?a=commitdiff_plain;h=a3d05bac13345696122f8d756a52c69f1cf4a3e5;p=note.git implement new_note --- diff --git a/application/src/backend.rs b/application/src/backend.rs index 809228b..52f5479 100644 --- a/application/src/backend.rs +++ b/application/src/backend.rs @@ -49,10 +49,10 @@ pub enum BroadcastMessage { NotesUpdate(NotesUpdate), } -#[derive(Clone)] +#[derive(Clone, PartialEq, Eq)] pub enum NoteId { Id(i32), - TmpId(String), + TmpId(u64), } impl Default for NoteId { @@ -362,10 +362,11 @@ impl BackendHandle { match result { Ok(note) => { let note_id = note.note_id; - if let Some(content) = content { + if let Some(mut content) = content { let result2 = if content.id.is_set() { content.update(&backend.db).await } else { + content.note_id = Set(note_id); content.insert(&backend.db).await }; diff --git a/application/src/main.rs b/application/src/main.rs index 6612cdc..563e6b3 100644 --- a/application/src/main.rs +++ b/application/src/main.rs @@ -36,7 +36,7 @@ use std::env; use entity::collection::{create_notebook_hierarchy, Node}; use ui_rs::{ui}; use ui_rs::ui::*; -use crate::backend::{Backend, BackendHandle, BroadcastMessage}; +use crate::backend::{Backend, BackendHandle}; use crate::window::*; fn main() { diff --git a/application/src/note.rs b/application/src/note.rs index dd84d89..87e4430 100644 --- a/application/src/note.rs +++ b/application/src/note.rs @@ -1,4 +1,5 @@ use std::rc::Rc; +use std::sync::atomic::{AtomicU64, Ordering}; use sea_orm::prelude::DateTimeWithTimeZone; use sea_orm::sea_query::prelude::Utc; use sea_orm::{NotSet, Set}; @@ -8,12 +9,15 @@ use ui_rs::ui::*; use crate::backend::{BackendHandle, BroadcastMessage, NoteId, NoteUpdate, NotesUpdate}; use crate::window::NoteTypeTabView; +static TMP_ID: AtomicU64 = AtomicU64::new(1); + #[derive(UiModel)] pub struct Note { pub backend: Rc, + pub id: NoteId, + pub collection_id: i32, - pub note_id: i32, pub content_id: i32, pub kind: NoteType, @@ -32,12 +36,12 @@ pub struct Note { #[ui_actions] impl Note { - pub fn new(collection_id: i32, backend: Rc) -> Self { + pub fn new(id: NoteId, collection_id: i32, backend: Rc) -> Self { Note { backend: backend, collection_id: collection_id, - note_id: 0, + id: id, content_id: 0, kind: NoteType::PlainTextNote, created: Default::default(), @@ -51,7 +55,7 @@ impl Note { } pub fn init_from_model(&mut self, model: &entity::note::Model) { - self.note_id = model.note_id; + self.id = NoteId::Id(model.note_id); let tab = match model.kind { NoteType::PlainTextNote => NoteTypeTabView::TextArea, @@ -59,6 +63,10 @@ impl Note { }; self.note_type.set(tab as i64); } + + pub fn init_new(&mut self) { + self.note_type.set(NoteTypeTabView::TextArea as i64); + } pub fn init_content(&mut self, content: &entity::notecontent::Model) { self.content_id = content.id; @@ -67,15 +75,19 @@ impl Note { } pub fn load_content(&mut self, doc: &UiDoc) { + let NoteId::Id(note_id) = self.id else { + return; + }; + let proxy = doc.doc_proxy(); - self.backend.get_note_content(self.note_id, move|result|{ - proxy.call_mainthread(|doc, note|{ + self.backend.get_note_content(note_id, move|result|{ + proxy.call_mainthread(move |_doc, note|{ match result { Ok(content) => { if let Some(content) = content { note.init_content(&content); } else { - println!("note {}: no content", note.note_id); + println!("note {}: no content", note_id); } }, Err(e) => { @@ -95,7 +107,7 @@ impl Note { if notify { let update = NoteUpdate { - note_id: NoteId::Id(self.note_id), + note_id: self.id.clone(), title: Some(title.to_string()), ..Default::default() }; @@ -117,7 +129,10 @@ impl Note { None => "Note".to_string() }; - let note_id = if self.note_id == 0 { NotSet } else { Set(self.note_id) }; + let (note_id, created) = match &self.id { + NoteId::Id(id) => (Set(*id), NotSet), + NoteId::TmpId(t) => (NotSet, Set(Utc::now().into())) + }; let note = entity::note::ActiveModel { note_id: note_id.clone(), @@ -125,7 +140,7 @@ impl Note { kind: Set(self.kind.clone()), title: Set(title), lastmodified: Set(Utc::now().into()), - created: if self.note_id == 0 { Set(Utc::now().into()) } else { NotSet } + created }; let notecontent = entity::notecontent::ActiveModel { @@ -136,7 +151,8 @@ impl Note { backend.save_note(note, Some(notecontent), |result|{ match result { - Ok(_model) => { + Ok((note_id, content_id)) => { + // TODO: how to save the note_id, content_id in self? println!("Note saved"); }, Err(error) => { @@ -191,4 +207,8 @@ fn generate_title(s: &str) -> Option<(&str, usize)> { } None +} + +pub fn new_note_id() -> NoteId { + NoteId::TmpId(TMP_ID.fetch_add(1, Ordering::Relaxed)) } \ No newline at end of file diff --git a/application/src/notebook.rs b/application/src/notebook.rs index a96138d..d8e0320 100644 --- a/application/src/notebook.rs +++ b/application/src/notebook.rs @@ -3,7 +3,8 @@ use ui_rs::{action, ui_actions, UiModel}; use ui_rs::ui::*; use entity::note::{Model as Note}; -use crate::backend::{BackendHandle, BroadcastMessage, NoteId}; +use crate::backend::{BackendHandle, BroadcastMessage, NoteId, NoteUpdate}; +use crate::note::new_note_id; /// UI model for a notebook #[derive(UiModel)] @@ -14,6 +15,7 @@ pub struct Notebook { pub collection_id: i32, pub selected_note: Option>, + pub selected_index: Option, #[bind("notes")] pub notes: UiList @@ -28,6 +30,7 @@ impl Notebook { broadcast_rx: backend.btx.subscribe(), collection_id: id, selected_note: None, + selected_index: None, notes: Default::default() } } @@ -37,7 +40,7 @@ impl Notebook { notes_list.clear(); for note in notes { - notes_list.push(NoteItem::new(note)); + notes_list.push(NoteItem::from_model(note)); } self.notes.update(); @@ -66,18 +69,25 @@ impl Notebook { #[action] pub fn note_selected(&mut self, event: &ActionEvent) { if let EventType::ListSelection(s) = event.event_type { - self.detach_current_note(); - self.selected_note = None; + self.selected_index = s.selected_index(); + if self.selected_index.is_some() { + self.detach_current_note(); + self.selected_note = None; + } if let Some(note) = s.selected_element_mut(self.notes.data_mut()) { let doc = if let Some(doc) = ¬e.model { doc.clone() } else { // Create the new note - let note_data = crate::note::Note::new(self.collection_id, self.backend.clone()); + let note_data = crate::note::Note::new(note.id.clone(), self.collection_id, self.backend.clone()); // Create the note document object UiDoc::new2(note_data, |n,d| { - n.init_from_model(¬e.data); - n.load_content(d); + if note.is_new() { + n.init_new(); + } else { + n.init_from_model(¬e.data); + n.load_content(d); + } }) }; note.model = Some(doc.clone()); @@ -90,29 +100,68 @@ impl Notebook { } } + #[action] + pub fn new_note(&mut self, _event: &ActionEvent) { + println!("new note"); + + let item = NoteItem::new(new_note_id()); + self.notes.data_mut().insert(0, item); + self.notes.update(); + self.notes.select_with_event(0, true); + } + + pub fn update_note(&mut self, update: &NoteUpdate) { + // Find the note in the list (or add it if it doesn't exist yet) + // Start with the currently selected note + let mut update_row: Option = None; + if let Some(index) = self.selected_index { + if let Some(item) = self.notes.data_mut().get_mut(index) { + if item.id == update.note_id { + item.update(&update); + update_row = Some(index); + } + } + } + + if update_row.is_none() { + for (index, item) in self.notes.data_mut().iter_mut().enumerate() { + if item.id == update.note_id { + item.update(&update); + update_row = Some(index); + break; + } + } + } + + if let Some(index) = update_row { + self.notes.update_row(index); + } + } + #[action] pub fn message(&mut self, _event: &ActionEvent) { while let Ok(msg) = self.broadcast_rx.try_recv() { match msg { BroadcastMessage::NotesUpdate(update) => { - let mut update_rows: Vec = Vec::new(); - for u in &update.note_updates { - if let NoteId::Id(id) = u.note_id { - let note_data = self.notes.data_mut(); - for (i, note) in note_data.iter_mut().enumerate() { - if note.data.note_id == id { - // update note - if let Some(title) = &u.title { - note.data.title = title.clone(); + if update.note_updates.len() == 1 { + self.update_note(&update.note_updates[0]); + } else if update.note_updates.len() > 1 { + let mut update_rows: Vec = Vec::new(); + for u in &update.note_updates { + if let NoteId::Id(id) = u.note_id { + let note_data = self.notes.data_mut(); + for (i, note) in note_data.iter_mut().enumerate() { + if note.data.note_id == id { + note.update(u); + update_rows.push(i); } - update_rows.push(i); } } } - } - for i in update_rows { - self.notes.update_row(i); + for i in update_rows { + self.notes.update_row(i); + } } }, _ => { @@ -145,11 +194,32 @@ pub struct NoteItem { } impl NoteItem { - pub fn new(data: entity::note::Model) -> Self { + pub fn from_model(data: entity::note::Model) -> Self { NoteItem { id: NoteId::Id(data.note_id), data: data, model: None } } + + pub fn new(id: NoteId) -> Self { + let mut item = NoteItem { + id: id, + data: entity::note::Model::new(), + model: None + }; + item.data.title = "New Note".to_owned(); + item + } + + pub fn is_new(&self) -> bool { + return matches!(self.id, NoteId::TmpId(_)) + } + + pub fn update(&mut self, update: &NoteUpdate) { + // update note + if let Some(title) = &update.title { + self.data.title = title.clone(); + } + } } \ No newline at end of file diff --git a/application/src/window.rs b/application/src/window.rs index fa0c9d3..855527c 100644 --- a/application/src/window.rs +++ b/application/src/window.rs @@ -30,7 +30,6 @@ use ui_rs::{action, ui_actions, UiModel}; use ui_rs::ui::*; use crate::App; use entity::collection::{Model as Collection, Node}; -use entity::note::Model as Note; use crate::backend::{BackendHandle, BroadcastMessage}; use crate::newnotebook::new_notebook_dialog; use crate::notebook::{notelist_getvalue, NoteItem, Notebook}; @@ -67,12 +66,65 @@ impl MainWindow { #[action] pub fn new_notebook(&mut self, event: &ActionEvent) { - new_notebook_dialog(event.obj, &self.groups, self.backend.clone()); + if let Some(obj) = event.obj { + new_notebook_dialog(obj, &self.groups, self.backend.clone()); + } } #[action] pub fn new_note(&mut self, _event: &ActionEvent) { + // The new_note action is handled by Notebook and MainWindow + // When a notebook is attached, the notebook receives this action, but in case + // no notebook is selected, the action is handled in the main window + + if self.selected_notebook.is_some() { + println!("Error: MainWindow received new_note action but selected_notebook is not None"); + return; + } + + // Select the first notebook + // TODO: maybe select a configured default notebook + if let Some(group) = self.notebooks.get(0) { + if let Some(notebook) = group.data().get(0) { + self.select_notebook(notebook.collection_id); + // self.selected_notebook has a value after select_notebook() + if let Some(doc) = &self.selected_notebook { + // Now that a notebook is selected, we can open the new note UI there + doc.ctx.call_action("new_note"); + } + } + } + } + + pub fn select_notebook(&mut self, collection_id: i32) { + let Some(mut obj) = self.obj.get_object() else { + return; + }; + + if let Some(current) = &self.selected_notebook { + obj.ctx.detach(current); + } + + let notebook = Notebook::new(collection_id, &self.backend); + let doc = UiDoc::new2(notebook, |notebook, doc| { + notebook.doc_ref = doc.doc_ref(); + }); + let proxy = doc.doc_proxy(); + self.backend.get_notes(collection_id, |result|{ + proxy.call_mainthread(|_, nb| { + match result { + Ok(notes) => { + nb.set_notes(notes); + }, + Err(err) => { + eprintln!("get_notes failed: {}", err); + } + } + }); + }); + obj.ctx.attach(&doc); + self.selected_notebook = Some(doc); } #[action] @@ -133,30 +185,7 @@ pub fn create_window(app: &App, ctx: &AppContext) -> UiObject { - nb.set_notes(notes); - }, - Err(err) => { - eprintln!("get_notes failed: {}", err); - } - } - }); - }); - - e.obj.ctx.attach(&doc); - e.data.selected_notebook = Some(doc); + e.data.select_notebook(nb.collection_id); } } }); @@ -202,7 +231,7 @@ pub fn create_window(app: &App, ctx: &AppContext) -> UiObject Self { + Model { + note_id: 0, + collection_id: 0, + kind: Default::default(), + title: Default::default(), + lastmodified: Default::default(), + created: Default::default() + } + } +} diff --git a/ui-rs/src/ui/event.rs b/ui-rs/src/ui/event.rs index cbdcd72..884baf4 100644 --- a/ui-rs/src/ui/event.rs +++ b/ui-rs/src/ui/event.rs @@ -43,7 +43,7 @@ pub struct Event<'a, T> { } pub struct ActionEvent<'a> { - pub obj: &'a UiObject, + pub obj: Option<&'a UiObject>, pub event_type: EventType<'a>, pub intval: i32, pub set: bool @@ -268,10 +268,14 @@ pub extern "C" fn action_event_wrapper(e: *const ffi::UiEvent, data: *mut c_v let event_type = get_event_type(&mut event_data); let obj_ptr = ui_event_get_obj(e); - let obj: UiObject = UiObject::from_ptr(obj_ptr); + let obj = if obj_ptr.is_null() { + None + } else { + Some(&UiObject::from_ptr(obj_ptr)) + }; let event = ActionEvent { - obj: &obj, + obj: obj, event_type: event_type, intval: ev_int, set: ev_set