Add nested folders to the file panel and File ▸ New project
Two features that arrived together, since the second depends on the first to show what it generates. Nested folders -------------- `App::files` now holds workspace-relative paths (`part-1/ch-03.md`) rather than bare names, and the workspace scan recurses eight levels, skipping dot-directories, `target/` and `node_modules/`. `order::tree_order` normalises the flat order so every folder's files are contiguous and each folder sits where its earliest-ordered file put it — which is what keeps the panel and the export in agreement: the export concatenates the tree read top to bottom. The panel draws a collapsible tree. Dragging within a folder reorders as before; dropping onto a folder header, or among another folder's files, moves the file on disk and carries its title override, session word baseline and cached header info with it. Emptied folders are pruned. New files take a path (`part-1/ch-01`) to create folders, and the Rename box now holds the whole relative path, so editing its folder part moves the file. File ▸ New project ------------------ Scaffolds a project from a cookiecutter template and opens its drafting subfolder (`06-First Draft`) as the workspace. `cookiecutter.rs` resolves the executable from PATH and the usual per-user Python prefixes — a desktop launcher inherits neither a conda PATH nor the tools a template's hooks shell out to, so the resolved binary's directory is prepended for the child — builds the non-interactive command line, and identifies the result by diffing the output directory, which works whatever a template names its root. Generation runs off the UI thread because hooks can reach the network. Settings ▸ New project… covers the template path, the subfolder to open, the cookiecutter path, a hooks toggle, and the Gitea credentials passed to hooks as GITEA_URL / GITEA_USER / GITEA_TOKEN. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011ZGoPiDuZ7vmryNCJWjYSD
This commit is contained in:
+42
-2
@@ -9,7 +9,7 @@ use crate::gitsync;
|
||||
use crate::odt::{self, Chapter};
|
||||
use crate::order;
|
||||
use eframe::egui;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
use std::time::{Duration, Instant};
|
||||
@@ -19,6 +19,7 @@ mod beats;
|
||||
mod editor;
|
||||
mod file_list;
|
||||
mod find;
|
||||
mod project;
|
||||
mod grammar;
|
||||
mod spelling;
|
||||
mod style;
|
||||
@@ -31,6 +32,7 @@ mod workspace;
|
||||
use self::autocomplete::*;
|
||||
use self::file_list::*;
|
||||
use self::grammar::*;
|
||||
use self::project::*;
|
||||
use self::style::*;
|
||||
use self::util::*;
|
||||
|
||||
@@ -161,8 +163,14 @@ struct IssueItem {
|
||||
|
||||
pub struct App {
|
||||
config: Config,
|
||||
/// Ordered markdown file names (relative to the workspace).
|
||||
/// Ordered markdown files, as workspace-relative paths with `/` separators
|
||||
/// (`part-1/ch-03.md`). Always held in folder-tree order — see
|
||||
/// [`crate::order::tree_order`] — so this list reads exactly as the file
|
||||
/// panel draws it and as the export concatenates it.
|
||||
files: Vec<String>,
|
||||
/// Folders the user has collapsed in the file panel, as workspace-relative
|
||||
/// paths. Purely a view concern, so it is not persisted.
|
||||
collapsed: HashSet<String>,
|
||||
/// Per-file chapter title overrides (file name -> title). Missing/empty means
|
||||
/// the title is derived automatically at export time.
|
||||
titles: HashMap<String, String>,
|
||||
@@ -276,6 +284,20 @@ pub struct App {
|
||||
beats_output: Option<String>,
|
||||
/// File stem of the proposal the beats came from, for the default save name.
|
||||
beats_source_stem: Option<String>,
|
||||
/// Whether the new-project dialog is open.
|
||||
show_new_project: bool,
|
||||
/// New-project dialog fields.
|
||||
np_name: String,
|
||||
np_author: String,
|
||||
np_description: String,
|
||||
/// Directory the project will be created in.
|
||||
np_parent: String,
|
||||
/// One-line status for the new-project dialog (validation or failure).
|
||||
np_status: String,
|
||||
/// In-flight background project generation, if any.
|
||||
np_rx: Option<std::sync::mpsc::Receiver<ProjectMsg>>,
|
||||
/// Whether the new-project settings window is open.
|
||||
show_project_settings: bool,
|
||||
}
|
||||
|
||||
impl App {
|
||||
@@ -287,6 +309,7 @@ impl App {
|
||||
export_input: config.export_path.display().to_string(),
|
||||
config,
|
||||
files: Vec::new(),
|
||||
collapsed: HashSet::new(),
|
||||
titles: HashMap::new(),
|
||||
title_input: String::new(),
|
||||
selected: None,
|
||||
@@ -340,6 +363,14 @@ impl App {
|
||||
beats_status: String::new(),
|
||||
beats_output: None,
|
||||
beats_source_stem: None,
|
||||
show_new_project: false,
|
||||
np_name: String::new(),
|
||||
np_author: String::new(),
|
||||
np_description: String::new(),
|
||||
np_parent: String::new(),
|
||||
np_status: String::new(),
|
||||
np_rx: None,
|
||||
show_project_settings: false,
|
||||
};
|
||||
app.load_spell_dict();
|
||||
app.open_workspace();
|
||||
@@ -375,6 +406,7 @@ impl eframe::App for App {
|
||||
self.poll_settings_test();
|
||||
self.poll_spell();
|
||||
self.poll_beats();
|
||||
self.poll_new_project();
|
||||
self.maybe_start_spell_check(ctx);
|
||||
|
||||
self.menu_bar(ctx);
|
||||
@@ -422,6 +454,14 @@ impl eframe::App for App {
|
||||
self.template_settings_window(ctx);
|
||||
}
|
||||
|
||||
if self.show_new_project {
|
||||
self.new_project_window(ctx);
|
||||
}
|
||||
|
||||
if self.show_project_settings {
|
||||
self.project_settings_window(ctx);
|
||||
}
|
||||
|
||||
if self.beats_output.is_some() {
|
||||
self.beats_window(ctx);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user