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:
@@ -600,6 +600,323 @@ impl App {
|
||||
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
|
||||
/// it. Shared by the plain and template-backed create paths.
|
||||
fn insert_new_file(&mut self, name: String, contents: String) {
|
||||
|
||||
Reference in New Issue
Block a user