finally another commit
This commit is contained in:
+24
-10
@@ -4,37 +4,51 @@ use crate::{PROJECT_FOLDER, util};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Asset {
|
||||
pub new_name: String,
|
||||
pub name: String,
|
||||
pub old_name: String,
|
||||
pub saved: bool,
|
||||
}
|
||||
|
||||
impl Asset {
|
||||
pub fn open(name: String) -> Self {
|
||||
Self {
|
||||
old_name: name.clone(),
|
||||
new_name: name.clone(),
|
||||
name,
|
||||
saved: false,
|
||||
saved: true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn save(&mut self) {
|
||||
let old_path = Self::path(&self.old_name);
|
||||
let new_path = Self::path(&self.name);
|
||||
let old_path = Self::path(&self.name);
|
||||
let new_path = Self::path(&self.new_name);
|
||||
|
||||
println!("old_path: {old_path:?}");
|
||||
println!("new_path: {new_path:?}");
|
||||
|
||||
// move from src dir to name path
|
||||
std::fs::rename(&old_path, &new_path).unwrap();
|
||||
if let Err(err) = std::fs::rename(&old_path, &new_path) {
|
||||
match err.kind() {
|
||||
std::io::ErrorKind::NotFound => {
|
||||
let dir = new_path.parent().unwrap();
|
||||
if !dir.exists() {
|
||||
std::fs::create_dir_all(dir).unwrap();
|
||||
}
|
||||
std::fs::rename(&old_path, &new_path).unwrap();
|
||||
}
|
||||
_ => panic!("Failed to rename file: {err}"),
|
||||
}
|
||||
}
|
||||
self.saved = true;
|
||||
self.old_name = self.name.clone();
|
||||
self.name = self.new_name.clone();
|
||||
}
|
||||
|
||||
pub fn path(name: &str) -> std::path::PathBuf {
|
||||
PROJECT_FOLDER.join("assets").join(format!("{name}.png"))
|
||||
PROJECT_FOLDER.join("assets").join(name)
|
||||
}
|
||||
|
||||
pub fn ui(&mut self, ui: &mut egui::Ui) {
|
||||
ui.vertical(|ui| {
|
||||
util::saved_status(ui, self.saved, &self.name, &self.name);
|
||||
util::saved_status(ui, self.saved, &self.name, &self.new_name);
|
||||
|
||||
if ui.input(|i| i.key_pressed(egui::Key::S) && i.modifiers.ctrl)
|
||||
|| ui.button("Save").clicked()
|
||||
@@ -46,7 +60,7 @@ impl Asset {
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.strong("Filename:");
|
||||
if TextEdit::singleline(&mut self.name)
|
||||
if TextEdit::singleline(&mut self.new_name)
|
||||
.desired_width(f32::INFINITY)
|
||||
.frame(false)
|
||||
.show(ui)
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use egui::TextEdit;
|
||||
use egui::{TextEdit, text};
|
||||
use egui_commonmark::{CommonMarkCache, CommonMarkViewer};
|
||||
use serde::{self, Deserialize, Serialize};
|
||||
|
||||
use crate::{PROJECT_FOLDER, editors::tags::Tag, util};
|
||||
use crate::{PROJECT_FOLDER, editors::tags::Tag, llm_integration::content_llm::ai_enabled, util};
|
||||
|
||||
pub struct MainEditor {
|
||||
pub content: ContentSection,
|
||||
@@ -95,7 +95,7 @@ impl MainEditor {
|
||||
Self {
|
||||
content: ContentSection::new(),
|
||||
show_editor: false, // Start with editor hidden
|
||||
show_preview: true,
|
||||
show_preview: false,
|
||||
preview_cache: CommonMarkCache::default(),
|
||||
}
|
||||
}
|
||||
@@ -104,7 +104,7 @@ impl MainEditor {
|
||||
Self {
|
||||
content,
|
||||
show_editor: true,
|
||||
show_preview: true,
|
||||
show_preview: false,
|
||||
preview_cache: CommonMarkCache::default(),
|
||||
}
|
||||
}
|
||||
@@ -264,7 +264,7 @@ impl MainEditor {
|
||||
}
|
||||
|
||||
fn editor_ui(&mut self, ui: &mut egui::Ui) {
|
||||
egui::ScrollArea::both()
|
||||
let response = egui::ScrollArea::both()
|
||||
.auto_shrink([false, false])
|
||||
.id_salt("editor_scroll")
|
||||
.show(ui, |ui| {
|
||||
@@ -290,14 +290,39 @@ impl MainEditor {
|
||||
.hint_text("Type here...")
|
||||
.desired_width(max_width as f32);
|
||||
|
||||
if ui
|
||||
let mut ctx_menu = false;
|
||||
let response = ui
|
||||
.add_sized(
|
||||
egui::vec2(max_width as f32 - 30.0, ui.available_height()),
|
||||
text_edit,
|
||||
)
|
||||
.changed()
|
||||
{
|
||||
self.content.saved = false;
|
||||
.on_hover_text("Right click to open context menu")
|
||||
.context_menu(|ui| {
|
||||
ctx_menu = true;
|
||||
|
||||
ui.menu_button("AI Actions", |ui| {
|
||||
ui.add_enabled_ui(ai_enabled(), |ui| {
|
||||
if ui.button("Summarise").clicked() {
|
||||
println!("Summarise");
|
||||
}
|
||||
|
||||
if ui.button("Continue").clicked() {
|
||||
let content = self.content.content.clone();
|
||||
let response =
|
||||
crate::llm_integration::content_llm::continue_content(
|
||||
&content, "", 1024,
|
||||
)
|
||||
.unwrap();
|
||||
self.content.content.push_str(&response);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
if let Some(response) = response {
|
||||
if response.response.changed() || ctx_menu {
|
||||
self.content.saved = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
use std::io::Read;
|
||||
|
||||
use chrono::NaiveDate;
|
||||
use egui_extras::DatePickerButton;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::PROJECT_FOLDER;
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct ProjectContext {
|
||||
date: NaiveDate,
|
||||
project_name: String,
|
||||
project_author: String,
|
||||
project_description: String,
|
||||
}
|
||||
|
||||
impl ProjectContext {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn load() -> Self {
|
||||
let path = PROJECT_FOLDER.join("context.json");
|
||||
if let Ok(mut file) = std::fs::File::open(path) {
|
||||
let mut contents = String::new();
|
||||
file.read_to_string(&mut contents).unwrap();
|
||||
if let Ok(proj) = serde_json::from_str(&contents) {
|
||||
return proj;
|
||||
}
|
||||
}
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn save(&self) {
|
||||
let path = PROJECT_FOLDER.join("context.json");
|
||||
let content = serde_json::to_string_pretty(self).unwrap();
|
||||
std::fs::write(path, content).unwrap();
|
||||
}
|
||||
|
||||
pub fn ui(&mut self, ui: &mut egui::Ui) {
|
||||
// table
|
||||
egui::Grid::new("context_editor")
|
||||
.striped(true)
|
||||
.num_columns(2)
|
||||
.show(ui, |ui| {
|
||||
ui.label("Project Name");
|
||||
ui.text_edit_singleline(&mut self.project_name);
|
||||
|
||||
ui.end_row();
|
||||
|
||||
ui.label("Project Author");
|
||||
ui.text_edit_singleline(&mut self.project_author);
|
||||
|
||||
ui.end_row();
|
||||
|
||||
ui.label("Project Description");
|
||||
ui.text_edit_singleline(&mut self.project_description);
|
||||
|
||||
ui.end_row();
|
||||
|
||||
ui.label("Date");
|
||||
ui.add(DatePickerButton::new(&mut self.date));
|
||||
|
||||
ui.end_row();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ProjectContext {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
date: chrono::Local::now().naive_local().into(),
|
||||
project_name: "New Project".to_string(),
|
||||
project_author: "Your Name".to_string(),
|
||||
project_description: "Description of your project".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
pub mod asset_editor;
|
||||
pub mod content_editor;
|
||||
pub mod context_editor;
|
||||
pub mod note_editor;
|
||||
pub mod object_editor;
|
||||
pub mod tags;
|
||||
|
||||
@@ -1,23 +1,22 @@
|
||||
use core::f32;
|
||||
use std::path::Path;
|
||||
|
||||
use chrono::NaiveDate;
|
||||
use egui::{CollapsingHeader, Response, RichText, Sense, TextEdit, Ui, UiBuilder, vec2};
|
||||
use egui::{CollapsingHeader, RichText, Sense, TextEdit, Ui, UiBuilder, vec2};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
PROJECT_FOLDER, RightPanelContent,
|
||||
editors::{
|
||||
tags::Tag,
|
||||
template_editor::{FieldDefinition, FieldType, FieldValue, Template},
|
||||
template_editor::{FieldValue, Template},
|
||||
},
|
||||
util,
|
||||
};
|
||||
|
||||
pub type ObjectId = String;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct ObjectInstance {
|
||||
// template info
|
||||
pub id: String,
|
||||
pub id: ObjectId,
|
||||
pub template_id: String,
|
||||
|
||||
// instance info
|
||||
@@ -67,7 +66,7 @@ impl ObjectInstance {
|
||||
let mut fields = std::collections::HashMap::new();
|
||||
|
||||
for field in &template.fields {
|
||||
fields.insert(field.name.clone(), FieldValue::default());
|
||||
fields.insert(field.name.clone(), FieldValue::from_type(&field.field_type));
|
||||
}
|
||||
|
||||
Self {
|
||||
@@ -106,7 +105,7 @@ impl ObjectInstance {
|
||||
ui: &mut Ui,
|
||||
template: &Template,
|
||||
right_panel: &mut Option<RightPanelContent>,
|
||||
objects: &mut Vec<ObjectInstance>,
|
||||
objects: &mut [ObjectInstance],
|
||||
) {
|
||||
let _ = right_panel;
|
||||
if ui.input(|i| i.key_pressed(egui::Key::S) && i.modifiers.ctrl) {
|
||||
@@ -193,13 +192,7 @@ impl ObjectInstance {
|
||||
|
||||
ui.separator();
|
||||
|
||||
Self::render_field(
|
||||
field_def,
|
||||
field_value,
|
||||
ui,
|
||||
&mut self.saved,
|
||||
objects,
|
||||
);
|
||||
Self::render_field(field_value, ui, &mut self.saved, objects);
|
||||
|
||||
ui.separator();
|
||||
});
|
||||
@@ -210,27 +203,25 @@ impl ObjectInstance {
|
||||
}
|
||||
|
||||
fn render_field(
|
||||
field_def: &FieldDefinition,
|
||||
field_value: &mut FieldValue,
|
||||
ui: &mut egui::Ui,
|
||||
saved: &mut bool,
|
||||
objects: &mut Vec<ObjectInstance>,
|
||||
objects: &mut [ObjectInstance],
|
||||
) {
|
||||
match field_def.field_type {
|
||||
FieldType::SingleLine => {
|
||||
if TextEdit::singleline(&mut field_value.value)
|
||||
match field_value {
|
||||
FieldValue::SingleLine(value) => {
|
||||
if TextEdit::singleline(value)
|
||||
.desired_width(f32::INFINITY)
|
||||
.frame(false)
|
||||
.show(ui)
|
||||
.response
|
||||
.changed()
|
||||
{
|
||||
field_value.modified = true;
|
||||
*saved = false;
|
||||
}
|
||||
}
|
||||
FieldType::MultiLine => {
|
||||
if TextEdit::multiline(&mut field_value.value)
|
||||
FieldValue::MultiLine(value) => {
|
||||
if TextEdit::multiline(value)
|
||||
.desired_width(f32::INFINITY)
|
||||
.desired_rows(5)
|
||||
.frame(false)
|
||||
@@ -238,51 +229,39 @@ impl ObjectInstance {
|
||||
.response
|
||||
.changed()
|
||||
{
|
||||
field_value.modified = true;
|
||||
*saved = false;
|
||||
}
|
||||
}
|
||||
FieldType::Date => {
|
||||
let date_str = &field_value.value;
|
||||
let mut date = NaiveDate::parse_from_str(date_str, "%Y-%m-%d")
|
||||
.unwrap_or_else(|_| chrono::Local::now().date_naive());
|
||||
|
||||
let response = ui.add(egui_extras::DatePickerButton::new(&mut date));
|
||||
|
||||
FieldValue::Date(value) => {
|
||||
let response = ui.add(egui_extras::DatePickerButton::new(value));
|
||||
if response.changed() {
|
||||
field_value.value = date.format("%Y-%m-%d").to_string();
|
||||
field_value.modified = true;
|
||||
*saved = false;
|
||||
}
|
||||
}
|
||||
FieldType::Number => {
|
||||
let mut num = field_value.value.parse::<f64>().unwrap_or(0.0);
|
||||
let response = ui.add(egui::DragValue::new(&mut num).speed(0.1));
|
||||
|
||||
FieldValue::Number(value) => {
|
||||
let response = ui.add(egui::DragValue::new(value).speed(0.1));
|
||||
if response.changed() {
|
||||
field_value.value = num.to_string();
|
||||
field_value.modified = true;
|
||||
*saved = false;
|
||||
}
|
||||
}
|
||||
FieldType::Image => {
|
||||
FieldValue::Image(value) => {
|
||||
ui.scope_builder(UiBuilder::new().sense(Sense::HOVER), |ui| {
|
||||
let id = ui.make_persistent_id("is_hovered");
|
||||
let should_show = field_value.value.is_empty()
|
||||
let should_show = value.is_empty()
|
||||
|| ui.response().hovered()
|
||||
|| ui.memory(|mem| mem.data.get_temp(id).unwrap_or(false));
|
||||
|| ui.memory(|mem| mem.data.get_temp(id).unwrap_or(false))
|
||||
|| !PROJECT_FOLDER.join("assets").join(&value).exists();
|
||||
|
||||
// Simple path input for now
|
||||
if should_show {
|
||||
let response = TextEdit::singleline(&mut field_value.value)
|
||||
.hint_text("Path to image")
|
||||
let response = TextEdit::singleline(value)
|
||||
.hint_text("Asset name (ignore file extension)")
|
||||
.desired_width(f32::INFINITY)
|
||||
.frame(false)
|
||||
.show(ui)
|
||||
.response;
|
||||
|
||||
if response.changed() {
|
||||
field_value.modified = true;
|
||||
*saved = false;
|
||||
}
|
||||
|
||||
@@ -292,10 +271,10 @@ impl ObjectInstance {
|
||||
}
|
||||
|
||||
// If we have a valid path, try to display a preview
|
||||
if !field_value.value.is_empty() {
|
||||
if let Ok(bytes) = std::fs::read(&field_value.value) {
|
||||
let path = PROJECT_FOLDER.join(&field_value.value);
|
||||
if !value.is_empty() {
|
||||
let path = PROJECT_FOLDER.join("assets").join(&value);
|
||||
|
||||
if let Ok(bytes) = std::fs::read(&path) {
|
||||
let image_source = egui::ImageSource::Bytes {
|
||||
uri: std::borrow::Cow::Owned(path.to_str().unwrap().to_string()),
|
||||
bytes: bytes.into(),
|
||||
@@ -307,10 +286,12 @@ impl ObjectInstance {
|
||||
}
|
||||
});
|
||||
}
|
||||
FieldType::Link => ObjectInstance::selector_ui(field_value, objects, ui, saved),
|
||||
FieldType::Links => {
|
||||
if ui.text_edit_singleline(&mut field_value.value).changed() {
|
||||
field_value.modified = true;
|
||||
FieldValue::Link(template_id) => {
|
||||
ObjectInstance::selector_ui(template_id, objects, ui, saved)
|
||||
}
|
||||
FieldValue::Links(_template_ids) => {
|
||||
let mut value = String::new();
|
||||
if ui.text_edit_singleline(&mut value).changed() {
|
||||
*saved = false;
|
||||
}
|
||||
}
|
||||
@@ -318,13 +299,13 @@ impl ObjectInstance {
|
||||
}
|
||||
|
||||
fn selector_ui(
|
||||
field_value: &mut FieldValue,
|
||||
objects: &mut Vec<ObjectInstance>,
|
||||
selected: &mut ObjectId,
|
||||
objects: &mut [ObjectInstance],
|
||||
ui: &mut egui::Ui,
|
||||
saved: &mut bool,
|
||||
) {
|
||||
if !field_value.value.is_empty() {
|
||||
if let Ok(object) = ObjectInstance::load(&field_value.value) {
|
||||
if !selected.is_empty() {
|
||||
if let Ok(object) = ObjectInstance::load(selected) {
|
||||
ui.strong(&object.name);
|
||||
}
|
||||
}
|
||||
@@ -334,7 +315,7 @@ impl ObjectInstance {
|
||||
|
||||
let ctx = ui.ctx();
|
||||
let mut object_selection: usize =
|
||||
ctx.memory_mut(|mem| *mem.data.get_temp_mut_or_default::<usize>(id));
|
||||
ctx.memory(|mem| mem.data.get_temp::<usize>(id).unwrap_or(0));
|
||||
|
||||
if objects.is_empty() {
|
||||
ui.label("No objects available");
|
||||
@@ -348,15 +329,18 @@ impl ObjectInstance {
|
||||
});
|
||||
}
|
||||
|
||||
let ctx = ui.ctx();
|
||||
ctx.memory_mut(|mem| {
|
||||
*mem.data.get_temp_mut_or_default::<usize>(id) = object_selection;
|
||||
});
|
||||
|
||||
if ui.button("Set").clicked() && object_selection < objects.len() {
|
||||
field_value.value = objects[object_selection].id.clone();
|
||||
field_value.modified = true;
|
||||
*selected = objects[object_selection].id.clone();
|
||||
*saved = false;
|
||||
}
|
||||
|
||||
if ui.button("Remove").clicked() {
|
||||
field_value.value.clear();
|
||||
field_value.modified = true;
|
||||
*selected = String::new();
|
||||
*saved = false;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use core::fmt;
|
||||
|
||||
use chrono::NaiveDate;
|
||||
use egui::ScrollArea;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -16,7 +17,7 @@ pub enum FieldType {
|
||||
MultiLine,
|
||||
Date,
|
||||
Number,
|
||||
Link,
|
||||
Link { template_id: Option<String> },
|
||||
Links,
|
||||
}
|
||||
|
||||
@@ -34,17 +35,54 @@ impl FieldType {
|
||||
FieldType::MultiLine,
|
||||
FieldType::Date,
|
||||
FieldType::Number,
|
||||
FieldType::Link,
|
||||
FieldType::Link { template_id: None },
|
||||
FieldType::Links,
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum FieldValue {
|
||||
Image(String),
|
||||
SingleLine(String),
|
||||
MultiLine(String),
|
||||
Date(NaiveDate),
|
||||
Number(f64),
|
||||
Link(String),
|
||||
Links(Vec<String>),
|
||||
}
|
||||
|
||||
impl FieldValue {
|
||||
pub fn from_type(_type: &FieldType) -> Self {
|
||||
match _type {
|
||||
FieldType::Image => Self::Image(String::new()),
|
||||
FieldType::SingleLine => Self::SingleLine(String::new()),
|
||||
FieldType::MultiLine => Self::MultiLine(String::new()),
|
||||
FieldType::Date => Self::Date(NaiveDate::from_ymd_opt(1970, 1, 1).unwrap()),
|
||||
FieldType::Number => Self::Number(0.0),
|
||||
FieldType::Link { template_id: None } => Self::Link(String::new()),
|
||||
FieldType::Link {
|
||||
template_id: Some(template_id),
|
||||
} => Self::Link(template_id.clone()),
|
||||
FieldType::Links => Self::Links(Vec::new()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for FieldValue {
|
||||
fn default() -> Self {
|
||||
Self::SingleLine(String::new())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct FieldDefinition {
|
||||
pub name: String,
|
||||
pub field_type: FieldType,
|
||||
pub required: bool,
|
||||
|
||||
#[serde(default)]
|
||||
pub on_preview: bool,
|
||||
pub description: Option<String>,
|
||||
}
|
||||
|
||||
@@ -73,6 +111,9 @@ pub struct Template {
|
||||
|
||||
#[serde(skip)]
|
||||
pub new_field_description: String,
|
||||
|
||||
#[serde(skip)]
|
||||
pub new_field_on_preview: bool,
|
||||
}
|
||||
|
||||
impl fmt::Debug for Template {
|
||||
@@ -105,6 +146,7 @@ impl Clone for Template {
|
||||
new_field_type: FieldType::default(),
|
||||
new_field_required: false,
|
||||
new_field_description: "".to_string(),
|
||||
new_field_on_preview: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -123,6 +165,7 @@ impl Default for Template {
|
||||
new_field_type: FieldType::default(),
|
||||
new_field_required: false,
|
||||
new_field_description: "".to_string(),
|
||||
new_field_on_preview: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -325,6 +368,12 @@ impl Template {
|
||||
}
|
||||
ui.end_row();
|
||||
|
||||
ui.label("On Preview:");
|
||||
if ui.checkbox(&mut field.on_preview, "").clicked() {
|
||||
self.saved = false;
|
||||
}
|
||||
ui.end_row();
|
||||
|
||||
ui.label("Description:");
|
||||
if ui
|
||||
.text_edit_singleline(
|
||||
@@ -377,6 +426,10 @@ impl Template {
|
||||
ui.checkbox(&mut self.new_field_required, "");
|
||||
ui.end_row();
|
||||
|
||||
ui.label("On Preview:");
|
||||
ui.checkbox(&mut self.new_field_on_preview, "");
|
||||
ui.end_row();
|
||||
|
||||
ui.label("Description:");
|
||||
ui.text_edit_singleline(&mut self.new_field_description);
|
||||
ui.end_row();
|
||||
@@ -385,6 +438,7 @@ impl Template {
|
||||
self.fields.push(FieldDefinition {
|
||||
name: self.new_field_name.clone(),
|
||||
field_type: self.new_field_type.clone(),
|
||||
on_preview: self.new_field_on_preview,
|
||||
required: self.new_field_required,
|
||||
description: if self.new_field_description.is_empty() {
|
||||
None
|
||||
@@ -404,10 +458,3 @@ impl Template {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||
pub struct FieldValue {
|
||||
pub value: String,
|
||||
#[serde(skip)]
|
||||
pub modified: bool,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user