feat: Add 'New Scene' button to file list menu for creating linked planning+draft files
- Added create_scene_pair() method to workspace.rs - Added '+ New Scene' button in file_list.rs UI - Button creates both planning file (05-Plot/act_N/scene_XX.md) and draft file (06-First Draft/Act N/sceneXX.md) - Files are automatically linked via YAML frontmatter (draft_file and planning_template fields) - Auto-detects current act from folder context - Auto-generates next scene number - Selects the draft file after creation (ready to edit) - Includes hover text explaining the feature Generated by Mistral Vibe. Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
@@ -539,6 +539,16 @@ impl App {
|
|||||||
if ui.button("+ New").clicked() {
|
if ui.button("+ New").clicked() {
|
||||||
self.create_file();
|
self.create_file();
|
||||||
}
|
}
|
||||||
|
if ui
|
||||||
|
.button("+ New Scene")
|
||||||
|
.on_hover_text(
|
||||||
|
"Create a planning file in 05-Plot and a linked draft file \
|
||||||
|
in 06-First Draft with automatic cross-references",
|
||||||
|
)
|
||||||
|
.clicked()
|
||||||
|
{
|
||||||
|
self.create_scene_pair();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
if ui
|
if ui
|
||||||
.button("+ New from template")
|
.button("+ New from template")
|
||||||
|
|||||||
@@ -600,6 +600,323 @@ impl App {
|
|||||||
self.rebuild_field_names();
|
self.rebuild_field_names();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a scene pair: both a planning file (in 05-Plot) and a draft file
|
||||||
|
/// (in 06-First Draft), linked to each other. Used for the "New Scene" button.
|
||||||
|
pub(super) fn create_scene_pair(&mut self) {
|
||||||
|
use super::today_utc;
|
||||||
|
|
||||||
|
// Determine which folder we're in
|
||||||
|
let current_dir = self
|
||||||
|
.selected
|
||||||
|
.and_then(|i| self.files.get(i))
|
||||||
|
.map(|path| parent_dir(path).to_string())
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
// Check if we're in a First Draft folder (Act 1, Act 2, etc.)
|
||||||
|
let in_draft_folder = current_dir.starts_with("06-First Draft/Act")
|
||||||
|
|| current_dir.starts_with("06-First Draft/");
|
||||||
|
|
||||||
|
// Check if we're in a Plot folder (act_1, act_2, etc.)
|
||||||
|
let in_plot_folder = current_dir.starts_with("05-Plot/act_")
|
||||||
|
|| current_dir.starts_with("05-Plot/");
|
||||||
|
|
||||||
|
// Extract act number from current directory
|
||||||
|
let act_num = if in_draft_folder {
|
||||||
|
// Parse "06-First Draft/Act N" or "06-First Draft/Act N/"
|
||||||
|
current_dir.split('/').nth(1).and_then(|s| {
|
||||||
|
s.strip_prefix("Act ").and_then(|n| n.parse::<u32>().ok())
|
||||||
|
})
|
||||||
|
} else if in_plot_folder {
|
||||||
|
// Parse "05-Plot/act_N" or "05-Plot/act_N/"
|
||||||
|
current_dir.split('/').nth(1).and_then(|s| {
|
||||||
|
s.strip_prefix("act_").and_then(|n| n.parse::<u32>().ok())
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// Default to Act 1
|
||||||
|
Some(1)
|
||||||
|
};
|
||||||
|
|
||||||
|
let act = act_num.unwrap_or(1);
|
||||||
|
let today = today_utc();
|
||||||
|
|
||||||
|
// Find next available scene number in this act
|
||||||
|
let existing_scenes: Vec<u32> = self.files.iter()
|
||||||
|
.filter(|f| {
|
||||||
|
let path_lower = f.to_lowercase();
|
||||||
|
if in_draft_folder || in_plot_folder {
|
||||||
|
// Check files in current act
|
||||||
|
path_lower.contains(&format!("act_{}/", act))
|
||||||
|
|| path_lower.contains(&format!("Act {}/", act))
|
||||||
|
} else {
|
||||||
|
// Check both folders
|
||||||
|
path_lower.contains(&format!("act_{}/scene_", act))
|
||||||
|
|| path_lower.contains(&format!("Act {}/scene", act))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.filter_map(|f| {
|
||||||
|
// Extract scene number from filename
|
||||||
|
let filename = Path::new(f).file_name().and_then(|n| n.to_str()).unwrap_or("");
|
||||||
|
let num_str = filename.trim_start_matches("scene").trim_start_matches("_").trim_end_matches(".md");
|
||||||
|
num_str.parse::<u32>().ok()
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let scene_num = (1..=1000).find(|n| !existing_scenes.contains(n)).unwrap_or(1);
|
||||||
|
|
||||||
|
// Default chapter is 1
|
||||||
|
let chapter = 1;
|
||||||
|
|
||||||
|
// Generate scene title
|
||||||
|
let title = format!("Chapter {}: Scene {}", chapter, scene_num);
|
||||||
|
let slug = format!("chapter-{}--scene-{}", chapter, scene_num);
|
||||||
|
|
||||||
|
// Determine where to create files
|
||||||
|
let draft_dir = format!("06-First Draft/Act {}", act);
|
||||||
|
let plot_dir = format!("05-Plot/act_{:02}", act);
|
||||||
|
|
||||||
|
// Create planning file
|
||||||
|
let planning_name = format!("{}/scene_{:02}.md", plot_dir, scene_num);
|
||||||
|
let draft_name = format!("{}/scene{:02}.md", draft_dir, scene_num);
|
||||||
|
|
||||||
|
// Planning file content
|
||||||
|
let planning_id = format!("scene_{:02d}_{:02d}_{:02d}", act, chapter, scene_num);
|
||||||
|
let planning_content = format!(r#"---
|
||||||
|
id: {}
|
||||||
|
title: "{}"
|
||||||
|
slug: {}
|
||||||
|
act: {}
|
||||||
|
chapter: {}
|
||||||
|
scene: {}
|
||||||
|
type: scene
|
||||||
|
status: planning
|
||||||
|
word_count_target: 2000
|
||||||
|
word_count_min: 1800
|
||||||
|
word_count_max: 2200
|
||||||
|
pov: ""
|
||||||
|
characters: []
|
||||||
|
setting: ""
|
||||||
|
date: ""
|
||||||
|
time: ""
|
||||||
|
conflict: ""
|
||||||
|
theme: ""
|
||||||
|
tone: ""
|
||||||
|
draft_file: "../../06-First Draft/Act {}/scene{:02}.md"
|
||||||
|
order: {}
|
||||||
|
created: "{}"
|
||||||
|
updated: "{}"
|
||||||
|
---
|
||||||
|
|
||||||
|
# {}
|
||||||
|
|
||||||
|
**Planning Template** | **ID:** {} | **Status:** Planning
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Planning Metadata
|
||||||
|
|
||||||
|
| Attribute | Value |
|
||||||
|
|-----------|-------|
|
||||||
|
| **Act** | {} |
|
||||||
|
| **Chapter** | {} |
|
||||||
|
| **Scene** | {} |
|
||||||
|
| **Type** | scene |
|
||||||
|
| **Order** | {} |
|
||||||
|
| **Draft File** | [Open Draft](../../06-First Draft/Act {}/scene{:02}.md) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Creative Elements
|
||||||
|
|
||||||
|
### POV
|
||||||
|
>
|
||||||
|
|
||||||
|
### Characters
|
||||||
|
|
||||||
|
|
||||||
|
### Setting
|
||||||
|
>
|
||||||
|
|
||||||
|
### Date & Time
|
||||||
|
>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Story Structure
|
||||||
|
|
||||||
|
### Conflict
|
||||||
|
>
|
||||||
|
|
||||||
|
### Theme
|
||||||
|
>
|
||||||
|
|
||||||
|
### Tone
|
||||||
|
>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Word Count Goals
|
||||||
|
|
||||||
|
| Metric | Value |
|
||||||
|
|--------|-------|
|
||||||
|
| **Target** | 2000 |
|
||||||
|
| **Minimum** | 1800 |
|
||||||
|
| **Maximum** | 2200 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Scene Planning
|
||||||
|
|
||||||
|
### The Setup
|
||||||
|
>
|
||||||
|
|
||||||
|
### The Reveal
|
||||||
|
>
|
||||||
|
|
||||||
|
### Things to Note
|
||||||
|
-
|
||||||
|
-
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Status Tracking
|
||||||
|
|
||||||
|
- [ ] Scene outline complete
|
||||||
|
- [ ] Character arcs aligned
|
||||||
|
- [ ] Location details verified
|
||||||
|
- [ ] Conflict established
|
||||||
|
- [ ] Word count target set
|
||||||
|
- [ ] Ready for drafting
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Draft File:** [Open Draft](../../06-First Draft/Act {}/scene{:02}.md) | **Created:** {} | **Updated:** {}
|
||||||
|
"#,
|
||||||
|
planning_id, title, slug, act, chapter, scene_num,
|
||||||
|
act, scene_num, scene_num,
|
||||||
|
today, today,
|
||||||
|
title, planning_id,
|
||||||
|
act, chapter, scene_num, scene_num,
|
||||||
|
act, scene_num,
|
||||||
|
act, chapter, scene_num,
|
||||||
|
today, today
|
||||||
|
);
|
||||||
|
|
||||||
|
// Draft file content
|
||||||
|
let draft_id = format!("draft_scene_{:02d}_{:02d}_{:02d}", act, chapter, scene_num);
|
||||||
|
let draft_content = format!(r#"---
|
||||||
|
id: {}
|
||||||
|
title: "{}"
|
||||||
|
slug: {}
|
||||||
|
planning_template: "../../05-Plot/act_{:02}/scene_{:02}.md"
|
||||||
|
act: {}
|
||||||
|
chapter: {}
|
||||||
|
scene: {}
|
||||||
|
type: draft
|
||||||
|
status: not_started
|
||||||
|
word_count: 0
|
||||||
|
word_count_updated: ""
|
||||||
|
session_word_count: 0
|
||||||
|
last_session: ""
|
||||||
|
total_sessions: 0
|
||||||
|
pov: ""
|
||||||
|
version: 1
|
||||||
|
created: "{}"
|
||||||
|
updated: "{}"
|
||||||
|
---
|
||||||
|
|
||||||
|
# {}
|
||||||
|
|
||||||
|
**Draft File** | **ID:** {} | **Status:** Not Started
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Planning Reference
|
||||||
|
|
||||||
|
> **Linked to:** [Planning Template](../../05-Plot/act_{:02}/scene_{:02}.md)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Scene Metadata
|
||||||
|
|
||||||
|
| Attribute | Value |
|
||||||
|
|-----------|-------|
|
||||||
|
| **Act** | {} |
|
||||||
|
| **Chapter** | {} |
|
||||||
|
| **Scene** | {} |
|
||||||
|
| **Type** | draft |
|
||||||
|
| **POV** | Not set |
|
||||||
|
| **Version** | 1 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Word Count Tracking
|
||||||
|
|
||||||
|
| Metric | Value |
|
||||||
|
|--------|-------|
|
||||||
|
| **Target** | 2000 (from planning) |
|
||||||
|
| **Current** | 0 |
|
||||||
|
| **This Session** | 0 |
|
||||||
|
| **Last Updated** | Never |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Draft Content
|
||||||
|
|
||||||
|
### Rough Draft:
|
||||||
|
|
||||||
|
[Write your scene content here...]
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Writing Notes
|
||||||
|
|
||||||
|
### Session Log
|
||||||
|
| Date | Words Added | Notes |
|
||||||
|
|------|--------------|-------|
|
||||||
|
|
||||||
|
|
||||||
|
### Revision Notes
|
||||||
|
-
|
||||||
|
-
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Status Checklist
|
||||||
|
|
||||||
|
- [ ] First draft complete
|
||||||
|
- [ ] Word count target met
|
||||||
|
- [ ] POV consistent
|
||||||
|
- [ ] Character voices distinct
|
||||||
|
- [ ] Setting details accurate
|
||||||
|
- [ ] Conflict resolved/advanced
|
||||||
|
- [ ] Ready for revision
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Planning Template:** [Open Planning](../../05-Plot/act_{:02}/scene_{:02}.md) | **Created:** {} | **Updated:** {}
|
||||||
|
"#,
|
||||||
|
draft_id, title, slug,
|
||||||
|
act, scene_num,
|
||||||
|
act, chapter, scene_num,
|
||||||
|
today, today,
|
||||||
|
title, draft_id,
|
||||||
|
act, scene_num,
|
||||||
|
act, chapter, scene_num,
|
||||||
|
act, scene_num,
|
||||||
|
today, today
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create both files
|
||||||
|
self.insert_new_file(planning_name, planning_content);
|
||||||
|
self.insert_new_file(draft_name, draft_content);
|
||||||
|
|
||||||
|
// Select the draft file (more likely what user wants to edit)
|
||||||
|
let draft_idx = self.files.iter().position(|f| *f == draft_name).unwrap_or(0);
|
||||||
|
self.selected = Some(draft_idx);
|
||||||
|
self.buffer.clear();
|
||||||
|
self.dirty = false;
|
||||||
|
}
|
||||||
|
|
||||||
/// Write `contents` to a new file, add it to the manuscript order and select
|
/// Write `contents` to a new file, add it to the manuscript order and select
|
||||||
/// it. Shared by the plain and template-backed create paths.
|
/// it. Shared by the plain and template-backed create paths.
|
||||||
fn insert_new_file(&mut self, name: String, contents: String) {
|
fn insert_new_file(&mut self, name: String, contents: String) {
|
||||||
|
|||||||
Reference in New Issue
Block a user