fix: loading of data from the database seems to do something
This commit is contained in:
@@ -7,7 +7,7 @@ use bds_core::db::Database;
|
||||
use bds_core::engine::task::{TaskId, TaskManager, TaskStatus};
|
||||
use bds_core::engine;
|
||||
use bds_core::i18n::{detect_os_locale, UiLocale};
|
||||
use bds_core::model::Project;
|
||||
use bds_core::model::{Post, Project};
|
||||
|
||||
use crate::i18n::{t, tw};
|
||||
use crate::platform::menu::{self, MenuAction, MenuRegistry};
|
||||
@@ -94,6 +94,13 @@ pub struct BdsApp {
|
||||
projects: Vec<Project>,
|
||||
data_dir: Option<PathBuf>,
|
||||
|
||||
// Counts
|
||||
post_count: usize,
|
||||
media_count: usize,
|
||||
|
||||
// Sidebar data
|
||||
sidebar_posts: Vec<Post>,
|
||||
|
||||
// Navigation
|
||||
sidebar_view: SidebarView,
|
||||
sidebar_visible: bool,
|
||||
@@ -215,6 +222,9 @@ impl BdsApp {
|
||||
active_project: active_project.clone(),
|
||||
projects,
|
||||
data_dir,
|
||||
post_count: 0,
|
||||
media_count: 0,
|
||||
sidebar_posts: Vec::new(),
|
||||
sidebar_view: SidebarView::Posts,
|
||||
sidebar_visible: true,
|
||||
tabs: Vec::new(),
|
||||
@@ -305,6 +315,7 @@ impl BdsApp {
|
||||
.and_then(|p| p.data_path.as_ref())
|
||||
.map(PathBuf::from);
|
||||
}
|
||||
self.refresh_counts();
|
||||
Task::none()
|
||||
}
|
||||
Message::SwitchProject(project_id) => {
|
||||
@@ -459,10 +470,14 @@ impl BdsApp {
|
||||
Message::RebuildDatabase => {
|
||||
self.spawn_engine_task(
|
||||
"engine.rebuildStarted",
|
||||
|db_path, project_id, data_dir| {
|
||||
|db_path, project_id, data_dir, tm, tid| {
|
||||
let db = Database::open(&db_path).map_err(|e| e.to_string())?;
|
||||
let report = engine::rebuild::rebuild_from_filesystem(db.conn(), &data_dir, &project_id)
|
||||
.map_err(|e| e.to_string())?;
|
||||
let on_progress: engine::rebuild::ProgressFn = Arc::new(move |pct, msg| {
|
||||
tm.report_progress(tid, Some(pct), Some(msg.to_string()));
|
||||
});
|
||||
let report = engine::rebuild::rebuild_from_filesystem_with_progress(
|
||||
db.conn(), &data_dir, &project_id, Some(on_progress),
|
||||
).map_err(|e| e.to_string())?;
|
||||
let posts = report.posts_created + report.posts_updated;
|
||||
let media = report.media_created + report.media_updated;
|
||||
let templates = report.templates_created + report.templates_updated;
|
||||
@@ -474,12 +489,14 @@ impl BdsApp {
|
||||
Message::ReindexText => {
|
||||
self.spawn_engine_task(
|
||||
"engine.reindexStarted",
|
||||
|db_path, project_id, data_dir| {
|
||||
|db_path, project_id, data_dir, tm, tid| {
|
||||
let db = Database::open(&db_path).map_err(|e| e.to_string())?;
|
||||
tm.report_progress(tid, Some(0.0), Some("Reading project config...".into()));
|
||||
let main_lang = engine::meta::read_project_json(&data_dir)
|
||||
.ok()
|
||||
.and_then(|m| m.main_language)
|
||||
.unwrap_or_else(|| "en".to_string());
|
||||
tm.report_progress(tid, Some(0.10), Some("Reindexing...".into()));
|
||||
let report = engine::search::reindex_all(db.conn(), &project_id, &main_lang)
|
||||
.map_err(|e| e.to_string())?;
|
||||
Ok(format!("posts={}, media={}", report.posts_indexed, report.media_indexed))
|
||||
@@ -489,8 +506,9 @@ impl BdsApp {
|
||||
Message::RegenerateCalendar => {
|
||||
self.spawn_engine_task(
|
||||
"engine.calendarStarted",
|
||||
|db_path, project_id, data_dir| {
|
||||
|db_path, project_id, data_dir, tm, tid| {
|
||||
let db = Database::open(&db_path).map_err(|e| e.to_string())?;
|
||||
tm.report_progress(tid, Some(0.10), Some("Generating calendar JSON...".into()));
|
||||
engine::calendar::regenerate_calendar(db.conn(), &data_dir, &project_id)
|
||||
.map_err(|e| e.to_string())?;
|
||||
Ok("done".to_string())
|
||||
@@ -501,8 +519,9 @@ impl BdsApp {
|
||||
self.open_singleton_tab(TabType::TranslationValidation, "Translation Validation");
|
||||
self.spawn_engine_task(
|
||||
"engine.validateTranslationsStarted",
|
||||
|db_path, project_id, data_dir| {
|
||||
|db_path, project_id, data_dir, tm, tid| {
|
||||
let db = Database::open(&db_path).map_err(|e| e.to_string())?;
|
||||
tm.report_progress(tid, Some(0.10), Some("Checking translations...".into()));
|
||||
let report = engine::validate_translations::validate_translations(
|
||||
db.conn(), &data_dir, &project_id,
|
||||
).map_err(|e| e.to_string())?;
|
||||
@@ -513,8 +532,9 @@ impl BdsApp {
|
||||
Message::GenerateSite => {
|
||||
self.spawn_engine_task(
|
||||
"engine.generateSiteStarted",
|
||||
|db_path, project_id, data_dir| {
|
||||
|db_path, project_id, data_dir, tm, tid| {
|
||||
let db = Database::open(&db_path).map_err(|e| e.to_string())?;
|
||||
tm.report_progress(tid, Some(0.10), Some("Generating calendar...".into()));
|
||||
engine::calendar::regenerate_calendar(db.conn(), &data_dir, &project_id)
|
||||
.map_err(|e| e.to_string())?;
|
||||
Ok("done".to_string())
|
||||
@@ -536,6 +556,7 @@ impl BdsApp {
|
||||
self.add_output(&format!("{label} failed: {err}"));
|
||||
}
|
||||
}
|
||||
self.refresh_counts();
|
||||
self.refresh_task_snapshots();
|
||||
Task::none()
|
||||
}
|
||||
@@ -561,11 +582,12 @@ impl BdsApp {
|
||||
self.panel_tab,
|
||||
&self.task_snapshots,
|
||||
&self.output_entries,
|
||||
&self.sidebar_posts,
|
||||
active_name,
|
||||
&self.projects,
|
||||
self.active_project.as_ref().map(|p| p.id.as_str()),
|
||||
0, // post_count — populated in later milestones
|
||||
0, // media_count — populated in later milestones
|
||||
self.post_count,
|
||||
self.media_count,
|
||||
self.offline_mode,
|
||||
self.locale_dropdown_open,
|
||||
self.project_dropdown_open,
|
||||
@@ -769,13 +791,18 @@ impl BdsApp {
|
||||
/// Returns `Task::none()` if no active project/db/data_dir.
|
||||
/// Otherwise registers the task, logs the start message, and returns an
|
||||
/// async `Task` that opens a fresh DB connection on a worker thread.
|
||||
///
|
||||
/// The closure receives `(db_path, project_id, data_dir, task_manager, task_id)`.
|
||||
/// Use `task_manager.report_progress(task_id, percent, message)` for live updates.
|
||||
fn spawn_engine_task<F>(
|
||||
&mut self,
|
||||
label_key: &str,
|
||||
work: F,
|
||||
) -> Task<Message>
|
||||
where
|
||||
F: FnOnce(PathBuf, String, PathBuf) -> Result<String, String> + Send + 'static,
|
||||
F: FnOnce(PathBuf, String, PathBuf, Arc<TaskManager>, TaskId) -> Result<String, String>
|
||||
+ Send
|
||||
+ 'static,
|
||||
{
|
||||
let (Some(project), Some(data_dir)) = (&self.active_project, &self.data_dir) else {
|
||||
return Task::none();
|
||||
@@ -792,10 +819,11 @@ impl BdsApp {
|
||||
self.refresh_task_snapshots();
|
||||
|
||||
let label_for_msg = label.clone();
|
||||
let tm = Arc::clone(&self.task_manager);
|
||||
|
||||
Task::perform(
|
||||
async move {
|
||||
tokio::task::spawn_blocking(move || work(db_path, project_id, data_dir))
|
||||
tokio::task::spawn_blocking(move || work(db_path, project_id, data_dir, tm, task_id))
|
||||
.await
|
||||
.unwrap_or_else(|e| Err(format!("task panicked: {e}")))
|
||||
},
|
||||
@@ -806,4 +834,24 @@ impl BdsApp {
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn refresh_counts(&mut self) {
|
||||
if let (Some(db), Some(project)) = (&self.db, &self.active_project) {
|
||||
self.post_count = bds_core::db::queries::post::count_posts_by_project(
|
||||
db.conn(),
|
||||
&project.id,
|
||||
)
|
||||
.unwrap_or(0) as usize;
|
||||
self.media_count = bds_core::db::queries::media::count_media_by_project(
|
||||
db.conn(),
|
||||
&project.id,
|
||||
)
|
||||
.unwrap_or(0) as usize;
|
||||
self.sidebar_posts = bds_core::db::queries::post::list_posts_by_project(
|
||||
db.conn(),
|
||||
&project.id,
|
||||
)
|
||||
.unwrap_or_default();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,13 +106,19 @@ pub fn view(
|
||||
let items: Vec<Element<'static, Message>> = task_snapshots
|
||||
.iter()
|
||||
.map(|snap| {
|
||||
let progress_str = snap.progress
|
||||
.map(|p| format!(" ({:.0}%)", p * 100.0))
|
||||
.unwrap_or_default();
|
||||
let phase_str = snap.message
|
||||
.as_deref()
|
||||
.map(|m| format!(" \u{2014} {m}"))
|
||||
.unwrap_or_default();
|
||||
let status_text = format!(
|
||||
"{} \u{2014} {}{}",
|
||||
"{} \u{2014} {}{}{}",
|
||||
snap.label,
|
||||
snap.status,
|
||||
snap.progress
|
||||
.map(|p| format!(" ({:.0}%)", p * 100.0))
|
||||
.unwrap_or_default(),
|
||||
progress_str,
|
||||
phase_str,
|
||||
);
|
||||
text(status_text).size(11).color(Color::from_rgb(0.70, 0.70, 0.75)).into()
|
||||
})
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
use iced::widget::{column, container, scrollable, text, Space};
|
||||
use iced::widget::{button, column, container, scrollable, text, Space};
|
||||
use iced::{Background, Border, Color, Element, Length, Theme};
|
||||
|
||||
use bds_core::i18n::UiLocale;
|
||||
use bds_core::model::Post;
|
||||
|
||||
use crate::app::Message;
|
||||
use crate::i18n::t;
|
||||
use crate::state::navigation::SidebarView;
|
||||
use crate::state::tabs::{Tab, TabType};
|
||||
|
||||
/// Sidebar container style — dark background with right border separator.
|
||||
fn sidebar_style(_theme: &Theme) -> container::Style {
|
||||
@@ -20,6 +22,20 @@ fn sidebar_style(_theme: &Theme) -> container::Style {
|
||||
}
|
||||
}
|
||||
|
||||
/// Sidebar item button style.
|
||||
fn item_style(_theme: &Theme, status: button::Status) -> button::Style {
|
||||
let bg = match status {
|
||||
button::Status::Hovered => Color::from_rgb(0.22, 0.22, 0.27),
|
||||
_ => Color::TRANSPARENT,
|
||||
};
|
||||
button::Style {
|
||||
background: Some(Background::Color(bg)),
|
||||
text_color: Color::from_rgb(0.80, 0.80, 0.85),
|
||||
border: Border::default(),
|
||||
..button::Style::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the appropriate empty-state message key for each sidebar view.
|
||||
fn placeholder_key(view: SidebarView) -> &'static str {
|
||||
match view {
|
||||
@@ -38,19 +54,63 @@ fn placeholder_key(view: SidebarView) -> &'static str {
|
||||
|
||||
pub fn view(
|
||||
sidebar_view: SidebarView,
|
||||
posts: &[Post],
|
||||
locale: UiLocale,
|
||||
) -> Element<'static, Message> {
|
||||
let header_text = t(locale, sidebar_view.i18n_key());
|
||||
let placeholder = t(locale, placeholder_key(sidebar_view));
|
||||
let muted = Color::from_rgb(0.50, 0.50, 0.55);
|
||||
|
||||
let header = text(header_text)
|
||||
.size(13)
|
||||
.color(Color::from_rgb(0.85, 0.85, 0.90));
|
||||
|
||||
let body: Element<'static, Message> = match sidebar_view {
|
||||
SidebarView::Posts => {
|
||||
if posts.is_empty() {
|
||||
text(t(locale, placeholder_key(sidebar_view)))
|
||||
.size(12)
|
||||
.color(muted)
|
||||
.into()
|
||||
} else {
|
||||
let items: Vec<Element<'static, Message>> = posts
|
||||
.iter()
|
||||
.map(|p| {
|
||||
let status_indicator = match p.status {
|
||||
bds_core::model::PostStatus::Draft => "\u{25CB} ", // ○
|
||||
bds_core::model::PostStatus::Published => "\u{25CF} ", // ●
|
||||
bds_core::model::PostStatus::Archived => "\u{25A1} ", // □
|
||||
};
|
||||
let label = format!("{status_indicator}{}", p.title);
|
||||
button(text(label).size(12))
|
||||
.on_press(Message::OpenTab(Tab {
|
||||
id: p.id.clone(),
|
||||
tab_type: TabType::Post,
|
||||
title: p.title.clone(),
|
||||
is_transient: true,
|
||||
}))
|
||||
.padding([3, 6])
|
||||
.width(Length::Fill)
|
||||
.style(item_style)
|
||||
.into()
|
||||
})
|
||||
.collect();
|
||||
iced::widget::Column::with_children(items)
|
||||
.spacing(1)
|
||||
.into()
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
text(t(locale, placeholder_key(sidebar_view)))
|
||||
.size(12)
|
||||
.color(muted)
|
||||
.into()
|
||||
}
|
||||
};
|
||||
|
||||
let content = column![
|
||||
text(header_text)
|
||||
.size(13)
|
||||
.color(Color::from_rgb(0.85, 0.85, 0.90)),
|
||||
header,
|
||||
Space::with_height(8.0),
|
||||
text(placeholder)
|
||||
.size(12)
|
||||
.color(Color::from_rgb(0.50, 0.50, 0.55)),
|
||||
body,
|
||||
]
|
||||
.spacing(4)
|
||||
.padding(12);
|
||||
|
||||
@@ -129,12 +129,24 @@ pub fn view(
|
||||
.filter(|t| t.status == "running")
|
||||
.collect();
|
||||
let task_indicator: Element<'static, Message> = if !running.is_empty() {
|
||||
let first = running[0].label.clone();
|
||||
if running.len() > 1 {
|
||||
text(format!("{first} (+{})", running.len() - 1)).size(11).color(label_color).into()
|
||||
let first = &running[0];
|
||||
let progress_str = first.progress
|
||||
.map(|p| format!(" {:.0}%", p * 100.0))
|
||||
.unwrap_or_default();
|
||||
let phase_str = first.message
|
||||
.as_deref()
|
||||
.unwrap_or("");
|
||||
let extra = if running.len() > 1 {
|
||||
format!(" (+{})", running.len() - 1)
|
||||
} else {
|
||||
text(first).size(11).color(label_color).into()
|
||||
}
|
||||
String::new()
|
||||
};
|
||||
let display = if phase_str.is_empty() {
|
||||
format!("{}{progress_str}{extra}", first.label)
|
||||
} else {
|
||||
format!("{phase_str}{progress_str}{extra}")
|
||||
};
|
||||
text(display).size(11).color(label_color).into()
|
||||
} else {
|
||||
Space::with_width(0).into()
|
||||
};
|
||||
|
||||
@@ -3,7 +3,7 @@ use iced::widget::text::Shaping;
|
||||
use iced::{Alignment, Background, Color, Element, Length, Padding, Theme};
|
||||
|
||||
use bds_core::i18n::UiLocale;
|
||||
use bds_core::model::Project;
|
||||
use bds_core::model::{Post, Project};
|
||||
|
||||
use crate::app::Message;
|
||||
use crate::state::navigation::{OutputEntry, PanelTab, SidebarView, TaskSnapshot};
|
||||
@@ -53,6 +53,8 @@ pub fn view(
|
||||
panel_tab: PanelTab,
|
||||
task_snapshots: &[TaskSnapshot],
|
||||
output_entries: &[OutputEntry],
|
||||
// Sidebar data
|
||||
sidebar_posts: &[Post],
|
||||
// Status bar
|
||||
active_project_name: Option<&str>,
|
||||
projects: &[Project],
|
||||
@@ -89,7 +91,7 @@ pub fn view(
|
||||
let mut main_row = row![activity];
|
||||
|
||||
if sidebar_visible {
|
||||
main_row = main_row.push(sidebar::view(sidebar_view, locale));
|
||||
main_row = main_row.push(sidebar::view(sidebar_view, sidebar_posts, locale));
|
||||
main_row = main_row.push(separator_v());
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user