#![allow(dead_code)]
use std::collections::HashMap;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct BinId(pub String);
impl BinId {
pub fn new(id: impl Into<String>) -> Self {
Self(id.into())
}
}
#[derive(Debug, Clone)]
pub struct BinItem {
pub item_id: String,
pub name: String,
pub folder: bool,
pub description: Option<String>,
}
impl BinItem {
pub fn new_clip(item_id: impl Into<String>, name: impl Into<String>) -> Self {
Self {
item_id: item_id.into(),
name: name.into(),
folder: false,
description: None,
}
}
pub fn new_folder(item_id: impl Into<String>, name: impl Into<String>) -> Self {
Self {
item_id: item_id.into(),
name: name.into(),
folder: true,
description: None,
}
}
pub fn is_folder(&self) -> bool {
self.folder
}
pub fn with_description(mut self, desc: impl Into<String>) -> Self {
self.description = Some(desc.into());
self
}
}
#[derive(Debug, Clone)]
pub struct ClipBin {
pub id: BinId,
pub name: String,
pub items: Vec<BinItem>,
pub color: Option<String>,
}
impl ClipBin {
pub fn new(id: impl Into<String>, name: impl Into<String>) -> Self {
Self {
id: BinId::new(id),
name: name.into(),
items: Vec::new(),
color: None,
}
}
pub fn add_item(&mut self, item: BinItem) {
self.items.push(item);
}
pub fn move_item(&mut self, from_index: usize, to_index: usize) -> bool {
let len = self.items.len();
if from_index >= len || to_index >= len {
return false;
}
let item = self.items.remove(from_index);
self.items.insert(to_index, item);
true
}
pub fn remove(&mut self, item_id: &str) -> Option<BinItem> {
if let Some(pos) = self.items.iter().position(|i| i.item_id == item_id) {
Some(self.items.remove(pos))
} else {
None
}
}
pub fn item_count(&self) -> usize {
self.items.len()
}
pub fn set_color(&mut self, color: impl Into<String>) {
self.color = Some(color.into());
}
}
#[derive(Debug, Default)]
pub struct BinManager {
bins: HashMap<BinId, ClipBin>,
}
impl BinManager {
pub fn new() -> Self {
Self::default()
}
pub fn create_bin(&mut self, id: impl Into<String>, name: impl Into<String>) -> &ClipBin {
let bin = ClipBin::new(id, name);
let bin_id = bin.id.clone();
self.bins.insert(bin_id.clone(), bin);
self.bins
.get(&bin_id)
.expect("bin was just inserted so it must be present")
}
pub fn find_bin(&self, id: &str) -> Option<&ClipBin> {
self.bins.get(&BinId::new(id))
}
pub fn find_bin_mut(&mut self, id: &str) -> Option<&mut ClipBin> {
self.bins.get_mut(&BinId::new(id))
}
pub fn bin_count(&self) -> usize {
self.bins.len()
}
pub fn remove_bin(&mut self, id: &str) -> Option<ClipBin> {
self.bins.remove(&BinId::new(id))
}
pub fn bins(&self) -> impl Iterator<Item = &ClipBin> {
self.bins.values()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_item(id: &str, name: &str) -> BinItem {
BinItem::new_clip(id, name)
}
#[test]
fn bin_item_is_folder_false_for_clip() {
let item = BinItem::new_clip("clip-1", "Interview");
assert!(!item.is_folder());
}
#[test]
fn bin_item_is_folder_true_for_folder() {
let item = BinItem::new_folder("folder-1", "Raw Footage");
assert!(item.is_folder());
}
#[test]
fn bin_item_description() {
let item = BinItem::new_clip("c1", "Take 1").with_description("Primary angle");
assert_eq!(item.description.as_deref(), Some("Primary angle"));
}
#[test]
fn clip_bin_add_item_increments_count() {
let mut bin = ClipBin::new("b1", "Day 1");
assert_eq!(bin.item_count(), 0);
bin.add_item(make_item("c1", "Clip A"));
bin.add_item(make_item("c2", "Clip B"));
assert_eq!(bin.item_count(), 2);
}
#[test]
fn clip_bin_remove_returns_item() {
let mut bin = ClipBin::new("b1", "Bin");
bin.add_item(make_item("c1", "Alpha"));
let removed = bin.remove("c1");
assert!(removed.is_some());
assert_eq!(removed.expect("value should be valid").name, "Alpha");
assert_eq!(bin.item_count(), 0);
}
#[test]
fn clip_bin_remove_missing_returns_none() {
let mut bin = ClipBin::new("b1", "Bin");
assert!(bin.remove("nonexistent").is_none());
}
#[test]
fn clip_bin_move_item_reorders() {
let mut bin = ClipBin::new("b1", "Bin");
bin.add_item(make_item("c1", "First"));
bin.add_item(make_item("c2", "Second"));
bin.add_item(make_item("c3", "Third"));
assert!(bin.move_item(0, 2));
assert_eq!(bin.items[0].item_id, "c2");
assert_eq!(bin.items[2].item_id, "c1");
}
#[test]
fn clip_bin_move_item_out_of_range() {
let mut bin = ClipBin::new("b1", "Bin");
bin.add_item(make_item("c1", "Only"));
assert!(!bin.move_item(0, 5));
}
#[test]
fn bin_manager_create_and_find() {
let mut mgr = BinManager::new();
mgr.create_bin("bin-a", "Scene 1");
let found = mgr.find_bin("bin-a");
assert!(found.is_some());
assert_eq!(found.expect("value should be valid").name, "Scene 1");
}
#[test]
fn bin_manager_find_missing_returns_none() {
let mgr = BinManager::new();
assert!(mgr.find_bin("no-such-bin").is_none());
}
#[test]
fn bin_manager_bin_count() {
let mut mgr = BinManager::new();
assert_eq!(mgr.bin_count(), 0);
mgr.create_bin("b1", "A");
mgr.create_bin("b2", "B");
assert_eq!(mgr.bin_count(), 2);
}
#[test]
fn bin_manager_remove_bin() {
let mut mgr = BinManager::new();
mgr.create_bin("x", "X");
let removed = mgr.remove_bin("x");
assert!(removed.is_some());
assert_eq!(mgr.bin_count(), 0);
}
#[test]
fn bin_manager_find_bin_mut_allows_modification() {
let mut mgr = BinManager::new();
mgr.create_bin("b1", "Editable");
if let Some(bin) = mgr.find_bin_mut("b1") {
bin.add_item(make_item("c1", "New Clip"));
}
assert_eq!(
mgr.find_bin("b1")
.expect("find_bin should succeed")
.item_count(),
1
);
}
#[test]
fn bin_color_label() {
let mut bin = ClipBin::new("b1", "Colored");
bin.set_color("#FF0000");
assert_eq!(bin.color.as_deref(), Some("#FF0000"));
}
}