Skip to content

Commit 40a8b92

Browse files
michael-borckclaude
andcommitted
Implement comprehensive import functionality for PowerPoint and Word files (task 7.5)
- Add complete import system architecture with PowerPoint (.pptx) and Word (.docx) support - Implement PowerPointParser and WordParser with XML content extraction - Create ImportService with progress tracking and session integration - Build ImportWizard component with 3-step workflow and content preview - Add import hook with file validation and progress callbacks - Integrate with backup system and session management - Support content type detection and smart mapping (slides, notes, worksheets, quizzes) - Add import button to main interface with professional UI 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 4647b4a commit 40a8b92

File tree

11 files changed

+2432
-5
lines changed

11 files changed

+2432
-5
lines changed

src-tauri/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,10 @@ markdown = "1.0"
6868
# Text processing and validation
6969
regex = "1.0"
7070
unicode-segmentation = "1.0"
71+
sha2 = "0.10"
72+
73+
# XML parsing for document import
74+
quick-xml = "0.31"
7175

7276
# Cost tracking and statistics
7377
rust_decimal = { version = "1.0", features = ["serde"] }

src-tauri/src/import/commands.rs

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
use super::*;
2+
use crate::session::SessionManager;
3+
use crate::backup::BackupService;
4+
use tauri::State;
5+
use std::sync::Arc;
6+
use tokio::sync::Mutex;
7+
use std::path::PathBuf;
8+
use anyhow::Result;
9+
10+
#[tauri::command]
11+
pub async fn get_import_config(
12+
import_service: State<'_, Arc<Mutex<ImportService>>>,
13+
) -> Result<ImportConfig, String> {
14+
let service = import_service.lock().await;
15+
Ok(service.get_config().await)
16+
}
17+
18+
#[tauri::command]
19+
pub async fn update_import_config(
20+
import_service: State<'_, Arc<Mutex<ImportService>>>,
21+
config: ImportConfig,
22+
) -> Result<(), String> {
23+
let service = import_service.lock().await;
24+
service.update_config(config).await
25+
.map_err(|e| e.to_string())
26+
}
27+
28+
#[tauri::command]
29+
pub async fn preview_import_file(
30+
import_service: State<'_, Arc<Mutex<ImportService>>>,
31+
file_path: String,
32+
) -> Result<ImportPreview, String> {
33+
let service = import_service.lock().await;
34+
let path = PathBuf::from(file_path);
35+
36+
service.preview_import(&path).await
37+
.map_err(|e| e.to_string())
38+
}
39+
40+
#[tauri::command]
41+
pub async fn import_file(
42+
import_service: State<'_, Arc<Mutex<ImportService>>>,
43+
file_path: String,
44+
settings: Option<ImportSettings>,
45+
) -> Result<ImportResult, String> {
46+
let service = import_service.lock().await;
47+
let path = PathBuf::from(file_path);
48+
49+
service.import_file(&path, settings, None).await
50+
.map_err(|e| e.to_string())
51+
}
52+
53+
#[tauri::command]
54+
pub async fn import_file_with_progress(
55+
import_service: State<'_, Arc<Mutex<ImportService>>>,
56+
file_path: String,
57+
settings: Option<ImportSettings>,
58+
window: tauri::Window,
59+
) -> Result<ImportResult, String> {
60+
let service = import_service.lock().await;
61+
let path = PathBuf::from(file_path);
62+
63+
// Create progress callback that emits events to frontend
64+
let progress_callback = Box::new(move |progress: ImportProgress| {
65+
let _ = window.emit("import-progress", &progress);
66+
});
67+
68+
service.import_file(&path, settings, Some(progress_callback)).await
69+
.map_err(|e| e.to_string())
70+
}
71+
72+
#[tauri::command]
73+
pub async fn get_supported_file_types() -> Result<Vec<SupportedFileTypeInfo>, String> {
74+
Ok(vec![
75+
SupportedFileTypeInfo {
76+
file_type: SupportedFileType::PowerPoint,
77+
display_name: "PowerPoint Presentation".to_string(),
78+
extensions: vec!["pptx".to_string()],
79+
icon: "📊".to_string(),
80+
description: "Import slides and speaker notes from PowerPoint presentations".to_string(),
81+
},
82+
SupportedFileTypeInfo {
83+
file_type: SupportedFileType::Word,
84+
display_name: "Word Document".to_string(),
85+
extensions: vec!["docx".to_string()],
86+
icon: "📝".to_string(),
87+
description: "Import content from Word documents, including lesson plans and worksheets".to_string(),
88+
},
89+
])
90+
}
91+
92+
#[tauri::command]
93+
pub async fn validate_import_file(
94+
file_path: String,
95+
) -> Result<FileValidationResult, String> {
96+
let path = PathBuf::from(&file_path);
97+
98+
// Check if file exists
99+
if !path.exists() {
100+
return Ok(FileValidationResult {
101+
is_valid: false,
102+
error_message: Some("File does not exist".to_string()),
103+
file_info: None,
104+
});
105+
}
106+
107+
// Check if it's a file
108+
if !path.is_file() {
109+
return Ok(FileValidationResult {
110+
is_valid: false,
111+
error_message: Some("Path is not a file".to_string()),
112+
file_info: None,
113+
});
114+
}
115+
116+
// Check file extension
117+
let extension = path.extension()
118+
.and_then(|ext| ext.to_str())
119+
.unwrap_or("");
120+
121+
let file_type = match SupportedFileType::from_extension(extension) {
122+
Some(ft) => ft,
123+
None => {
124+
return Ok(FileValidationResult {
125+
is_valid: false,
126+
error_message: Some(format!("Unsupported file type: .{}. Supported types: .pptx, .docx", extension)),
127+
file_info: None,
128+
});
129+
}
130+
};
131+
132+
// Get file metadata
133+
let metadata = std::fs::metadata(&path)
134+
.map_err(|e| format!("Failed to read file metadata: {}", e))?;
135+
136+
let file_info = BasicFileInfo {
137+
filename: path.file_name()
138+
.and_then(|name| name.to_str())
139+
.unwrap_or("unknown")
140+
.to_string(),
141+
file_size: metadata.len(),
142+
file_type,
143+
last_modified: metadata.modified().ok()
144+
.map(|time| chrono::DateTime::<chrono::Utc>::from(time)),
145+
};
146+
147+
// Check file size (50MB limit)
148+
const MAX_FILE_SIZE: u64 = 50 * 1024 * 1024;
149+
if metadata.len() > MAX_FILE_SIZE {
150+
return Ok(FileValidationResult {
151+
is_valid: false,
152+
error_message: Some(format!(
153+
"File size ({} MB) exceeds maximum allowed size (50 MB)",
154+
metadata.len() / (1024 * 1024)
155+
)),
156+
file_info: Some(file_info),
157+
});
158+
}
159+
160+
Ok(FileValidationResult {
161+
is_valid: true,
162+
error_message: None,
163+
file_info: Some(file_info),
164+
})
165+
}
166+
167+
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
168+
pub struct SupportedFileTypeInfo {
169+
pub file_type: SupportedFileType,
170+
pub display_name: String,
171+
pub extensions: Vec<String>,
172+
pub icon: String,
173+
pub description: String,
174+
}
175+
176+
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
177+
pub struct FileValidationResult {
178+
pub is_valid: bool,
179+
pub error_message: Option<String>,
180+
pub file_info: Option<BasicFileInfo>,
181+
}
182+
183+
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
184+
pub struct BasicFileInfo {
185+
pub filename: String,
186+
pub file_size: u64,
187+
pub file_type: SupportedFileType,
188+
pub last_modified: Option<chrono::DateTime<chrono::Utc>>,
189+
}

0 commit comments

Comments
 (0)