pinakes: import in parallel; various UI improvements
Signed-off-by: NotAShelf <raf@notashelf.dev> Change-Id: I1eb47cd79cd4145c56af966f6756fe1d6a6a6964
This commit is contained in:
parent
278bcaa4b0
commit
116fe7b059
42 changed files with 4316 additions and 316 deletions
|
|
@ -23,6 +23,10 @@ pub fn Import(
|
|||
preview_total_size: u64,
|
||||
scan_progress: Option<ScanStatusResponse>,
|
||||
#[props(default = false)] is_importing: bool,
|
||||
// Extended import state
|
||||
#[props(default)] current_file: Option<String>,
|
||||
#[props(default)] import_queue: Vec<String>,
|
||||
#[props(default = (0, 0))] import_progress: (usize, usize),
|
||||
) -> Element {
|
||||
let mut import_mode = use_signal(|| 0usize);
|
||||
let mut file_path = use_signal(String::new);
|
||||
|
|
@ -47,13 +51,45 @@ pub fn Import(
|
|||
rsx! {
|
||||
// Import status panel (shown when import is in progress)
|
||||
if is_importing {
|
||||
div { class: "import-status-panel",
|
||||
div { class: "import-status-header",
|
||||
div { class: "status-dot checking" }
|
||||
span { "Import in progress..." }
|
||||
}
|
||||
div { class: "progress-bar",
|
||||
div { class: "progress-fill indeterminate" }
|
||||
{
|
||||
let (completed, total) = import_progress;
|
||||
let has_progress = total > 0;
|
||||
let pct = if total > 0 { (completed * 100) / total } else { 0 };
|
||||
let queue_count = import_queue.len();
|
||||
rsx! {
|
||||
div { class: "import-status-panel",
|
||||
div { class: "import-status-header",
|
||||
div { class: "status-dot checking" }
|
||||
span {
|
||||
if has_progress {
|
||||
"Importing {completed}/{total}..."
|
||||
} else {
|
||||
"Import in progress..."
|
||||
}
|
||||
}
|
||||
}
|
||||
// Show current file being imported
|
||||
if let Some(ref file_name) = current_file {
|
||||
div { class: "import-current-file",
|
||||
span { class: "import-file-label", "Current: " }
|
||||
span { class: "import-file-name", "{file_name}" }
|
||||
}
|
||||
}
|
||||
// Show queue indicator
|
||||
if queue_count > 0 {
|
||||
div { class: "import-queue-indicator",
|
||||
span { class: "import-queue-badge", "{queue_count}" }
|
||||
span { class: "import-queue-text", " item(s) queued" }
|
||||
}
|
||||
}
|
||||
div { class: "progress-bar",
|
||||
if has_progress {
|
||||
div { class: "progress-fill", style: "width: {pct}%;" }
|
||||
} else {
|
||||
div { class: "progress-fill indeterminate" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -229,13 +265,13 @@ pub fn Import(
|
|||
|
||||
// Recursive toggle
|
||||
div { class: "form-group",
|
||||
label { class: "form-row",
|
||||
label { class: "checkbox-label",
|
||||
input {
|
||||
r#type: "checkbox",
|
||||
checked: *recursive.read(),
|
||||
onchange: move |_| recursive.toggle(),
|
||||
}
|
||||
span { style: "margin-left: 6px;", "Recursive (include subdirectories)" }
|
||||
span { "Recursive (include subdirectories)" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -299,9 +335,12 @@ pub fn Import(
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
div { class: "filter-bar",
|
||||
div { class: "flex-row mb-8",
|
||||
label {
|
||||
div { class: "filter-row",
|
||||
span { class: "filter-label", "Types" }
|
||||
label { class: if types_snapshot[0] { "filter-chip active" } else { "filter-chip" },
|
||||
input {
|
||||
r#type: "checkbox",
|
||||
checked: types_snapshot[0],
|
||||
|
|
@ -311,9 +350,9 @@ pub fn Import(
|
|||
filter_types.set(types);
|
||||
},
|
||||
}
|
||||
" Audio"
|
||||
"Audio"
|
||||
}
|
||||
label {
|
||||
label { class: if types_snapshot[1] { "filter-chip active" } else { "filter-chip" },
|
||||
input {
|
||||
r#type: "checkbox",
|
||||
checked: types_snapshot[1],
|
||||
|
|
@ -323,9 +362,9 @@ pub fn Import(
|
|||
filter_types.set(types);
|
||||
},
|
||||
}
|
||||
" Video"
|
||||
"Video"
|
||||
}
|
||||
label {
|
||||
label { class: if types_snapshot[2] { "filter-chip active" } else { "filter-chip" },
|
||||
input {
|
||||
r#type: "checkbox",
|
||||
checked: types_snapshot[2],
|
||||
|
|
@ -335,9 +374,9 @@ pub fn Import(
|
|||
filter_types.set(types);
|
||||
},
|
||||
}
|
||||
" Image"
|
||||
"Image"
|
||||
}
|
||||
label {
|
||||
label { class: if types_snapshot[3] { "filter-chip active" } else { "filter-chip" },
|
||||
input {
|
||||
r#type: "checkbox",
|
||||
checked: types_snapshot[3],
|
||||
|
|
@ -347,9 +386,9 @@ pub fn Import(
|
|||
filter_types.set(types);
|
||||
},
|
||||
}
|
||||
" Document"
|
||||
"Document"
|
||||
}
|
||||
label {
|
||||
label { class: if types_snapshot[4] { "filter-chip active" } else { "filter-chip" },
|
||||
input {
|
||||
r#type: "checkbox",
|
||||
checked: types_snapshot[4],
|
||||
|
|
@ -359,9 +398,9 @@ pub fn Import(
|
|||
filter_types.set(types);
|
||||
},
|
||||
}
|
||||
" Text"
|
||||
"Text"
|
||||
}
|
||||
label {
|
||||
label { class: if types_snapshot[5] { "filter-chip active" } else { "filter-chip" },
|
||||
input {
|
||||
r#type: "checkbox",
|
||||
checked: types_snapshot[5],
|
||||
|
|
@ -371,33 +410,41 @@ pub fn Import(
|
|||
filter_types.set(types);
|
||||
},
|
||||
}
|
||||
" Other"
|
||||
"Other"
|
||||
}
|
||||
}
|
||||
div { class: "flex-row",
|
||||
label { class: "form-label", "Min size (MB): " }
|
||||
input {
|
||||
r#type: "number",
|
||||
value: "{min / (1024 * 1024)}",
|
||||
oninput: move |e| {
|
||||
if let Ok(mb) = e.value().parse::<u64>() {
|
||||
filter_min_size.set(mb * 1024 * 1024);
|
||||
} else {
|
||||
filter_min_size.set(0);
|
||||
}
|
||||
},
|
||||
div { class: "size-filters",
|
||||
div { class: "size-filter-group",
|
||||
label { "Min size" }
|
||||
input {
|
||||
r#type: "number",
|
||||
placeholder: "MB",
|
||||
value: if min > 0 { format!("{}", min / (1024 * 1024)) } else { String::new() },
|
||||
oninput: move |e| {
|
||||
if let Ok(mb) = e.value().parse::<u64>() {
|
||||
filter_min_size.set(mb * 1024 * 1024);
|
||||
} else {
|
||||
filter_min_size.set(0);
|
||||
}
|
||||
},
|
||||
}
|
||||
span { class: "text-muted text-sm", "MB" }
|
||||
}
|
||||
label { class: "form-label", "Max size (MB): " }
|
||||
input {
|
||||
r#type: "number",
|
||||
value: "{max / (1024 * 1024)}",
|
||||
oninput: move |e| {
|
||||
if let Ok(mb) = e.value().parse::<u64>() {
|
||||
filter_max_size.set(mb * 1024 * 1024);
|
||||
} else {
|
||||
filter_max_size.set(0);
|
||||
}
|
||||
},
|
||||
div { class: "size-filter-group",
|
||||
label { "Max size" }
|
||||
input {
|
||||
r#type: "number",
|
||||
placeholder: "MB",
|
||||
value: if max > 0 { format!("{}", max / (1024 * 1024)) } else { String::new() },
|
||||
oninput: move |e| {
|
||||
if let Ok(mb) = e.value().parse::<u64>() {
|
||||
filter_max_size.set(mb * 1024 * 1024);
|
||||
} else {
|
||||
filter_max_size.set(0);
|
||||
}
|
||||
},
|
||||
}
|
||||
span { class: "text-muted text-sm", "MB" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -565,34 +612,46 @@ pub fn Import(
|
|||
}
|
||||
|
||||
// Import entire directory
|
||||
button {
|
||||
class: "btn btn-secondary",
|
||||
disabled: is_importing,
|
||||
onclick: {
|
||||
let mut dir_path = dir_path;
|
||||
let mut selected_tags = selected_tags;
|
||||
let mut new_tags_input = new_tags_input;
|
||||
let mut selected_collection = selected_collection;
|
||||
let mut selected_file_paths = selected_file_paths;
|
||||
move |_| {
|
||||
let path = dir_path.read().clone();
|
||||
if !path.is_empty() {
|
||||
let tag_ids = selected_tags.read().clone();
|
||||
let new_tags = parse_new_tags(&new_tags_input.read());
|
||||
let col_id = selected_collection.read().clone();
|
||||
on_import_directory.call((path, tag_ids, new_tags, col_id));
|
||||
dir_path.set(String::new());
|
||||
selected_tags.set(Vec::new());
|
||||
new_tags_input.set(String::new());
|
||||
selected_collection.set(None);
|
||||
selected_file_paths.set(HashSet::new());
|
||||
{
|
||||
let has_dir = !dir_path.read().is_empty();
|
||||
let has_preview = !preview_files.is_empty();
|
||||
let file_count = preview_files.len();
|
||||
rsx! {
|
||||
button {
|
||||
class: if has_dir { "btn btn-secondary" } else { "btn btn-secondary btn-disabled-hint" },
|
||||
disabled: is_importing || !has_dir,
|
||||
title: if !has_dir { "Select a directory first" } else { "" },
|
||||
onclick: {
|
||||
let mut dir_path = dir_path;
|
||||
let mut selected_tags = selected_tags;
|
||||
let mut new_tags_input = new_tags_input;
|
||||
let mut selected_collection = selected_collection;
|
||||
let mut selected_file_paths = selected_file_paths;
|
||||
move |_| {
|
||||
let path = dir_path.read().clone();
|
||||
if !path.is_empty() {
|
||||
let tag_ids = selected_tags.read().clone();
|
||||
let new_tags = parse_new_tags(&new_tags_input.read());
|
||||
let col_id = selected_collection.read().clone();
|
||||
on_import_directory.call((path, tag_ids, new_tags, col_id));
|
||||
dir_path.set(String::new());
|
||||
selected_tags.set(Vec::new());
|
||||
new_tags_input.set(String::new());
|
||||
selected_collection.set(None);
|
||||
selected_file_paths.set(HashSet::new());
|
||||
}
|
||||
}
|
||||
},
|
||||
if is_importing {
|
||||
"Importing..."
|
||||
} else if has_preview {
|
||||
"Import All ({file_count} files)"
|
||||
} else if has_dir {
|
||||
"Import Entire Directory"
|
||||
} else {
|
||||
"Select Directory First"
|
||||
}
|
||||
}
|
||||
},
|
||||
if is_importing {
|
||||
"Importing..."
|
||||
} else {
|
||||
"Import Entire Directory"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue