380 lines
12 KiB
Rust
380 lines
12 KiB
Rust
use std::path::Path;
|
|
|
|
use zxq5_id_lib::Identifiable;
|
|
|
|
use crate::{
|
|
PROJECT_FOLDER, RightPanelContent,
|
|
content_editor::MainEditor,
|
|
editors::{asset_editor::Asset, content_editor::ContentSection, tags::Tag},
|
|
model::{object::Object, template::Template},
|
|
note_editor::Note,
|
|
services::persist::{FILESYSTEM, FsError, LegacyFileSystem},
|
|
};
|
|
|
|
pub struct Explorer {
|
|
templates: Vec<Template>,
|
|
objects: Vec<Object>,
|
|
notes: Vec<Note>,
|
|
documents: Vec<MainEditor>,
|
|
tags: Vec<Tag>,
|
|
}
|
|
|
|
impl Explorer {
|
|
pub fn new() -> Self {
|
|
Self {
|
|
templates: Vec::new(),
|
|
objects: Vec::new(),
|
|
notes: Vec::new(),
|
|
documents: Vec::new(),
|
|
tags: Vec::new(),
|
|
}
|
|
}
|
|
|
|
pub fn objects(&self) -> Vec<Object> {
|
|
self.objects.clone()
|
|
}
|
|
|
|
pub fn ui(
|
|
&mut self,
|
|
to_load: &mut Option<RightPanelContent>,
|
|
load_doc: &mut Option<MainEditor>,
|
|
ui: &mut egui::Ui,
|
|
) {
|
|
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_tags().expect("Failed to load tags");
|
|
self.load_assets().expect("Failed to load assets");
|
|
|
|
ui.vertical(|ui| {
|
|
self.render_templates(ui, to_load);
|
|
self.render_notes(ui, to_load);
|
|
self.render_doc_root(ui, load_doc);
|
|
self.render_tags(ui, to_load);
|
|
self.render_assets(ui, to_load);
|
|
});
|
|
}
|
|
|
|
fn render_templates(&mut self, ui: &mut egui::Ui, to_load: &mut Option<RightPanelContent>) {
|
|
egui::collapsing_header::CollapsingState::load_with_default_open(
|
|
ui.ctx(),
|
|
ui.make_persistent_id("templates"),
|
|
true,
|
|
)
|
|
.show_header(ui, |ui| {
|
|
ui.horizontal(|ui| {
|
|
ui.label("Templates");
|
|
if ui.button("+").clicked() {
|
|
*to_load = Some(RightPanelContent::template(Template::default()));
|
|
}
|
|
});
|
|
})
|
|
.body(|ui| {
|
|
for template in &self.templates {
|
|
let id = ui.make_persistent_id(template.name.clone());
|
|
egui::collapsing_header::CollapsingState::load_with_default_open(
|
|
ui.ctx(),
|
|
id,
|
|
true,
|
|
)
|
|
.show_header(ui, |ui| {
|
|
// load the template
|
|
if ui.selectable_label(false, template.name.clone()).clicked() {
|
|
*to_load = Some(RightPanelContent::template(template.clone()));
|
|
}
|
|
|
|
// create a new object based on this template
|
|
if ui.button("+").clicked() {
|
|
*to_load = Some(RightPanelContent::object(
|
|
Object::new(&template),
|
|
template.clone(),
|
|
));
|
|
}
|
|
})
|
|
.body(|ui| {
|
|
for object in &self.objects {
|
|
if object.template == 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.clone(),
|
|
template.clone(),
|
|
));
|
|
}
|
|
});
|
|
}
|
|
}
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
fn render_notes(&mut self, ui: &mut egui::Ui, to_load: &mut Option<RightPanelContent>) {
|
|
egui::collapsing_header::CollapsingState::load_with_default_open(
|
|
ui.ctx(),
|
|
ui.make_persistent_id("notes"),
|
|
true,
|
|
)
|
|
.show_header(ui, |ui| {
|
|
ui.horizontal(|ui| {
|
|
ui.label("Notes");
|
|
if ui.button("+").clicked() {
|
|
*to_load = Some(RightPanelContent::note(Some(Note::default())));
|
|
}
|
|
});
|
|
})
|
|
.body(|ui| {
|
|
for note in &self.notes {
|
|
ui.horizontal(|ui| {
|
|
ui.add_space(10.0);
|
|
|
|
// load the note
|
|
if ui.selectable_label(false, ¬e.name).clicked() {
|
|
*to_load = Some(RightPanelContent::note(Some(note.clone())));
|
|
}
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
fn render_doc_root(&self, ui: &mut egui::Ui, load_doc: &mut Option<MainEditor>) {
|
|
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();
|
|
|
|
Self::render_doc_branch(ui, &content_sections, None, load_doc);
|
|
});
|
|
}
|
|
|
|
fn render_doc_branch(
|
|
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_doc_branch(ui, documents, Some(&doc.id), load_doc);
|
|
});
|
|
}
|
|
}
|
|
|
|
fn render_tags(&mut self, ui: &mut egui::Ui, to_load: &mut Option<RightPanelContent>) {
|
|
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 tag
|
|
if tag.list_ui(ui).clicked() {
|
|
*to_load = Some(RightPanelContent::Tag(tag.clone()));
|
|
}
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
fn render_assets(&mut self, ui: &mut egui::Ui, to_load: &mut Option<RightPanelContent>) {
|
|
Self::render_asset_dir(ui, to_load, Path::new("assets"));
|
|
}
|
|
|
|
fn render_asset_dir(ui: &mut egui::Ui, to_load: &mut Option<RightPanelContent>, path: &Path) {
|
|
let files = FILESYSTEM.lsfiles(path).unwrap();
|
|
let dirs = FILESYSTEM.lsdirs(path).unwrap();
|
|
|
|
let file_name = path.file_name().unwrap().to_str().unwrap().to_string();
|
|
|
|
egui::collapsing_header::CollapsingState::load_with_default_open(
|
|
ui.ctx(),
|
|
ui.make_persistent_id(&file_name),
|
|
false,
|
|
)
|
|
.show_header(ui, |ui| {
|
|
ui.horizontal(|ui| {
|
|
ui.label(file_name);
|
|
let _clicked = ui.button("+").on_hover_text("Add new item").clicked();
|
|
});
|
|
})
|
|
.body(|ui| {
|
|
// recursive call to render the next level of documents
|
|
for dir in dirs.iter() {
|
|
Self::render_asset_dir(ui, to_load, dir);
|
|
}
|
|
|
|
for file in files.iter() {
|
|
Self::render_asset(ui, to_load, file);
|
|
}
|
|
});
|
|
}
|
|
|
|
fn render_asset(ui: &mut egui::Ui, to_load: &mut Option<RightPanelContent>, path: &Path) {
|
|
let file_name = path.file_name().unwrap().to_str().unwrap().to_string();
|
|
|
|
if ui
|
|
.selectable_label(false, format!("📄 {file_name}"))
|
|
.clicked()
|
|
{
|
|
// use asset::load to get the file at the path
|
|
let asset_path = path.strip_prefix(PROJECT_FOLDER.join("assets")).unwrap();
|
|
let asset = Asset::open(asset_path.to_string_lossy().to_string());
|
|
*to_load = Some(RightPanelContent::Asset(Box::new(asset)));
|
|
}
|
|
}
|
|
|
|
fn load_assets(&mut self) -> Result<(), FsError> {
|
|
let path = Path::new("assets");
|
|
if !FILESYSTEM.exists(path) {
|
|
FILESYSTEM.mkdir(path)?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
// load templates from the templates folder
|
|
fn load_templates(&mut self) -> Result<(), FsError> {
|
|
let path = Path::new("templates");
|
|
|
|
if !FILESYSTEM.exists(path) {
|
|
FILESYSTEM.mkdir(path)?;
|
|
}
|
|
let mut templates = Vec::new();
|
|
for entry in FILESYSTEM.lsfiles(path)? {
|
|
match FILESYSTEM.read::<Template>(&entry) {
|
|
Ok(t) => templates.push(t),
|
|
Err(err) => eprintln!("Could not parse file {entry:?}: {err}"),
|
|
}
|
|
}
|
|
self.templates = templates;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
// load objects from the objects folder
|
|
fn load_objects(&mut self) -> Result<(), FsError> {
|
|
let path = Path::new("objects");
|
|
if !FILESYSTEM.exists(path) {
|
|
FILESYSTEM.mkdir(path)?;
|
|
}
|
|
let mut objects = Vec::new();
|
|
for entry in FILESYSTEM.lsfiles(path)? {
|
|
match FILESYSTEM.read::<Object>(&entry) {
|
|
Ok(o) => objects.push(o),
|
|
Err(err) => eprintln!("Could not parse file {entry:?}: {err}"),
|
|
}
|
|
}
|
|
self.objects = objects;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
// load notes from the notes folder
|
|
fn load_notes(&mut self) -> Result<(), FsError> {
|
|
let path = Path::new("notes");
|
|
if !FILESYSTEM.exists(path) {
|
|
FILESYSTEM.mkdir(path)?;
|
|
}
|
|
let mut notes = Vec::new();
|
|
|
|
for entry in FILESYSTEM.lsfiles(path)? {
|
|
match FILESYSTEM.read::<Note>(&entry) {
|
|
Ok(note) => notes.push(note),
|
|
Err(err) => eprintln!("Could not parse file {path:?}: {err}"),
|
|
}
|
|
}
|
|
|
|
self.notes = notes;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
// load documents from the documents folder
|
|
fn load_documents(&mut self) -> Result<(), FsError> {
|
|
let path = Path::new("documents");
|
|
if !FILESYSTEM.exists(path) {
|
|
FILESYSTEM.mkdir(path)?;
|
|
}
|
|
let mut documents = Vec::new();
|
|
|
|
for entry in FILESYSTEM.lsfiles(path)? {
|
|
match FILESYSTEM.read::<ContentSection>(&entry) {
|
|
Ok(document) => documents.push(MainEditor::open(document)),
|
|
Err(err) => eprintln!("Could not parse file {entry:?}: {err}"),
|
|
}
|
|
}
|
|
|
|
self.documents = documents;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn load_tags(&mut self) -> Result<(), FsError> {
|
|
let path = Path::new("tags");
|
|
if !FILESYSTEM.exists(path) {
|
|
FILESYSTEM.mkdir(path)?;
|
|
}
|
|
|
|
self.tags = Tag::load_all();
|
|
Ok(())
|
|
}
|
|
}
|