This commit is contained in:
2025-07-17 02:18:29 +01:00
parent fe8bbae68e
commit 224300f3ea
29 changed files with 1880 additions and 1439 deletions
+192 -56
View File
@@ -1,18 +1,36 @@
use egui::RichText;
use crate::{
PROJECT_FOLDER, RightPanelContent,
main_editor::MainEditor,
note::Note,
object::ObjectInstance,
template::{FieldType, Template},
content_editor::MainEditor,
editors::{
asset_editor::Asset, content_editor::ContentSection, object_editor::ObjectInstance,
tags::Tag, template_editor::Template,
},
note_editor::Note,
};
pub struct Explorer {}
pub struct Explorer {
templates: Vec<Template>,
objects: Vec<ObjectInstance>,
notes: Vec<Note>,
documents: Vec<MainEditor>,
tags: Vec<Tag>,
assets: Vec<Asset>,
}
impl Explorer {
pub fn new() -> Self {
Self {}
Self {
templates: Vec::new(),
objects: Vec::new(),
notes: Vec::new(),
documents: Vec::new(),
tags: Vec::new(),
assets: Vec::new(),
}
}
pub fn objects(&self) -> Vec<ObjectInstance> {
self.objects.clone()
}
pub fn ui(
@@ -21,14 +39,11 @@ impl Explorer {
load_doc: &mut Option<MainEditor>,
ui: &mut egui::Ui,
) {
let (templates, objects) = match Self::load_templates() {
Ok((templates, objects)) => (templates, objects),
Err(e) => {
eprintln!("Failed to load project: {e}");
ui.label(RichText::new("Failed to load project").color(egui::Color32::RED));
return;
}
};
self.load_templates().expect("Failed to load templates");
self.load_objects().expect("Failed to load objects");
self.load_notes().expect("Failed to load notes");
self.load_documents().expect("Failed to load documents");
self.load_assets().expect("Failed to load assets");
ui.vertical(|ui| {
egui::collapsing_header::CollapsingState::load_with_default_open(
@@ -40,18 +55,12 @@ impl Explorer {
ui.horizontal(|ui| {
ui.label("Templates");
if ui.button("+").clicked() {
*to_load = Some(RightPanelContent::Template {
template: Box::new(Template::default()),
new_field_name: Default::default(),
new_field_type: FieldType::SingleLine,
new_field_required: false,
new_field_description: Default::default(),
});
*to_load = Some(RightPanelContent::template(Some(Template::default())));
}
});
})
.body(|ui| {
for template in &templates {
for template in &self.templates {
let id = ui.make_persistent_id(template.name.clone());
egui::collapsing_header::CollapsingState::load_with_default_open(
ui.ctx(),
@@ -66,22 +75,21 @@ impl Explorer {
// create a new object based on this template
if ui.button("+").clicked() {
*to_load = Some(RightPanelContent::Object {
object: Box::new(ObjectInstance::new(template)),
});
*to_load = Some(RightPanelContent::object(Some(ObjectInstance::new(
template,
))));
}
})
.body(|ui| {
for object in &objects {
for object in &self.objects {
if object.template_id == template.id {
ui.horizontal(|ui| {
ui.add_space(10.0);
// load the object
if ui.selectable_label(false, &object.name).clicked() {
*to_load = Some(RightPanelContent::Object {
object: Box::new(object.clone()),
});
*to_load =
Some(RightPanelContent::object(Some(object.clone())));
}
});
}
@@ -90,8 +98,6 @@ impl Explorer {
}
});
let notes = Self::load_notes().unwrap();
egui::collapsing_header::CollapsingState::load_with_default_open(
ui.ctx(),
ui.make_persistent_id("notes"),
@@ -101,37 +107,93 @@ impl Explorer {
ui.horizontal(|ui| {
ui.label("Notes");
if ui.button("+").clicked() {
*to_load = Some(RightPanelContent::Note {
note: Box::new(Note::default()),
});
*to_load = Some(RightPanelContent::note(Some(Note::default())));
}
});
})
.body(|ui| {
for note in &notes {
for note in &self.notes {
ui.horizontal(|ui| {
ui.add_space(10.0);
// load the note
if ui.selectable_label(false, &note.name).clicked() {
*to_load = Some(RightPanelContent::Note {
note: Box::new(note.clone()),
});
*to_load = Some(RightPanelContent::note(Some(note.clone())));
}
});
}
});
let documents = Self::load_documents().unwrap();
egui::collapsing_header::CollapsingState::load_with_default_open(
ui.ctx(),
ui.make_persistent_id("projects"),
true,
)
.show_header(ui, |ui| {
ui.horizontal(|ui| {
ui.label("Projects");
if ui.button("+").clicked() {
*load_doc = Some(MainEditor::open(ContentSection::new()));
}
});
})
.body(|ui| {
// Convert MainEditor vec to ContentSection vec
let content_sections: Vec<ContentSection> = self
.documents
.iter()
.map(|doc| doc.content.clone())
.collect();
egui::CollapsingHeader::new("Projects").show(ui, |ui| {
for document in &documents {
Self::render_document_tree(ui, &content_sections, None, load_doc);
});
self.tags = Tag::load_all();
egui::collapsing_header::CollapsingState::load_with_default_open(
ui.ctx(),
ui.make_persistent_id("tags"),
true,
)
.show_header(ui, |ui| {
ui.horizontal(|ui| {
ui.label("Tags");
if ui.button("+").clicked() {
*to_load = Some(RightPanelContent::Tag(Tag::default()));
}
});
})
.body(|ui| {
for tag in &mut self.tags {
ui.horizontal(|ui| {
ui.add_space(10.0);
// load the document
if ui.selectable_label(false, &document.name).clicked() {
*load_doc = Some(document.clone());
// load the tag
if tag.list_ui(ui).clicked() {
*to_load = Some(RightPanelContent::Tag(tag.clone()));
}
});
}
});
egui::collapsing_header::CollapsingState::load_with_default_open(
ui.ctx(),
ui.make_persistent_id("assets"),
true,
)
.show_header(ui, |ui| {
ui.horizontal(|ui| {
ui.label("Assets");
});
})
.body(|ui| {
for asset in &mut self.assets {
ui.horizontal(|ui| {
ui.add_space(10.0);
// load the asset
if ui.selectable_label(false, &asset.name).clicked() {
*to_load = Some(RightPanelContent::Asset(Box::new(asset.clone())));
}
});
}
@@ -139,10 +201,56 @@ impl Explorer {
});
}
fn load_templates() -> std::io::Result<(Vec<Template>, Vec<ObjectInstance>)> {
let mut templates = Vec::new();
let mut objects = Vec::new();
/// Recursively renders a tree of documents.
///
/// Each document is represented by a single element in the `documents` array.
/// The `parent_id` parameter is used to filter out documents that do not have the current
/// parent. If `parent_id` is `None`, all documents are rendered.
///
/// `load_doc` is a mutable reference to a `MainEditor`. When a document is clicked, it
/// is loaded into the `MainEditor` and returned as `Some`.
fn render_document_tree(
ui: &mut egui::Ui,
documents: &[ContentSection],
parent_id: Option<&str>,
load_doc: &mut Option<MainEditor>,
) {
// Filter documents that have the current parent (or no parent if this is the root)
let child_docs: Vec<&ContentSection> = documents
.iter()
.filter(|doc| doc.parent.as_deref() == parent_id)
.collect();
for doc in child_docs {
egui::collapsing_header::CollapsingState::load_with_default_open(
ui.ctx(),
ui.make_persistent_id(&doc.id),
false,
)
.show_header(ui, |ui| {
ui.horizontal(|ui| {
// Document title
if ui.selectable_label(false, &doc.title).clicked() {
*load_doc = Some(MainEditor::open(doc.clone()));
}
// Add child button
if ui.button("+").clicked() {
let child = doc.create_child();
*load_doc = Some(MainEditor::open(child));
}
});
})
.body(|ui| {
// recursive call to render the next level of documents
Self::render_document_tree(ui, documents, Some(&doc.id), load_doc);
});
}
}
// load templates from the templates folder
fn load_templates(&mut self) -> std::io::Result<()> {
let mut templates = Vec::new();
for entry in std::fs::read_dir(PROJECT_FOLDER.join("templates")).unwrap() {
let path = entry.unwrap().path();
match Template::load(path.file_stem().unwrap().to_str().unwrap()) {
@@ -150,7 +258,14 @@ impl Explorer {
Err(err) => eprintln!("Could not parse file {path:?}: {err}"),
}
}
self.templates = templates;
Ok(())
}
// load objects from the objects folder
fn load_objects(&mut self) -> std::io::Result<()> {
let mut objects = Vec::new();
for entry in std::fs::read_dir(PROJECT_FOLDER.join("objects")).unwrap() {
let path = entry.unwrap().path();
match ObjectInstance::load(path.file_stem().unwrap().to_str().unwrap()) {
@@ -158,11 +273,13 @@ impl Explorer {
Err(err) => eprintln!("Could not parse file {path:?}: {err}"),
}
}
self.objects = objects;
Ok((templates, objects))
Ok(())
}
fn load_notes() -> std::io::Result<Vec<Note>> {
// load notes from the notes folder
fn load_notes(&mut self) -> std::io::Result<()> {
let mut notes = Vec::new();
for entry in std::fs::read_dir(PROJECT_FOLDER.join("notes")).unwrap() {
@@ -173,20 +290,39 @@ impl Explorer {
}
}
Ok(notes)
self.notes = notes;
Ok(())
}
fn load_documents() -> std::io::Result<Vec<MainEditor>> {
// load documents from the documents folder
fn load_documents(&mut self) -> std::io::Result<()> {
let mut documents = Vec::new();
for entry in std::fs::read_dir(PROJECT_FOLDER.join("documents")).unwrap() {
let path = entry.unwrap().path();
match MainEditor::load(path.file_stem().unwrap().to_str().unwrap()) {
Ok(document) => documents.push(document),
match ContentSection::load(path.file_stem().unwrap().to_str().unwrap()) {
Ok(document) => documents.push(MainEditor::open(document)),
Err(err) => eprintln!("Could not parse file {path:?}: {err}"),
}
}
Ok(documents)
self.documents = documents;
Ok(())
}
fn load_assets(&mut self) -> std::io::Result<()> {
let mut assets = Vec::new();
for entry in std::fs::read_dir(PROJECT_FOLDER.join("assets")).unwrap() {
let path = entry.unwrap().path();
assets.push(Asset::open(
path.file_stem().unwrap().to_str().unwrap().to_string(),
));
}
self.assets = assets;
Ok(())
}
}