very badly coded gtk4 ui.
This commit is contained in:
parent
0bfe9f1ef2
commit
024b259bbe
12 changed files with 1570 additions and 10 deletions
973
Cargo.lock
generated
973
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
12
crates/noteriety-gtk4/Cargo.toml
Normal file
12
crates/noteriety-gtk4/Cargo.toml
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
[package]
|
||||||
|
name = "noteriety-gtk4"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
relm4 = { version = "0.9.0", features = ["adw", "libadwaita"] }
|
||||||
|
libadwaita = { version = "0.7.0", features = ["gtk_v4_6", "v1_5"] }
|
||||||
|
markdown-ast = "0.1.1"
|
||||||
|
crop = "0.4.2"
|
||||||
|
noteriety = { version = "0.1.0", path = "../noteriety" }
|
||||||
|
cap-std = "3.2.0"
|
11
crates/noteriety-gtk4/src/configuration.rs
Normal file
11
crates/noteriety-gtk4/src/configuration.rs
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
pub(crate) struct Configuration {
|
||||||
|
pub notes_dir: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Configuration {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
notes_dir: String::from("/home/arcayr/.local/share/noteriety/notebooks"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
11
crates/noteriety-gtk4/src/main.rs
Normal file
11
crates/noteriety-gtk4/src/main.rs
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
use configuration::Configuration;
|
||||||
|
use relm4::prelude::*;
|
||||||
|
|
||||||
|
mod window;
|
||||||
|
mod configuration;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let config = Configuration::default();
|
||||||
|
let app = RelmApp::new("expert.mischief.noteriety");
|
||||||
|
app.run::<window::Window>(config);
|
||||||
|
}
|
30
crates/noteriety-gtk4/src/note.rs
Normal file
30
crates/noteriety-gtk4/src/note.rs
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
pub struct NoteTitle(String);
|
||||||
|
|
||||||
|
impl Default for NoteTitle {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self(String::from("Untitled Note"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NoteTitle {
|
||||||
|
pub fn as_filename(&self) -> String {
|
||||||
|
filenamify::filenamify(&self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct NoteBody(crop::Rope);
|
||||||
|
|
||||||
|
pub struct Note {
|
||||||
|
title: NoteTitle,
|
||||||
|
body: NoteBody,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Notebook {
|
||||||
|
basedir: String,
|
||||||
|
notes: Vec<Note>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Outline {
|
||||||
|
basedir: String,
|
||||||
|
body: NoteBody,
|
||||||
|
}
|
86
crates/noteriety-gtk4/src/window/content_entry.rs
Normal file
86
crates/noteriety-gtk4/src/window/content_entry.rs
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
use gtk::{prelude::*, TextBuffer};
|
||||||
|
use markdown_ast::markdown_to_ast;
|
||||||
|
use relm4::prelude::*;
|
||||||
|
|
||||||
|
pub(crate) struct NoteContentEntry {
|
||||||
|
pub(crate) content: gtk::TextBuffer,
|
||||||
|
pub(crate) rope: crop::Rope,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) enum NoteContentEntryInput {
|
||||||
|
SetContent(String),
|
||||||
|
Clear,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) enum NoteContentEntryOutput {
|
||||||
|
Content(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[relm4::component(pub(crate))]
|
||||||
|
impl SimpleComponent for NoteContentEntry {
|
||||||
|
type Init = ();
|
||||||
|
type Input = NoteContentEntryInput;
|
||||||
|
type Output = NoteContentEntryOutput;
|
||||||
|
|
||||||
|
view! {
|
||||||
|
#[root]
|
||||||
|
gtk::ScrolledWindow {
|
||||||
|
set_margin_horizontal: 10,
|
||||||
|
set_margin_vertical: 10,
|
||||||
|
|
||||||
|
// libadwaita doesn't provide styling for gtk::TextView.
|
||||||
|
// reason unknown.
|
||||||
|
// https://gitlab.gnome.org/GNOME/libadwaita/-/issues/353
|
||||||
|
gtk::TextView {
|
||||||
|
inline_css: "padding: 10px; border-radius: 6px",
|
||||||
|
set_hexpand: true,
|
||||||
|
set_vexpand: true,
|
||||||
|
set_buffer: Some(&model.content),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init(
|
||||||
|
_init: Self::Init,
|
||||||
|
root: Self::Root,
|
||||||
|
sender: ComponentSender<Self>,
|
||||||
|
) -> ComponentParts<Self> {
|
||||||
|
let model = Self {
|
||||||
|
content: TextBuffer::default(),
|
||||||
|
rope: crop::Rope::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
model.content.connect_text_notify(move |tb| {
|
||||||
|
let (min, max) = tb.bounds();
|
||||||
|
// let ast = markdown_to_ast(.as_str());
|
||||||
|
sender.output(NoteContentEntryOutput::Content(tb.text(&min, &max, true).to_string()));
|
||||||
|
});
|
||||||
|
|
||||||
|
let widgets = view_output!();
|
||||||
|
|
||||||
|
ComponentParts { model, widgets }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, message: Self::Input, _sender: ComponentSender<Self>) {
|
||||||
|
match message {
|
||||||
|
Self::Input::Clear => {
|
||||||
|
self.content = TextBuffer::default();
|
||||||
|
},
|
||||||
|
|
||||||
|
Self::Input::SetContent(body) => {
|
||||||
|
self.content = TextBuffer::builder().text(body.to_string()).build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for NoteContentEntry {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
content: TextBuffer::default(),
|
||||||
|
rope: crop::Rope::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
54
crates/noteriety-gtk4/src/window/header_bar.rs
Normal file
54
crates/noteriety-gtk4/src/window/header_bar.rs
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
use gtk::prelude::{
|
||||||
|
ButtonExt, GtkWindowExt, ToggleButtonExt, WidgetExt,
|
||||||
|
};
|
||||||
|
use relm4::*;
|
||||||
|
|
||||||
|
pub(crate) struct HeaderBar;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) enum Output {
|
||||||
|
OutlineMode,
|
||||||
|
NotesMode,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[relm4::component(pub(crate))]
|
||||||
|
impl SimpleComponent for HeaderBar {
|
||||||
|
type Init = ();
|
||||||
|
type Input = ();
|
||||||
|
type Output = Output;
|
||||||
|
|
||||||
|
view! {
|
||||||
|
#[root]
|
||||||
|
>k::Box {
|
||||||
|
add_css_class: "linked",
|
||||||
|
|
||||||
|
#[name = "group"]
|
||||||
|
gtk::ToggleButton {
|
||||||
|
set_label: "Outline",
|
||||||
|
set_active: false,
|
||||||
|
connect_toggled[sender] => move |btn| {
|
||||||
|
if btn.is_active() {
|
||||||
|
sender.output(Self::Output::OutlineMode).unwrap()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
gtk::ToggleButton {
|
||||||
|
set_label: "Notes",
|
||||||
|
set_active: true,
|
||||||
|
set_group: Some(&group),
|
||||||
|
connect_toggled[sender] => move |btn| {
|
||||||
|
if btn.is_active() {
|
||||||
|
sender.output(Self::Output::NotesMode).unwrap()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init(_params: Self::Init, root: Self::Root, sender: ComponentSender<Self>) -> ComponentParts<Self> {
|
||||||
|
let model = HeaderBar;
|
||||||
|
let widgets = view_output!();
|
||||||
|
|
||||||
|
ComponentParts { model, widgets }
|
||||||
|
}
|
||||||
|
}
|
190
crates/noteriety-gtk4/src/window/mod.rs
Normal file
190
crates/noteriety-gtk4/src/window/mod.rs
Normal file
|
@ -0,0 +1,190 @@
|
||||||
|
use core::panic;
|
||||||
|
use std::{cell::RefCell, rc::Rc};
|
||||||
|
|
||||||
|
use libadwaita::prelude::{ButtonExt, NavigationPageExt, WidgetExt};
|
||||||
|
use libadwaita::{
|
||||||
|
self as adw,
|
||||||
|
prelude::{GtkWindowExt, OrientableExt},
|
||||||
|
};
|
||||||
|
use noteriety::{self, note::Note, notebook::Notebook};
|
||||||
|
use relm4::factory::Position;
|
||||||
|
use relm4::gtk;
|
||||||
|
use relm4::{
|
||||||
|
Component, ComponentController, ComponentParts, ComponentSender, Controller, RelmWidgetExt,
|
||||||
|
SimpleComponent,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::configuration::Configuration;
|
||||||
|
|
||||||
|
mod content_entry;
|
||||||
|
mod header_bar;
|
||||||
|
mod sidebar;
|
||||||
|
mod title_entry;
|
||||||
|
|
||||||
|
pub(crate) struct Window {
|
||||||
|
header_bar: Controller<header_bar::HeaderBar>,
|
||||||
|
sidebar: Controller<sidebar::Sidebar>,
|
||||||
|
title_entry: Controller<title_entry::NoteTitleEntry>,
|
||||||
|
content_entry: Controller<content_entry::NoteContentEntry>,
|
||||||
|
config: Configuration,
|
||||||
|
root_notebook: Notebook,
|
||||||
|
active_note: Note,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) enum WindowInput {
|
||||||
|
SetActiveNote(Note),
|
||||||
|
UpdateActiveNoteTitle(String),
|
||||||
|
UpdateActiveNoteContent(String),
|
||||||
|
CreateNote(Note),
|
||||||
|
CreateNotebook(Notebook),
|
||||||
|
CreateNewNote,
|
||||||
|
SaveActiveNote,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum WindowOutput {}
|
||||||
|
|
||||||
|
#[relm4::component(pub(crate))]
|
||||||
|
impl SimpleComponent for Window {
|
||||||
|
type Init = Configuration;
|
||||||
|
type Input = WindowInput;
|
||||||
|
type Output = ();
|
||||||
|
|
||||||
|
view! {
|
||||||
|
main_window = adw::ApplicationWindow {
|
||||||
|
set_default_width: 640,
|
||||||
|
set_default_height: 480,
|
||||||
|
|
||||||
|
adw::NavigationSplitView {
|
||||||
|
#[wrap(Some)]
|
||||||
|
set_sidebar = &adw::NavigationPage {
|
||||||
|
#[wrap(Some)]
|
||||||
|
set_child = &adw::ToolbarView {
|
||||||
|
#[wrap(Some)]
|
||||||
|
set_content = >k::Box {
|
||||||
|
set_orientation: gtk::Orientation::Vertical,
|
||||||
|
set_margin_all: 10,
|
||||||
|
model.header_bar.widget(),
|
||||||
|
|
||||||
|
model.sidebar.widget(),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
#[wrap(Some)]
|
||||||
|
set_content = &adw::NavigationPage {
|
||||||
|
#[wrap(Some)]
|
||||||
|
set_child = >k::Box {
|
||||||
|
set_orientation: gtk::Orientation::Vertical,
|
||||||
|
adw::HeaderBar {
|
||||||
|
#[wrap(Some)]
|
||||||
|
set_title_widget = &adw::WindowTitle {
|
||||||
|
#[watch]
|
||||||
|
set_title: &model.active_note.title.to_string()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
gtk::Box {
|
||||||
|
set_orientation: gtk::Orientation::Horizontal,
|
||||||
|
model.title_entry.widget(),
|
||||||
|
gtk::Box {
|
||||||
|
set_orientation: gtk::Orientation::Vertical,
|
||||||
|
gtk::Button {
|
||||||
|
set_label: "new note",
|
||||||
|
set_margin_bottom: 6,
|
||||||
|
set_halign: gtk::Align::Center,
|
||||||
|
set_valign: gtk::Align::Center,
|
||||||
|
connect_clicked => WindowInput::CreateNewNote
|
||||||
|
},
|
||||||
|
gtk::Button {
|
||||||
|
set_label: "save note",
|
||||||
|
set_halign: gtk::Align::Center,
|
||||||
|
set_valign: gtk::Align::Center,
|
||||||
|
connect_clicked => WindowInput::SaveActiveNote
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
model.content_entry.widget(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init(
|
||||||
|
config: Self::Init,
|
||||||
|
window: Self::Root,
|
||||||
|
sender: ComponentSender<Self>,
|
||||||
|
) -> ComponentParts<Self> {
|
||||||
|
let root_notebook = Notebook::read(&config.notes_dir).unwrap();
|
||||||
|
|
||||||
|
let title_entry =
|
||||||
|
title_entry::NoteTitleEntry::builder()
|
||||||
|
.launch(())
|
||||||
|
.forward(sender.input_sender(), |m| match m {
|
||||||
|
title_entry::NoteTitleEntryOutput::Content(v) => {
|
||||||
|
WindowInput::UpdateActiveNoteTitle(v)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let content_entry = content_entry::NoteContentEntry::builder()
|
||||||
|
.launch(())
|
||||||
|
.forward(sender.input_sender(), |m| match m {
|
||||||
|
content_entry::NoteContentEntryOutput::Content(content) => {
|
||||||
|
WindowInput::UpdateActiveNoteContent(content)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let sidebar = sidebar::Sidebar::builder().launch(()).detach();
|
||||||
|
|
||||||
|
let header_bar = header_bar::HeaderBar::builder().launch(()).detach();
|
||||||
|
|
||||||
|
let model = Self {
|
||||||
|
title_entry,
|
||||||
|
header_bar,
|
||||||
|
sidebar,
|
||||||
|
content_entry,
|
||||||
|
config,
|
||||||
|
root_notebook: root_notebook.clone(),
|
||||||
|
active_note: Note::new("untitled note"),
|
||||||
|
};
|
||||||
|
|
||||||
|
model.sidebar.emit(sidebar::Input::UpdateRoot(root_notebook.clone()));
|
||||||
|
|
||||||
|
let widgets = view_output!();
|
||||||
|
ComponentParts { model, widgets }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, input: Self::Input, sender: ComponentSender<Self>) {
|
||||||
|
match input {
|
||||||
|
WindowInput::UpdateActiveNoteTitle(title) => {
|
||||||
|
self.active_note.title = title;
|
||||||
|
},
|
||||||
|
|
||||||
|
WindowInput::UpdateActiveNoteContent(content) => {
|
||||||
|
self.active_note.content = content;
|
||||||
|
},
|
||||||
|
|
||||||
|
WindowInput::SetActiveNote(note) => {
|
||||||
|
self.active_note = note;
|
||||||
|
},
|
||||||
|
|
||||||
|
WindowInput::SaveActiveNote => {
|
||||||
|
self.active_note
|
||||||
|
.write_in(&self.root_notebook.root_path)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
self.root_notebook = Notebook::read(&self.root_notebook.root_path).unwrap();
|
||||||
|
self.sidebar.emit(sidebar::Input::UpdateRoot(self.root_notebook.clone()));
|
||||||
|
},
|
||||||
|
|
||||||
|
WindowInput::CreateNewNote => {
|
||||||
|
self.active_note = Note::new("");
|
||||||
|
},
|
||||||
|
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
124
crates/noteriety-gtk4/src/window/sidebar.rs
Normal file
124
crates/noteriety-gtk4/src/window/sidebar.rs
Normal file
|
@ -0,0 +1,124 @@
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use binding::U8Binding;
|
||||||
|
use gtk::{prelude::{
|
||||||
|
ButtonExt, GtkWindowExt, ListItemExt, WidgetExt,
|
||||||
|
}};
|
||||||
|
use noteriety::{note::Note, notebook::Notebook};
|
||||||
|
use relm4::*;
|
||||||
|
use typed_view::list::{RelmListItem, TypedListView};
|
||||||
|
|
||||||
|
pub struct SidebarItemWidgets {
|
||||||
|
label: gtk::Label,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
pub(crate) struct SidebarItem {
|
||||||
|
note: Note,
|
||||||
|
title: String,
|
||||||
|
binding: U8Binding,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SidebarItem {
|
||||||
|
fn new(note: Note) -> Self {
|
||||||
|
Self {
|
||||||
|
note: note.clone(),
|
||||||
|
title: note.title.clone(),
|
||||||
|
binding: U8Binding::new(0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RelmListItem for SidebarItem {
|
||||||
|
type Root = gtk::Box;
|
||||||
|
type Widgets = SidebarItemWidgets;
|
||||||
|
|
||||||
|
fn setup(_list_item: >k::ListItem) -> (Self::Root, Self::Widgets) {
|
||||||
|
view! {
|
||||||
|
list_item = gtk::Box {
|
||||||
|
inline_css: "padding: 10px; border-radius: 6px",
|
||||||
|
|
||||||
|
#[name = "label"]
|
||||||
|
gtk::Label,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let widgets = SidebarItemWidgets {
|
||||||
|
label,
|
||||||
|
};
|
||||||
|
|
||||||
|
(list_item, widgets)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bind(&mut self, widgets: &mut Self::Widgets, _root: &mut Self::Root) {
|
||||||
|
let SidebarItemWidgets {
|
||||||
|
label
|
||||||
|
} = widgets;
|
||||||
|
|
||||||
|
label.set_label(&self.note.title.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct Sidebar {
|
||||||
|
list_view: TypedListView<SidebarItem, gtk::SingleSelection>,
|
||||||
|
root: Option<Notebook>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) enum Output {
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) enum Input {
|
||||||
|
UpdateRoot(Notebook),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[relm4::component(pub(crate))]
|
||||||
|
impl SimpleComponent for Sidebar {
|
||||||
|
type Init = ();
|
||||||
|
type Input = Input;
|
||||||
|
type Output = Output;
|
||||||
|
|
||||||
|
view! {
|
||||||
|
#[root]
|
||||||
|
#[wrap(Some)]
|
||||||
|
>k::Box{
|
||||||
|
set_margin_top: 12,
|
||||||
|
gtk::ScrolledWindow {
|
||||||
|
set_vexpand: true,
|
||||||
|
set_hexpand: true,
|
||||||
|
|
||||||
|
#[local_ref]
|
||||||
|
sidebar_view -> gtk::ListView {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init(root_notebook: Self::Init, root: Self::Root, sender: ComponentSender<Self>) -> ComponentParts<Self> {
|
||||||
|
let list_view_wrapper: TypedListView<SidebarItem, gtk::SingleSelection> =
|
||||||
|
TypedListView::with_sorting();
|
||||||
|
|
||||||
|
let model = Self {
|
||||||
|
list_view: list_view_wrapper,
|
||||||
|
root: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let sidebar_view = &model.list_view.view;
|
||||||
|
let widgets = view_output!();
|
||||||
|
|
||||||
|
ComponentParts { model, widgets }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, message: Self::Input, sender: ComponentSender<Self>) {
|
||||||
|
match message {
|
||||||
|
Input::UpdateRoot(notebook) => {
|
||||||
|
self.list_view.clear();
|
||||||
|
|
||||||
|
notebook.child_notes.into_iter().for_each(|n| {
|
||||||
|
self.list_view.append(SidebarItem::new(n.clone()));
|
||||||
|
});
|
||||||
|
},
|
||||||
|
_ => todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
55
crates/noteriety-gtk4/src/window/title_entry.rs
Normal file
55
crates/noteriety-gtk4/src/window/title_entry.rs
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
use gtk::prelude::*;
|
||||||
|
use relm4::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub(crate) struct NoteTitleEntry {
|
||||||
|
pub(crate) content: gtk::EntryBuffer,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) enum NoteTitleEntryInput {
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) enum NoteTitleEntryOutput {
|
||||||
|
Content(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[relm4::component(pub(crate))]
|
||||||
|
impl SimpleComponent for NoteTitleEntry {
|
||||||
|
type Init = ();
|
||||||
|
type Input = NoteTitleEntryInput;
|
||||||
|
type Output = NoteTitleEntryOutput;
|
||||||
|
|
||||||
|
view! {
|
||||||
|
#[root]
|
||||||
|
gtk::Entry {
|
||||||
|
set_hexpand: true,
|
||||||
|
set_margin_all: 12,
|
||||||
|
set_buffer: &model.content,
|
||||||
|
set_placeholder_text: Some("note title"),
|
||||||
|
connect_has_focus_notify: move |entry| {
|
||||||
|
if !entry.is_focus() {
|
||||||
|
sender.output(NoteTitleEntryOutput::Content(entry.buffer().text().to_string().into())).unwrap();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init(
|
||||||
|
_init: Self::Init,
|
||||||
|
root: Self::Root,
|
||||||
|
sender: ComponentSender<Self>,
|
||||||
|
) -> ComponentParts<Self> {
|
||||||
|
let model = Self {
|
||||||
|
content: gtk::EntryBuffer::default(),
|
||||||
|
};
|
||||||
|
let widgets = view_output!();
|
||||||
|
ComponentParts { model, widgets }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, message: Self::Input, _sender: ComponentSender<Self>) {
|
||||||
|
match message {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,10 +20,10 @@ pub struct Note {
|
||||||
pub title: String,
|
pub title: String,
|
||||||
/// todo: replace body type with something more appropriate.
|
/// todo: replace body type with something more appropriate.
|
||||||
/// a rope-backed buffer, perhaps.
|
/// a rope-backed buffer, perhaps.
|
||||||
body: String,
|
pub content: String,
|
||||||
created_at: chrono::DateTime<chrono::Utc>,
|
pub created_at: chrono::DateTime<chrono::Utc>,
|
||||||
/// if a note has never been updated since its creation, updated_at will be none.
|
/// if a note has never been updated since its creation, updated_at will be none.
|
||||||
updated_at: Option<chrono::DateTime<chrono::Utc>>,
|
pub updated_at: Option<chrono::DateTime<chrono::Utc>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq for Note {
|
impl PartialEq for Note {
|
||||||
|
@ -40,6 +40,12 @@ impl PartialOrd for Note {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Ord for Note {
|
||||||
|
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||||
|
self.created_at.partial_cmp(&other.created_at).unwrap_or(std::cmp::Ordering::Equal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Note {
|
impl Note {
|
||||||
pub fn new<T>(title: T) -> Self
|
pub fn new<T>(title: T) -> Self
|
||||||
where
|
where
|
||||||
|
@ -48,7 +54,7 @@ impl Note {
|
||||||
Self {
|
Self {
|
||||||
id: Uuid::new_v4(),
|
id: Uuid::new_v4(),
|
||||||
title: title.as_ref().to_string(),
|
title: title.as_ref().to_string(),
|
||||||
body: Default::default(),
|
content: Default::default(),
|
||||||
created_at: chrono::Utc::now(),
|
created_at: chrono::Utc::now(),
|
||||||
updated_at: None,
|
updated_at: None,
|
||||||
}
|
}
|
||||||
|
@ -61,7 +67,7 @@ impl Note {
|
||||||
Self {
|
Self {
|
||||||
id: self.id,
|
id: self.id,
|
||||||
title: title.as_ref().to_string(),
|
title: title.as_ref().to_string(),
|
||||||
body: self.body,
|
content: self.content,
|
||||||
created_at: self.created_at,
|
created_at: self.created_at,
|
||||||
updated_at: Some(Utc::now())
|
updated_at: Some(Utc::now())
|
||||||
}
|
}
|
||||||
|
@ -71,7 +77,7 @@ impl Note {
|
||||||
Self {
|
Self {
|
||||||
id: self.id,
|
id: self.id,
|
||||||
title: self.title,
|
title: self.title,
|
||||||
body: body.as_ref().to_string(),
|
content: body.as_ref().to_string(),
|
||||||
created_at: self.created_at,
|
created_at: self.created_at,
|
||||||
updated_at: Some(Utc::now()),
|
updated_at: Some(Utc::now()),
|
||||||
}
|
}
|
||||||
|
@ -88,7 +94,15 @@ impl Note {
|
||||||
where
|
where
|
||||||
P: AsRef<Path>,
|
P: AsRef<Path>,
|
||||||
{
|
{
|
||||||
let file = File::open(path).map_err(|_| Error::NoteFileRead)?;
|
let file = File::create(path).map_err(|_| Error::NoteFileRead)?;
|
||||||
|
self.write(file)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_in<P>(&self, path: P) -> Result<(), Error>
|
||||||
|
where
|
||||||
|
P: AsRef<Path>,
|
||||||
|
{
|
||||||
|
let file = File::create(path.as_ref().join(self.id.to_string())).map_err(|_| Error::NoteFileRead)?;
|
||||||
self.write(file)
|
self.write(file)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -128,7 +142,7 @@ mod test {
|
||||||
let note1 = Note {
|
let note1 = Note {
|
||||||
id: Default::default(),
|
id: Default::default(),
|
||||||
title: Default::default(),
|
title: Default::default(),
|
||||||
body: Default::default(),
|
content: Default::default(),
|
||||||
created_at: DateTime::from_timestamp(2000000000, 0).unwrap(),
|
created_at: DateTime::from_timestamp(2000000000, 0).unwrap(),
|
||||||
updated_at: Default::default(),
|
updated_at: Default::default(),
|
||||||
};
|
};
|
||||||
|
@ -136,7 +150,7 @@ mod test {
|
||||||
let note2 = Note {
|
let note2 = Note {
|
||||||
id: Default::default(),
|
id: Default::default(),
|
||||||
title: Default::default(),
|
title: Default::default(),
|
||||||
body: Default::default(),
|
content: Default::default(),
|
||||||
created_at: DateTime::from_timestamp(1000000000, 0).unwrap(),
|
created_at: DateTime::from_timestamp(1000000000, 0).unwrap(),
|
||||||
updated_at: Default::default(),
|
updated_at: Default::default(),
|
||||||
};
|
};
|
||||||
|
|
|
@ -8,7 +8,7 @@ use crate::{error::Error, note::Note};
|
||||||
|
|
||||||
/// a notebook is a representation of a named collection of notes and child notebooks.
|
/// a notebook is a representation of a named collection of notes and child notebooks.
|
||||||
/// notebooks are ephemeral, and are represented on-disk by the directory tree.
|
/// notebooks are ephemeral, and are represented on-disk by the directory tree.
|
||||||
#[derive(Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
pub struct Notebook {
|
pub struct Notebook {
|
||||||
pub root_path: PathBuf,
|
pub root_path: PathBuf,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
|
Loading…
Reference in a new issue