Complete Clean Architecture migration
Phase 1-7: Full migration from src/app/ to Clean Architecture
BREAKING CHANGES:
- Removed src/app/ (old TEA-style implementation)
- Removed src/constant.rs (constants now local to modules)
- Removed deprecated canvas_to_image_coords functions
NEW STRUCTURE:
- src/ui/ - UI Layer (COSMIC interface)
- src/application/ - Application Layer (DocumentManager, Commands)
- src/domain/ - Domain Layer (Document types, Operations)
- src/infrastructure/ - Infrastructure Layer (Loaders, Cache, System)
FEATURES:
- DocumentManager as Single Source of Truth
- Command Pattern for all operations
- Model caching for render data (performance)
- Sync mechanism between DocumentManager and UI Model
- Wallpaper support (COSMIC, KDE, GNOME, feh)
- Thumbnail cache with disk persistence
IMPROVEMENTS:
- Warnings: 62 → 43 (-31%)
- Deprecated warnings: 2 → 0 (-100%)
- Code removed: src/app/ (~2000 lines), constant.rs, deprecated functions
- Better Locality of Reference (constants local to modules)
- Clean separation of concerns
- No circular dependencies
DOCUMENTATION:
- Updated AGENTS.md (100% migration status)
- Updated README.md (architecture section)
- Updated Workflow.md
- Added Migration-Plan.md with full completion summary
TESTS:
- All 41 tests passing
- Build successful (0 errors, 43 warnings)
- Release build verified
Migration Status: ✅ 100% Complete
This commit is contained in:
parent
f8087a3c6a
commit
fc73e4b76b
87 changed files with 9461 additions and 3324 deletions
275
src/domain/document/collection.rs
Normal file
275
src/domain/document/collection.rs
Normal file
|
|
@ -0,0 +1,275 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// src/domain/document/collection.rs
|
||||
//
|
||||
// Document collection for managing multiple documents.
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::domain::document::core::content::DocumentContent;
|
||||
|
||||
/// A collection of documents with navigation support.
|
||||
///
|
||||
/// This abstraction is useful for:
|
||||
/// - Browsing through folders of images
|
||||
/// - Batch operations on multiple documents
|
||||
/// - Comparison views (showing multiple documents side-by-side)
|
||||
#[derive(Debug)]
|
||||
pub struct DocumentCollection {
|
||||
/// List of document paths in the collection.
|
||||
paths: Vec<PathBuf>,
|
||||
/// Currently active document index.
|
||||
current_index: Option<usize>,
|
||||
/// Currently loaded document (lazy-loaded).
|
||||
current_document: Option<DocumentContent>,
|
||||
}
|
||||
|
||||
impl DocumentCollection {
|
||||
/// Create an empty collection.
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
paths: Vec::new(),
|
||||
current_index: None,
|
||||
current_document: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a collection from a list of paths.
|
||||
#[must_use]
|
||||
pub fn from_paths(paths: Vec<PathBuf>) -> Self {
|
||||
let current_index = if paths.is_empty() { None } else { Some(0) };
|
||||
|
||||
Self {
|
||||
paths,
|
||||
current_index,
|
||||
current_document: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the number of documents in the collection.
|
||||
#[must_use]
|
||||
pub fn len(&self) -> usize {
|
||||
self.paths.len()
|
||||
}
|
||||
|
||||
/// Check if the collection is empty.
|
||||
#[must_use]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.paths.is_empty()
|
||||
}
|
||||
|
||||
/// Get the current document index (0-based).
|
||||
#[must_use]
|
||||
pub fn current_index(&self) -> Option<usize> {
|
||||
self.current_index
|
||||
}
|
||||
|
||||
/// Get the current document path.
|
||||
#[must_use]
|
||||
pub fn current_path(&self) -> Option<&PathBuf> {
|
||||
self.current_index.and_then(|idx| self.paths.get(idx))
|
||||
}
|
||||
|
||||
/// Get all paths in the collection.
|
||||
#[must_use]
|
||||
pub fn paths(&self) -> &[PathBuf] {
|
||||
&self.paths
|
||||
}
|
||||
|
||||
/// Get a reference to the currently loaded document.
|
||||
#[must_use]
|
||||
pub fn current_document(&self) -> Option<&DocumentContent> {
|
||||
self.current_document.as_ref()
|
||||
}
|
||||
|
||||
/// Get a mutable reference to the currently loaded document.
|
||||
#[must_use]
|
||||
pub fn current_document_mut(&mut self) -> Option<&mut DocumentContent> {
|
||||
self.current_document.as_mut()
|
||||
}
|
||||
|
||||
/// Set the currently loaded document.
|
||||
pub fn set_current_document(&mut self, document: DocumentContent) {
|
||||
self.current_document = Some(document);
|
||||
}
|
||||
|
||||
/// Clear the currently loaded document.
|
||||
pub fn clear_current_document(&mut self) {
|
||||
self.current_document = None;
|
||||
}
|
||||
|
||||
/// Navigate to the next document in the collection.
|
||||
///
|
||||
/// Returns the new index if successful, None if already at the end.
|
||||
pub fn next(&mut self) -> Option<usize> {
|
||||
if let Some(current) = self.current_index
|
||||
&& current + 1 < self.paths.len() {
|
||||
self.current_index = Some(current + 1);
|
||||
self.current_document = None; // Clear document (needs reload)
|
||||
return self.current_index;
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Navigate to the previous document in the collection.
|
||||
///
|
||||
/// Returns the new index if successful, None if already at the start.
|
||||
pub fn previous(&mut self) -> Option<usize> {
|
||||
if let Some(current) = self.current_index
|
||||
&& current > 0 {
|
||||
self.current_index = Some(current - 1);
|
||||
self.current_document = None; // Clear document (needs reload)
|
||||
return self.current_index;
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Navigate to a specific index.
|
||||
///
|
||||
/// Returns true if the index is valid and navigation succeeded.
|
||||
pub fn goto(&mut self, index: usize) -> bool {
|
||||
if index < self.paths.len() {
|
||||
self.current_index = Some(index);
|
||||
self.current_document = None; // Clear document (needs reload)
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a document path to the collection.
|
||||
pub fn add_path(&mut self, path: PathBuf) {
|
||||
self.paths.push(path);
|
||||
if self.current_index.is_none() {
|
||||
self.current_index = Some(0);
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove a document path at the given index.
|
||||
///
|
||||
/// Returns the removed path if successful.
|
||||
pub fn remove_at(&mut self, index: usize) -> Option<PathBuf> {
|
||||
if index < self.paths.len() {
|
||||
let removed = self.paths.remove(index);
|
||||
|
||||
// Update current index if needed
|
||||
if let Some(current) = self.current_index {
|
||||
if current == index {
|
||||
// Removed current document
|
||||
self.current_document = None;
|
||||
if self.paths.is_empty() {
|
||||
self.current_index = None;
|
||||
} else if current >= self.paths.len() {
|
||||
self.current_index = Some(self.paths.len() - 1);
|
||||
}
|
||||
} else if current > index {
|
||||
// Adjust index after removal
|
||||
self.current_index = Some(current - 1);
|
||||
}
|
||||
}
|
||||
|
||||
Some(removed)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Clear the entire collection.
|
||||
pub fn clear(&mut self) {
|
||||
self.paths.clear();
|
||||
self.current_index = None;
|
||||
self.current_document = None;
|
||||
}
|
||||
|
||||
/// Check if there is a next document available.
|
||||
#[must_use]
|
||||
pub fn has_next(&self) -> bool {
|
||||
if let Some(current) = self.current_index {
|
||||
current + 1 < self.paths.len()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if there is a previous document available.
|
||||
#[must_use]
|
||||
pub fn has_previous(&self) -> bool {
|
||||
if let Some(current) = self.current_index {
|
||||
current > 0
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the path at a specific index.
|
||||
#[must_use]
|
||||
pub fn path_at(&self, index: usize) -> Option<&PathBuf> {
|
||||
self.paths.get(index)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for DocumentCollection {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_empty_collection() {
|
||||
let collection = DocumentCollection::new();
|
||||
assert!(collection.is_empty());
|
||||
assert_eq!(collection.len(), 0);
|
||||
assert_eq!(collection.current_index(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_navigation() {
|
||||
let paths = vec![
|
||||
PathBuf::from("a.png"),
|
||||
PathBuf::from("b.png"),
|
||||
PathBuf::from("c.png"),
|
||||
];
|
||||
let mut collection = DocumentCollection::from_paths(paths);
|
||||
|
||||
assert_eq!(collection.current_index(), Some(0));
|
||||
assert_eq!(collection.next(), Some(1));
|
||||
assert_eq!(collection.next(), Some(2));
|
||||
assert_eq!(collection.next(), None); // At end
|
||||
assert_eq!(collection.previous(), Some(1));
|
||||
assert_eq!(collection.previous(), Some(0));
|
||||
assert_eq!(collection.previous(), None); // At start
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_goto() {
|
||||
let paths = vec![
|
||||
PathBuf::from("a.png"),
|
||||
PathBuf::from("b.png"),
|
||||
PathBuf::from("c.png"),
|
||||
];
|
||||
let mut collection = DocumentCollection::from_paths(paths);
|
||||
|
||||
assert!(collection.goto(2));
|
||||
assert_eq!(collection.current_index(), Some(2));
|
||||
assert!(!collection.goto(10)); // Invalid index
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_remove() {
|
||||
let paths = vec![
|
||||
PathBuf::from("a.png"),
|
||||
PathBuf::from("b.png"),
|
||||
PathBuf::from("c.png"),
|
||||
];
|
||||
let mut collection = DocumentCollection::from_paths(paths);
|
||||
|
||||
collection.goto(1);
|
||||
assert_eq!(collection.remove_at(1), Some(PathBuf::from("b.png")));
|
||||
assert_eq!(collection.len(), 2);
|
||||
assert_eq!(collection.current_index(), Some(1)); // Now points to c.png
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue