fully abstracted away filesystem with LegacyFileSystem trait. next I need to start updating to an ID based save/load system with a ModernFileSystem trait
Continuous integration / build (push) Successful in 1m35s
Continuous integration / build (push) Successful in 1m35s
This commit is contained in:
@@ -79,11 +79,31 @@ impl Note {
|
||||
|
||||
util::saved_status(ui, self.saved, &self.id, &self.name);
|
||||
|
||||
if ui.button("Save").clicked() {
|
||||
if let Err(e) = self.save() {
|
||||
eprintln!("Failed to save: {e}");
|
||||
ui.horizontal(|ui| {
|
||||
if ui.button("Save").clicked() {
|
||||
if let Err(e) = self.save() {
|
||||
eprintln!("Failed to save: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ui.button("Create Copy").clicked() {
|
||||
let new_id = uuid::Uuid::new_v4().to_string();
|
||||
let mut new_note = self.clone();
|
||||
new_note.id = new_id;
|
||||
new_note.name = format!("{} (Copy)", self.name);
|
||||
if let Err(e) = new_note.save() {
|
||||
eprintln!("Failed to save copy: {e}");
|
||||
}
|
||||
}
|
||||
|
||||
if ui.button("Delete").clicked() {
|
||||
if let Err(e) =
|
||||
FILESYSTEM.delete(Path::new(&format!("notes/{id}.json", id = self.id)))
|
||||
{
|
||||
eprintln!("Failed to delete: {e}");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let id = ui.make_persistent_id("note_name");
|
||||
egui::collapsing_header::CollapsingState::load_with_default_open(ui.ctx(), id, true)
|
||||
|
||||
+60
-104
@@ -1,7 +1,4 @@
|
||||
use itertools::Itertools;
|
||||
use std::fs::{self, DirEntry};
|
||||
|
||||
// use walkdir::{DirEntry, WalkDir};
|
||||
use std::path::Path;
|
||||
|
||||
use crate::{
|
||||
PROJECT_FOLDER, RightPanelContent,
|
||||
@@ -10,6 +7,7 @@ use crate::{
|
||||
asset_editor::Asset, content_editor::ContentSection, object_editor::ObjectInstance,
|
||||
tags::Tag, template_editor::Template,
|
||||
},
|
||||
filesystem::{FILESYSTEM, FsError, LegacyFileSystem},
|
||||
note_editor::Note,
|
||||
};
|
||||
|
||||
@@ -165,14 +163,6 @@ impl Explorer {
|
||||
});
|
||||
}
|
||||
|
||||
/// 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_doc_branch(
|
||||
ui: &mut egui::Ui,
|
||||
documents: &[ContentSection],
|
||||
@@ -241,95 +231,64 @@ impl Explorer {
|
||||
}
|
||||
|
||||
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("assets"),
|
||||
true,
|
||||
ui.make_persistent_id(&file_name),
|
||||
false,
|
||||
)
|
||||
.show_header(ui, |ui| {
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Assets");
|
||||
ui.label(file_name);
|
||||
let _clicked = ui.button("+").on_hover_text("Add new item").clicked();
|
||||
});
|
||||
})
|
||||
.body(|ui| {
|
||||
let entries = fs::read_dir(PROJECT_FOLDER.join("assets"))
|
||||
.unwrap()
|
||||
.filter_map(Result::ok)
|
||||
.sorted_by(|a, b| {
|
||||
// Directories first, then files
|
||||
let a_is_dir = a.file_type().unwrap().is_dir();
|
||||
let b_is_dir = b.file_type().unwrap().is_dir();
|
||||
if a_is_dir == b_is_dir {
|
||||
a.file_name().cmp(&b.file_name())
|
||||
} else if a_is_dir {
|
||||
std::cmp::Ordering::Less
|
||||
} else {
|
||||
std::cmp::Ordering::Greater
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
// recursive call to render the next level of documents
|
||||
for dir in dirs.iter() {
|
||||
Self::render_asset_dir(ui, to_load, dir);
|
||||
}
|
||||
|
||||
for entry in entries {
|
||||
Self::render_entry(ui, to_load, &entry);
|
||||
for file in files.iter() {
|
||||
Self::render_asset(ui, to_load, file);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn render_entry(ui: &mut egui::Ui, to_load: &mut Option<RightPanelContent>, entry: &DirEntry) {
|
||||
let file_type = entry.file_type().unwrap();
|
||||
let is_dir = file_type.is_dir();
|
||||
let file_name = entry.file_name().to_str().unwrap().to_string();
|
||||
let path = entry.path();
|
||||
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 is_dir {
|
||||
let entries = fs::read_dir(path)
|
||||
.unwrap()
|
||||
.filter_map(Result::ok)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
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 entry in entries {
|
||||
Self::render_entry(ui, to_load, &entry);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Handle file
|
||||
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)));
|
||||
}
|
||||
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)));
|
||||
}
|
||||
}
|
||||
|
||||
// load templates from the templates folder
|
||||
fn load_templates(&mut self) -> std::io::Result<()> {
|
||||
let templates_folder = PROJECT_FOLDER.join("templates");
|
||||
if !templates_folder.exists() {
|
||||
std::fs::create_dir_all(&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 std::fs::read_dir(&templates_folder).unwrap() {
|
||||
let path = entry.unwrap().path();
|
||||
match Template::load(path.file_stem().unwrap().to_str().unwrap()) {
|
||||
for entry in FILESYSTEM.lsfiles(path)? {
|
||||
match FILESYSTEM.read::<Template>(&entry) {
|
||||
Ok(t) => templates.push(t),
|
||||
Err(err) => eprintln!("Could not parse file {path:?}: {err}"),
|
||||
Err(err) => eprintln!("Could not parse file {entry:?}: {err}"),
|
||||
}
|
||||
}
|
||||
self.templates = templates;
|
||||
@@ -338,17 +297,16 @@ impl Explorer {
|
||||
}
|
||||
|
||||
// load objects from the objects folder
|
||||
fn load_objects(&mut self) -> std::io::Result<()> {
|
||||
let objects_folder = PROJECT_FOLDER.join("objects");
|
||||
if !objects_folder.exists() {
|
||||
std::fs::create_dir_all(&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 std::fs::read_dir(&objects_folder).unwrap() {
|
||||
let path = entry.unwrap().path();
|
||||
match ObjectInstance::load(path.file_stem().unwrap().to_str().unwrap()) {
|
||||
for entry in FILESYSTEM.lsfiles(path)? {
|
||||
match FILESYSTEM.read::<ObjectInstance>(&entry) {
|
||||
Ok(o) => objects.push(o),
|
||||
Err(err) => eprintln!("Could not parse file {path:?}: {err}"),
|
||||
Err(err) => eprintln!("Could not parse file {entry:?}: {err}"),
|
||||
}
|
||||
}
|
||||
self.objects = objects;
|
||||
@@ -357,16 +315,15 @@ impl Explorer {
|
||||
}
|
||||
|
||||
// load notes from the notes folder
|
||||
fn load_notes(&mut self) -> std::io::Result<()> {
|
||||
let notes_folder = PROJECT_FOLDER.join("notes");
|
||||
if !notes_folder.exists() {
|
||||
std::fs::create_dir_all(¬es_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 std::fs::read_dir(¬es_folder).unwrap() {
|
||||
let path = entry.unwrap().path();
|
||||
match Note::load(path.file_stem().unwrap().to_str().unwrap()) {
|
||||
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}"),
|
||||
}
|
||||
@@ -378,18 +335,17 @@ impl Explorer {
|
||||
}
|
||||
|
||||
// load documents from the documents folder
|
||||
fn load_documents(&mut self) -> std::io::Result<()> {
|
||||
let documents_folder = PROJECT_FOLDER.join("documents");
|
||||
if !documents_folder.exists() {
|
||||
std::fs::create_dir_all(&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 std::fs::read_dir(&documents_folder).unwrap() {
|
||||
let path = entry.unwrap().path();
|
||||
match ContentSection::load(path.file_stem().unwrap().to_str().unwrap()) {
|
||||
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 {path:?}: {err}"),
|
||||
Err(err) => eprintln!("Could not parse file {entry:?}: {err}"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -398,7 +354,7 @@ impl Explorer {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn load_tags(&mut self) -> std::io::Result<()> {
|
||||
fn load_tags(&mut self) -> Result<(), FsError> {
|
||||
self.tags = Tag::load_all();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -29,12 +29,12 @@ pub trait LegacyFileSystem {
|
||||
fn read_bytes(&self, path: &Path) -> Result<Vec<u8>, FsError>;
|
||||
fn write<T: Serialize>(&self, path: &Path, data: T) -> Result<(), FsError>;
|
||||
fn delete(&self, path: &Path) -> Result<(), FsError>;
|
||||
fn lsfiles(&self, path: &Path) -> Result<Vec<PathBuf>, FsError>;
|
||||
fn lsdirs(&self, path: &Path) -> Result<Vec<PathBuf>, FsError>;
|
||||
fn mkdir(&self, path: &Path) -> Result<(), FsError>;
|
||||
fn rename(&self, path: &Path, new_path: &Path) -> Result<(), FsError>;
|
||||
|
||||
#[allow(unused)]
|
||||
fn exists(&self, path: &Path) -> bool;
|
||||
|
||||
fn config_path(&self) -> PathBuf;
|
||||
}
|
||||
|
||||
|
||||
+22
-11
@@ -1,6 +1,3 @@
|
||||
// ────────────────────────────────────────────────────────────────
|
||||
// Imports
|
||||
// ────────────────────────────────────────────────────────────────
|
||||
use std::fs;
|
||||
use std::io::{ErrorKind, Read};
|
||||
use std::path::{Path, PathBuf};
|
||||
@@ -10,11 +7,6 @@ use serde::de::DeserializeOwned;
|
||||
|
||||
use crate::filesystem::{FsError, LegacyFileSystem};
|
||||
|
||||
// ────────────────────────────────────────────────────────────────
|
||||
// Concrete implementation
|
||||
// ────────────────────────────────────────────────────────────────
|
||||
/// The concrete file‑system. All paths are interpreted relative to
|
||||
/// `project_root`.
|
||||
pub struct NativeFileSystem {
|
||||
project_root: PathBuf,
|
||||
}
|
||||
@@ -34,9 +26,6 @@ impl NativeFileSystem {
|
||||
}
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────────────────────
|
||||
// Implementation of the trait
|
||||
// ────────────────────────────────────────────────────────────────
|
||||
impl LegacyFileSystem for NativeFileSystem {
|
||||
fn read<T: DeserializeOwned>(&self, path: &Path) -> Result<T, FsError> {
|
||||
let full_path = self.full_path(path);
|
||||
@@ -80,6 +69,28 @@ impl LegacyFileSystem for NativeFileSystem {
|
||||
fs::create_dir_all(full_path).map_err(FsError::Io)
|
||||
}
|
||||
|
||||
fn lsfiles(&self, path: &Path) -> Result<Vec<PathBuf>, FsError> {
|
||||
let full_path = self.full_path(path);
|
||||
let paths = fs::read_dir(full_path)?
|
||||
.filter_map(|res| res.ok())
|
||||
.filter(|entry| entry.file_type().unwrap().is_file())
|
||||
.map(|entry| entry.path())
|
||||
.collect();
|
||||
|
||||
Ok(paths)
|
||||
}
|
||||
|
||||
fn lsdirs(&self, path: &Path) -> Result<Vec<PathBuf>, FsError> {
|
||||
let full_path = self.full_path(path);
|
||||
let paths = fs::read_dir(full_path)?
|
||||
.filter_map(|res| res.ok())
|
||||
.filter(|entry| entry.file_type().unwrap().is_dir())
|
||||
.map(|entry| entry.path())
|
||||
.collect();
|
||||
|
||||
Ok(paths)
|
||||
}
|
||||
|
||||
fn exists(&self, path: &Path) -> bool {
|
||||
let full_path = self.full_path(path);
|
||||
full_path.exists()
|
||||
|
||||
Reference in New Issue
Block a user