finally another commit

This commit is contained in:
2025-07-23 23:56:07 +01:00
parent 224300f3ea
commit ba468cafa7
30 changed files with 1591 additions and 318 deletions
+24 -10
View File
@@ -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)
+34 -9
View File
@@ -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;
}
}
});
});
+78
View File
@@ -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
View File
@@ -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;
+45 -61
View File
@@ -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;
}
});
+56 -9
View File
@@ -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,
}