NotesUpdate(NotesUpdate),
}
-#[derive(Clone)]
+#[derive(Clone, PartialEq, Eq)]
pub enum NoteId {
Id(i32),
- TmpId(String),
+ TmpId(u64),
}
impl Default for NoteId {
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
};
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() {
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};
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<BackendHandle>,
+ pub id: NoteId,
+
pub collection_id: i32,
- pub note_id: i32,
pub content_id: i32,
pub kind: NoteType,
#[ui_actions]
impl Note {
- pub fn new(collection_id: i32, backend: Rc<BackendHandle>) -> Self {
+ pub fn new(id: NoteId, collection_id: i32, backend: Rc<BackendHandle>) -> Self {
Note {
backend: backend,
collection_id: collection_id,
- note_id: 0,
+ id: id,
content_id: 0,
kind: NoteType::PlainTextNote,
created: Default::default(),
}
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,
};
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;
}
pub fn load_content(&mut self, doc: &UiDoc<Note>) {
+ 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) => {
if notify {
let update = NoteUpdate {
- note_id: NoteId::Id(self.note_id),
+ note_id: self.id.clone(),
title: Some(title.to_string()),
..Default::default()
};
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(),
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 {
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) => {
}
None
+}
+
+pub fn new_note_id() -> NoteId {
+ NoteId::TmpId(TMP_ID.fetch_add(1, Ordering::Relaxed))
}
\ No newline at end of file
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)]
pub collection_id: i32,
pub selected_note: Option<UiDoc<crate::note::Note>>,
+ pub selected_index: Option<usize>,
#[bind("notes")]
pub notes: UiList<NoteItem>
broadcast_rx: backend.btx.subscribe(),
collection_id: id,
selected_note: None,
+ selected_index: None,
notes: Default::default()
}
}
notes_list.clear();
for note in notes {
- notes_list.push(NoteItem::new(note));
+ notes_list.push(NoteItem::from_model(note));
}
self.notes.update();
#[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());
}
}
+ #[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<usize> = 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<usize> = 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<usize> = 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);
+ }
}
},
_ => {
}
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
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};
#[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]
b.onactivate(|e|{
if let EventType::SubList(sl) = &e.event_type {
if let Some(nb) = sl.get_from(&e.data.notebooks) {
- if let Some(current) = &e.data.selected_notebook {
- e.obj.ctx.detach(current);
- }
-
- let notebook = Notebook::new(nb.collection_id, &e.data.backend);
- let doc = UiDoc::new2(notebook, |notebook, doc| {
- notebook.doc_ref = doc.doc_ref();
- });
- let proxy = doc.doc_proxy();
- e.data.backend.get_notes(nb.collection_id, |result|{
- proxy.call_mainthread(|_, nb| {
- match result {
- Ok(notes) => {
- 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);
}
}
});
window.show();
window.onclose(|event| {
if let Some(doc) = &event.data.selected_notebook {
- doc.doc_proxy().call_mainthread(|doc, notebook| {
+ doc.doc_proxy().call_mainthread(|_doc, notebook| {
notebook.detach_current_note();
});
}
}
impl ActiveModelBehavior for ActiveModel {}
+
+impl Model {
+ pub fn new() -> Self {
+ Model {
+ note_id: 0,
+ collection_id: 0,
+ kind: Default::default(),
+ title: Default::default(),
+ lastmodified: Default::default(),
+ created: Default::default()
+ }
+ }
+}
}
pub struct ActionEvent<'a> {
- pub obj: &'a UiObject<NoAppData>,
+ pub obj: Option<&'a UiObject<NoAppData>>,
pub event_type: EventType<'a>,
pub intval: i32,
pub set: bool
let event_type = get_event_type(&mut event_data);
let obj_ptr = ui_event_get_obj(e);
- let obj: UiObject<NoAppData> = 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