fix: make code aligned with spec

This commit is contained in:
2026-04-05 12:16:57 +02:00
parent 822aded44e
commit 31fcd344ff
40 changed files with 412 additions and 64 deletions

View File

@@ -1,9 +1,11 @@
use iced::widget::{button, column, container, svg, Column, Space};
use iced::widget::{button, column, container, svg, text, tooltip, Column, Space};
use iced::widget::text::Shaping;
use iced::{Background, Border, Color, Element, Length, Theme};
use bds_core::i18n::UiLocale;
use crate::app::Message;
use crate::i18n::t;
use crate::state::navigation::SidebarView;
// ---------------------------------------------------------------------------
@@ -93,11 +95,14 @@ fn bar_background_style(_theme: &Theme) -> container::Style {
pub fn view(
active_view: SidebarView,
_locale: UiLocale,
sidebar_visible: bool,
locale: UiLocale,
) -> Element<'static, Message> {
let make_btn = |view: SidebarView| -> Element<'static, Message> {
let handle = svg::Handle::from_memory(icon_svg(view));
let is_active = view == active_view;
// Per layout.allium ActivityActiveHighlight invariant:
// button shows active iff its view == active_view AND sidebar is visible
let is_active = view == active_view && sidebar_visible;
// Render SVG: active = full opacity, inactive = 0.4 opacity (like bDS 60% opacity)
let icon = svg(handle)
@@ -117,7 +122,7 @@ pub fn view(
.style(if is_active { active_button_style } else { inactive_button_style });
// Active indicator: 2px left border (like bDS/VS Code)
if is_active {
let btn_row: Element<'static, Message> = if is_active {
let indicator = container(Space::new(0, 0))
.width(Length::Fixed(2.0))
.height(Length::Fixed(48.0))
@@ -129,7 +134,13 @@ pub fn view(
} else {
let spacer = Space::with_width(2.0);
iced::widget::row![spacer, btn].into()
}
};
// Wrap in tooltip per layout.allium ActivityButton.label_key
let tip_text = t(locale, view.i18n_key());
tooltip(btn_row, text(tip_text).size(12).shaping(Shaping::Advanced), tooltip::Position::Right)
.gap(4)
.into()
};
let top_items: Vec<Element<'static, Message>> = TOP_ACTIVITIES

View File

@@ -1,4 +1,4 @@
use iced::widget::{button, column, container, row, scrollable, text, Space};
use iced::widget::{button, column, container, scrollable, text, Space};
use iced::widget::text::Shaping;
use iced::{Alignment, Background, Border, Color, Element, Length, Theme};
@@ -67,10 +67,14 @@ pub fn view(
task_snapshots: &[TaskSnapshot],
output_entries: &[OutputEntry],
locale: UiLocale,
active_tab_is_post: bool,
active_tab_is_post_or_media: bool,
) -> Element<'static, Message> {
let muted = Color::from_rgb(0.50, 0.50, 0.55);
// Tab header
// Tab header — per layout.allium: tasks, output, post_links (only when
// active editor tab is a post), git_log (only when active tab is post or
// media).
let tasks_btn = button(text(t(locale, "common.tasks")).size(12).shaping(Shaping::Advanced))
.on_press(Message::SetPanelTab(PanelTab::Tasks))
.padding([4, 8])
@@ -86,15 +90,34 @@ pub fn view(
.padding([4, 6])
.style(close_btn_style);
let tab_header = row![
tasks_btn,
output_btn,
Space::with_width(Length::Fill),
close_btn,
]
.spacing(4)
.align_y(Alignment::Center)
.padding([4, 8]);
let mut tab_row: Vec<Element<'static, Message>> = vec![
tasks_btn.into(),
output_btn.into(),
];
if active_tab_is_post {
let post_links_btn = button(text(t(locale, "panel.postLinks")).size(12).shaping(Shaping::Advanced))
.on_press(Message::SetPanelTab(PanelTab::PostLinks))
.padding([4, 8])
.style(if panel_tab == PanelTab::PostLinks { tab_active } else { tab_inactive });
tab_row.push(post_links_btn.into());
}
if active_tab_is_post_or_media {
let git_log_btn = button(text(t(locale, "panel.gitLog")).size(12).shaping(Shaping::Advanced))
.on_press(Message::SetPanelTab(PanelTab::GitLog))
.padding([4, 8])
.style(if panel_tab == PanelTab::GitLog { tab_active } else { tab_inactive });
tab_row.push(git_log_btn.into());
}
tab_row.push(Space::with_width(Length::Fill).into());
tab_row.push(close_btn.into());
let tab_header = iced::widget::Row::with_children(tab_row)
.spacing(4)
.align_y(Alignment::Center)
.padding([4, 8]);
// Tab content
let content: Element<'static, Message> = match panel_tab {
@@ -156,6 +179,18 @@ pub fn view(
.into()
}
}
PanelTab::PostLinks => {
// Post Links content populated in M3 (editor integration)
container(text(t(locale, "panel.postLinksPlaceholder")).size(12).shaping(Shaping::Advanced).color(muted))
.padding(8)
.into()
}
PanelTab::GitLog => {
// Git Log content populated in extension bucket A (git integration)
container(text(t(locale, "panel.gitLogPlaceholder")).size(12).shaping(Shaping::Advanced).color(muted))
.padding(8)
.into()
}
};
container(

View File

@@ -91,6 +91,7 @@ pub fn view(
tab_type: TabType::Post,
title: p.title.clone(),
is_transient: true,
is_dirty: false,
}))
.padding([3, 6])
.width(Length::Fill)
@@ -123,6 +124,7 @@ pub fn view(
tab_type: TabType::Media,
title: display_name.to_string(),
is_transient: true,
is_dirty: false,
}))
.padding([3, 6])
.width(Length::Fill)
@@ -153,7 +155,7 @@ pub fn view(
.padding(12);
container(scrollable(content))
.width(Length::Fixed(240.0))
.width(Length::Fixed(280.0))
.height(Length::Fill)
.style(sidebar_style)
.into()

View File

@@ -117,6 +117,8 @@ pub fn view(
locale: UiLocale,
offline_mode: bool,
task_snapshots: &[TaskSnapshot],
theme_badge: &str,
active_post_status: Option<&str>,
) -> Element<'static, Message> {
let label_color = Color::from_rgb(0.60, 0.60, 0.65);
@@ -157,6 +159,19 @@ pub fn view(
// ── Right side ──
// Post status indicator dot (per layout.allium StatusBarRight.post_status)
let post_status_el: Element<'static, Message> = if let Some(status) = active_post_status {
let (dot, color) = match status {
"draft" => ("\u{25CB}", Color::from_rgb(0.60, 0.60, 0.65)), // hollow circle
"published" => ("\u{25CF}", Color::from_rgb(0.30, 0.75, 0.40)), // green filled
"archived" => ("\u{25A0}", Color::from_rgb(0.60, 0.60, 0.65)), // square
_ => ("\u{25CF}", Color::from_rgb(0.60, 0.60, 0.65)),
};
text(dot).size(11).shaping(Shaping::Advanced).color(color).into()
} else {
Space::with_width(0).into()
};
// Post + media counts
let posts_label = tw(locale, "statusBar.posts", &[("count", &post_count.to_string())]);
let media_label = tw(locale, "statusBar.media", &[("count", &media_count.to_string())]);
@@ -180,8 +195,10 @@ pub fn view(
.style(dropdown_trigger);
let right = row![
post_status_el,
text(posts_label).size(11).shaping(Shaping::Advanced).color(label_color),
text(media_label).size(11).shaping(Shaping::Advanced).color(label_color),
text(theme_badge.to_string()).size(11).shaping(Shaping::Advanced).color(label_color),
airplane_btn,
locale_trigger,
text("bDS").size(11).shaping(Shaping::Advanced).color(Color::from_rgb(0.45, 0.45, 0.50)),

View File

@@ -1,6 +1,6 @@
use iced::widget::{button, container, row, text, Space};
use iced::widget::text::Shaping;
use iced::{Background, Border, Color, Element, Length, Theme};
use iced::{Background, Border, Color, Element, Font, Length, Theme};
use crate::app::Message;
use crate::state::tabs::Tab;
@@ -68,28 +68,41 @@ pub fn view(
tabs: &[Tab],
active_tab: Option<&str>,
) -> Element<'static, Message> {
// Per tabs.allium: "Hidden when no tabs exist."
if tabs.is_empty() {
return container(Space::new(0, 0))
.width(Length::Fill)
.height(Length::Fixed(35.0))
.style(bar_style)
.into();
return Space::with_height(0).into();
}
let tab_buttons: Vec<Element<'static, Message>> = tabs
.iter()
.map(|tab| {
let is_active = active_tab == Some(tab.id.as_str());
let title_text = if tab.is_transient {
format!("{} \u{25CB}", tab.title) // hollow circle for transient
} else {
tab.title.clone()
};
let tab_id = tab.id.clone();
let close_id = tab.id.clone();
// Per tabs.allium: transient tabs show italic title
let title_label = if tab.is_transient {
text(tab.title.clone())
.size(12)
.shaping(Shaping::Advanced)
.font(Font { style: iced::font::Style::Italic, ..Font::DEFAULT })
} else {
text(tab.title.clone())
.size(12)
.shaping(Shaping::Advanced)
};
// Per tabs.allium DirtyIndicator: dot for dirty post tabs
let dirty_indicator = if tab.is_dirty {
text(" \u{25CF}").size(10).shaping(Shaping::Advanced)
.color(Color::from_rgb(0.90, 0.70, 0.30))
} else {
text("").size(10)
};
let label = row![
text(title_text).size(12).shaping(Shaping::Advanced),
title_label,
dirty_indicator,
button(text("\u{2715}").size(10).shaping(Shaping::Advanced))
.on_press(Message::CloseTab(close_id))
.padding(2)

View File

@@ -7,7 +7,7 @@ use bds_core::model::{Media, Post, Project};
use crate::app::Message;
use crate::state::navigation::{OutputEntry, PanelTab, SidebarView, TaskSnapshot};
use crate::state::tabs::Tab;
use crate::state::tabs::{Tab, TabType};
use crate::state::toast::Toast;
use crate::views::{activity_bar, panel, project_selector, sidebar, status_bar, tab_bar, toast, welcome};
@@ -66,13 +66,14 @@ pub fn view(
offline_mode: bool,
locale_dropdown_open: bool,
project_dropdown_open: bool,
theme_badge: &str,
// i18n
locale: UiLocale,
// Toasts
toasts: &[Toast],
) -> Element<'static, Message> {
// Activity bar (leftmost column)
let activity = activity_bar::view(sidebar_view, locale);
let activity = activity_bar::view(sidebar_view, sidebar_visible, locale);
// Tab bar
let tabs_el = tab_bar::view(tabs, active_tab);
@@ -83,8 +84,20 @@ pub fn view(
// Right column: tab bar + content + panel
let mut right_col = column![tabs_el, content_area];
if panel_visible {
// Determine active tab type for panel tab availability (per layout.allium PanelTabAvailability)
let active_tab_type = active_tab.and_then(|id| tabs.iter().find(|t| t.id == id)).map(|t| &t.tab_type);
let active_tab_is_post = active_tab_type == Some(&TabType::Post);
let active_tab_is_post_or_media = active_tab_is_post || active_tab_type == Some(&TabType::Media);
right_col = right_col.push(separator_h());
right_col = right_col.push(panel::view(panel_tab, task_snapshots, output_entries, locale));
right_col = right_col.push(panel::view(
panel_tab,
task_snapshots,
output_entries,
locale,
active_tab_is_post,
active_tab_is_post_or_media,
));
}
let right = container(right_col.width(Length::Fill).height(Length::Fill))
.width(Length::Fill)
@@ -102,7 +115,19 @@ pub fn view(
main_row = main_row.push(right);
let main_row = main_row.height(Length::Fill);
// Status bar at bottom
// Status bar at bottom — determine active post status for status dot
let active_post_status: Option<String> = active_tab.and_then(|id| {
tabs.iter().find(|t| t.id == id && t.tab_type == TabType::Post)
}).and_then(|tab| {
sidebar_posts.iter().find(|p| p.id == tab.id).map(|p| {
match p.status {
bds_core::model::PostStatus::Draft => "draft".to_string(),
bds_core::model::PostStatus::Published => "published".to_string(),
bds_core::model::PostStatus::Archived => "archived".to_string(),
}
})
});
let status = status_bar::view(
active_project_name,
post_count,
@@ -110,6 +135,8 @@ pub fn view(
locale,
offline_mode,
task_snapshots,
theme_badge,
active_post_status.as_deref(),
);
let base_layout: Element<'static, Message> = column![main_row, separator_h(), status]