Implement the Pico CSS theme editor.
This commit is contained in:
@@ -18,6 +18,7 @@ pub mod settings_view;
|
||||
pub mod sidebar;
|
||||
pub mod site_validation;
|
||||
pub mod status_bar;
|
||||
pub mod style_view;
|
||||
pub mod tab_bar;
|
||||
pub mod tags_view;
|
||||
pub mod template_editor;
|
||||
|
||||
406
crates/bds-ui/src/views/style_view.rs
Normal file
406
crates/bds-ui/src/views/style_view.rs
Normal file
@@ -0,0 +1,406 @@
|
||||
use iced::widget::text::Shaping;
|
||||
use iced::widget::{Column, Space, button, column, container, row, scrollable, text};
|
||||
use iced::{Background, Border, Color, Element, Length, Theme};
|
||||
|
||||
use bds_core::i18n::UiLocale;
|
||||
|
||||
use crate::app::Message;
|
||||
use crate::components::inputs;
|
||||
use crate::i18n::t;
|
||||
|
||||
const DEFAULT_THEME: &str = "default";
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum PreviewMode {
|
||||
Auto,
|
||||
Light,
|
||||
Dark,
|
||||
}
|
||||
|
||||
impl PreviewMode {
|
||||
pub fn query_value(self) -> &'static str {
|
||||
match self {
|
||||
Self::Auto => "auto",
|
||||
Self::Light => "light",
|
||||
Self::Dark => "dark",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
struct PreviewModeOption {
|
||||
mode: PreviewMode,
|
||||
label: String,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for PreviewModeOption {
|
||||
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
formatter.write_str(&self.label)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct StyleTheme {
|
||||
pub name: &'static str,
|
||||
pub accent_color: &'static str,
|
||||
pub light_bg_color: &'static str,
|
||||
pub dark_bg_color: &'static str,
|
||||
}
|
||||
|
||||
const fn theme(name: &'static str, accent_color: &'static str) -> StyleTheme {
|
||||
StyleTheme {
|
||||
name,
|
||||
accent_color,
|
||||
light_bg_color: "#ffffff",
|
||||
dark_bg_color: "#13171f",
|
||||
}
|
||||
}
|
||||
|
||||
pub const THEMES: [StyleTheme; 20] = [
|
||||
theme("default", "#0172ad"),
|
||||
theme("amber", "#ffbf00"),
|
||||
theme("blue", "#2060df"),
|
||||
theme("cyan", "#047878"),
|
||||
theme("fuchsia", "#c1208b"),
|
||||
theme("green", "#398712"),
|
||||
theme("grey", "#ababab"),
|
||||
theme("indigo", "#524ed2"),
|
||||
theme("jade", "#007a50"),
|
||||
theme("lime", "#a5d601"),
|
||||
theme("orange", "#d24317"),
|
||||
theme("pink", "#d92662"),
|
||||
theme("pumpkin", "#ff9500"),
|
||||
theme("purple", "#9236a4"),
|
||||
theme("red", "#c52f21"),
|
||||
theme("sand", "#ccc6b4"),
|
||||
theme("slate", "#525f7a"),
|
||||
theme("violet", "#7540bf"),
|
||||
theme("yellow", "#f2df0d"),
|
||||
theme("zinc", "#646b79"),
|
||||
];
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct StyleViewState {
|
||||
pub selected_theme: String,
|
||||
pub applied_theme: String,
|
||||
pub preview_mode: PreviewMode,
|
||||
}
|
||||
|
||||
impl StyleViewState {
|
||||
pub fn new(applied_theme: Option<&str>) -> Self {
|
||||
let applied_theme = normalize_theme(applied_theme);
|
||||
Self {
|
||||
selected_theme: applied_theme.clone(),
|
||||
applied_theme,
|
||||
preview_mode: PreviewMode::Auto,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn select_theme(&mut self, theme: &str) {
|
||||
if is_supported_theme(theme) {
|
||||
self.selected_theme = theme.to_string();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_preview_mode(&mut self, mode: PreviewMode) {
|
||||
self.preview_mode = mode;
|
||||
}
|
||||
|
||||
pub fn can_apply(&self) -> bool {
|
||||
self.selected_theme != self.applied_theme
|
||||
}
|
||||
|
||||
pub fn mark_applied(&mut self) {
|
||||
self.applied_theme.clone_from(&self.selected_theme);
|
||||
}
|
||||
|
||||
pub fn refresh_applied_theme(&mut self, applied_theme: Option<&str>) {
|
||||
let preserve_pending_selection = self.can_apply();
|
||||
self.applied_theme = normalize_theme(applied_theme);
|
||||
if !preserve_pending_selection {
|
||||
self.selected_theme.clone_from(&self.applied_theme);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn preview_url(&self) -> String {
|
||||
format!(
|
||||
"http://{}:{}/__style-preview?theme={}&mode={}",
|
||||
bds_core::engine::preview::PREVIEW_HOST,
|
||||
bds_core::engine::preview::PREVIEW_PORT,
|
||||
self.selected_theme,
|
||||
self.preview_mode.query_value(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum StyleMsg {
|
||||
SelectTheme(String),
|
||||
PreviewModeChanged(PreviewMode),
|
||||
Apply,
|
||||
}
|
||||
|
||||
pub fn is_supported_theme(theme: &str) -> bool {
|
||||
THEMES.iter().any(|candidate| candidate.name == theme)
|
||||
}
|
||||
|
||||
pub fn display_theme_name(theme: &str) -> String {
|
||||
let replaced = theme.replace('-', " ");
|
||||
let mut characters = replaced.chars();
|
||||
match characters.next() {
|
||||
Some(first) => first.to_uppercase().chain(characters).collect(),
|
||||
None => String::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn normalize_theme(theme: Option<&str>) -> String {
|
||||
theme
|
||||
.filter(|theme| is_supported_theme(theme))
|
||||
.unwrap_or(DEFAULT_THEME)
|
||||
.to_string()
|
||||
}
|
||||
|
||||
fn parse_hex_color(hex: &str) -> Color {
|
||||
let bytes = hex.as_bytes();
|
||||
if bytes.len() == 7 && bytes[0] == b'#' {
|
||||
let component = |start| u8::from_str_radix(&hex[start..start + 2], 16).unwrap_or_default();
|
||||
Color::from_rgb8(component(1), component(3), component(5))
|
||||
} else {
|
||||
Color::TRANSPARENT
|
||||
}
|
||||
}
|
||||
|
||||
fn theme_button_style(selected: bool, status: button::Status) -> button::Style {
|
||||
let hovered = matches!(status, button::Status::Hovered);
|
||||
button::Style {
|
||||
background: Some(Background::Color(if selected {
|
||||
Color::from_rgb8(0x1F, 0x45, 0x63)
|
||||
} else if hovered {
|
||||
Color::from_rgb8(0x32, 0x32, 0x35)
|
||||
} else {
|
||||
Color::from_rgb8(0x24, 0x24, 0x26)
|
||||
})),
|
||||
text_color: Color::from_rgb8(0xE4, 0xE4, 0xE4),
|
||||
border: Border {
|
||||
color: if selected {
|
||||
Color::from_rgb8(0x00, 0x7F, 0xD4)
|
||||
} else {
|
||||
Color::from_rgb8(0x3C, 0x3C, 0x3C)
|
||||
},
|
||||
width: if selected { 2.0 } else { 1.0 },
|
||||
radius: 6.0.into(),
|
||||
},
|
||||
..button::Style::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn swatch(color: &'static str) -> Element<'static, Message> {
|
||||
container(Space::new(Length::Fill, Length::Fixed(16.0)))
|
||||
.width(Length::Fill)
|
||||
.style(move |_: &Theme| container::Style {
|
||||
background: Some(Background::Color(parse_hex_color(color))),
|
||||
..container::Style::default()
|
||||
})
|
||||
.into()
|
||||
}
|
||||
|
||||
fn theme_button<'a>(theme: &StyleTheme, selected_theme: &str) -> Element<'a, Message> {
|
||||
let selected = theme.name == selected_theme;
|
||||
let theme_name = theme.name.to_string();
|
||||
button(
|
||||
column![
|
||||
row![
|
||||
swatch(theme.accent_color),
|
||||
swatch(theme.light_bg_color),
|
||||
swatch(theme.dark_bg_color),
|
||||
]
|
||||
.spacing(2),
|
||||
text(display_theme_name(theme.name))
|
||||
.size(12)
|
||||
.shaping(Shaping::Advanced),
|
||||
]
|
||||
.spacing(7)
|
||||
.width(Length::Fill),
|
||||
)
|
||||
.on_press(Message::Style(StyleMsg::SelectTheme(theme_name)))
|
||||
.padding(8)
|
||||
.width(Length::FillPortion(1))
|
||||
.style(move |_: &Theme, status| theme_button_style(selected, status))
|
||||
.into()
|
||||
}
|
||||
|
||||
pub fn view<'a>(
|
||||
state: &'a StyleViewState,
|
||||
locale: UiLocale,
|
||||
preview_widget: Option<Element<'a, Message>>,
|
||||
) -> Element<'a, Message> {
|
||||
let header = inputs::card(
|
||||
column![
|
||||
text(t(locale, "style.title"))
|
||||
.size(18)
|
||||
.shaping(Shaping::Advanced),
|
||||
text(t(locale, "style.subtitle"))
|
||||
.size(12)
|
||||
.color(inputs::LABEL_COLOR)
|
||||
.shaping(Shaping::Advanced),
|
||||
]
|
||||
.spacing(4),
|
||||
);
|
||||
|
||||
let theme_rows = THEMES
|
||||
.chunks(4)
|
||||
.map(|themes| {
|
||||
let mut children = themes
|
||||
.iter()
|
||||
.map(|theme| theme_button(theme, &state.selected_theme))
|
||||
.collect::<Vec<_>>();
|
||||
while children.len() < 4 {
|
||||
children.push(Space::with_width(Length::FillPortion(1)).into());
|
||||
}
|
||||
iced::widget::Row::with_children(children)
|
||||
.spacing(8)
|
||||
.width(Length::Fill)
|
||||
.into()
|
||||
})
|
||||
.collect::<Vec<Element<'a, Message>>>();
|
||||
let picker = inputs::card(
|
||||
column![
|
||||
text(t(locale, "style.themes"))
|
||||
.size(12)
|
||||
.color(inputs::SECTION_COLOR)
|
||||
.shaping(Shaping::Advanced),
|
||||
scrollable(Column::with_children(theme_rows).spacing(8))
|
||||
.direction(scrollable::Direction::Vertical(inputs::compact_scrollbar()))
|
||||
.style(inputs::scrollable_style)
|
||||
.height(Length::Fixed(250.0)),
|
||||
]
|
||||
.spacing(8),
|
||||
);
|
||||
|
||||
let preview_modes = [
|
||||
PreviewModeOption {
|
||||
mode: PreviewMode::Auto,
|
||||
label: t(locale, "style.previewMode.auto"),
|
||||
},
|
||||
PreviewModeOption {
|
||||
mode: PreviewMode::Light,
|
||||
label: t(locale, "style.previewMode.light"),
|
||||
},
|
||||
PreviewModeOption {
|
||||
mode: PreviewMode::Dark,
|
||||
label: t(locale, "style.previewMode.dark"),
|
||||
},
|
||||
];
|
||||
let selected_mode = preview_modes
|
||||
.iter()
|
||||
.find(|option| option.mode == state.preview_mode);
|
||||
let mode_select = container(inputs::labeled_select(
|
||||
&t(locale, "style.previewMode"),
|
||||
&preview_modes,
|
||||
selected_mode,
|
||||
|option| Message::Style(StyleMsg::PreviewModeChanged(option.mode)),
|
||||
))
|
||||
.width(Length::Fixed(220.0));
|
||||
let apply_label = text(t(locale, "style.apply"))
|
||||
.size(13)
|
||||
.shaping(Shaping::Advanced);
|
||||
let apply_button: Element<'a, Message> = if state.can_apply() {
|
||||
button(apply_label)
|
||||
.on_press(Message::Style(StyleMsg::Apply))
|
||||
.padding([8, 16])
|
||||
.style(inputs::primary_button)
|
||||
.into()
|
||||
} else {
|
||||
button(apply_label)
|
||||
.padding([8, 16])
|
||||
.style(inputs::primary_button)
|
||||
.into()
|
||||
};
|
||||
let controls = inputs::toolbar(vec![mode_select.into()], vec![apply_button]);
|
||||
|
||||
let preview = preview_widget.unwrap_or_else(|| {
|
||||
container(
|
||||
text(t(locale, "tabBar.loading"))
|
||||
.size(14)
|
||||
.shaping(Shaping::Advanced),
|
||||
)
|
||||
.center_x(Length::Fill)
|
||||
.center_y(Length::Fill)
|
||||
.into()
|
||||
});
|
||||
let preview_card = inputs::card(
|
||||
column![
|
||||
text(t(locale, "style.preview"))
|
||||
.size(12)
|
||||
.color(inputs::SECTION_COLOR)
|
||||
.shaping(Shaping::Advanced),
|
||||
preview,
|
||||
]
|
||||
.spacing(8)
|
||||
.height(Length::Fill),
|
||||
)
|
||||
.height(Length::Fill);
|
||||
|
||||
container(
|
||||
column![header, picker, controls, preview_card]
|
||||
.spacing(10)
|
||||
.height(Length::Fill),
|
||||
)
|
||||
.padding(16)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.into()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn themes_match_the_allium_and_bds2_catalog() {
|
||||
assert_eq!(THEMES.len(), 20);
|
||||
assert_eq!(THEMES.first().unwrap().name, "default");
|
||||
assert_eq!(THEMES.last().unwrap().name, "zinc");
|
||||
assert_eq!(
|
||||
THEMES.iter().map(|theme| theme.name).collect::<Vec<_>>(),
|
||||
bds_core::model::SUPPORTED_PICO_THEMES.to_vec()
|
||||
);
|
||||
assert_eq!(THEMES[1].accent_color, "#ffbf00");
|
||||
assert_eq!(THEMES[2].accent_color, "#2060df");
|
||||
assert_eq!(THEMES[18].accent_color, "#f2df0d");
|
||||
assert_eq!(THEMES[19].accent_color, "#646b79");
|
||||
assert_eq!(
|
||||
THEMES
|
||||
.iter()
|
||||
.map(|theme| theme.accent_color)
|
||||
.collect::<std::collections::HashSet<_>>()
|
||||
.len(),
|
||||
THEMES.len()
|
||||
);
|
||||
assert!(THEMES.iter().all(|theme| {
|
||||
theme.light_bg_color == "#ffffff" && theme.dark_bg_color == "#13171f"
|
||||
}));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn preview_url_tracks_selection_and_local_mode() {
|
||||
let mut state = StyleViewState::new(Some("blue"));
|
||||
state.select_theme("pumpkin");
|
||||
state.set_preview_mode(PreviewMode::Light);
|
||||
assert_eq!(
|
||||
state.preview_url(),
|
||||
"http://127.0.0.1:4123/__style-preview?theme=pumpkin&mode=light"
|
||||
);
|
||||
assert_eq!(state.applied_theme, "blue");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_or_empty_applied_theme_uses_default() {
|
||||
assert_eq!(StyleViewState::new(None).applied_theme, "default");
|
||||
assert_eq!(StyleViewState::new(Some("")).applied_theme, "default");
|
||||
assert_eq!(
|
||||
StyleViewState::new(Some("unknown")).applied_theme,
|
||||
"default"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -35,36 +35,3 @@ pub fn view(locale: UiLocale) -> Element<'static, Message> {
|
||||
})
|
||||
.into()
|
||||
}
|
||||
|
||||
pub fn tab_placeholder(
|
||||
locale: UiLocale,
|
||||
title: impl Into<String>,
|
||||
subtitle: Option<String>,
|
||||
) -> Element<'static, Message> {
|
||||
let title = title.into();
|
||||
let subtitle = subtitle.unwrap_or_else(|| t(locale, "common.tabNotImplemented"));
|
||||
|
||||
container(
|
||||
column![
|
||||
text(title)
|
||||
.size(24)
|
||||
.shaping(Shaping::Advanced)
|
||||
.color(Color::from_rgb(0.85, 0.85, 0.90)),
|
||||
text(subtitle)
|
||||
.size(14)
|
||||
.shaping(Shaping::Advanced)
|
||||
.color(Color::from_rgb(0.55, 0.55, 0.60)),
|
||||
]
|
||||
.spacing(12)
|
||||
.align_x(iced::Alignment::Center),
|
||||
)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.center_x(Length::Fill)
|
||||
.center_y(Length::Fill)
|
||||
.style(|_theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(Color::from_rgb(0.11, 0.11, 0.14))),
|
||||
..container::Style::default()
|
||||
})
|
||||
.into()
|
||||
}
|
||||
|
||||
@@ -31,7 +31,9 @@ use crate::views::{
|
||||
settings_view::{self, SettingsViewState},
|
||||
sidebar,
|
||||
site_validation::{self, SiteValidationState},
|
||||
status_bar, tab_bar,
|
||||
status_bar,
|
||||
style_view::{self, StyleViewState},
|
||||
tab_bar,
|
||||
tags_view::{self, TagsViewState},
|
||||
template_editor::{self, TemplateEditorState},
|
||||
toast,
|
||||
@@ -120,6 +122,7 @@ pub fn view<'a>(
|
||||
// Data directory (for thumbnail paths)
|
||||
data_dir: Option<&'a Path>,
|
||||
post_preview_widget: Option<Element<'a, Message>>,
|
||||
style_preview_widget: Option<Element<'a, Message>>,
|
||||
// Editor states
|
||||
post_editors: &'a HashMap<String, PostEditorState>,
|
||||
media_editors: &'a HashMap<String, MediaEditorState>,
|
||||
@@ -129,6 +132,7 @@ pub fn view<'a>(
|
||||
chat_editors: &'a HashMap<String, ChatEditorState>,
|
||||
tags_view_state: Option<&'a TagsViewState>,
|
||||
settings_state: Option<&'a SettingsViewState>,
|
||||
style_view_state: Option<&'a StyleViewState>,
|
||||
dashboard_state: Option<&'a DashboardState>,
|
||||
site_validation_state: &'a SiteValidationState,
|
||||
duplicates_state: &'a DuplicatesState,
|
||||
@@ -157,6 +161,7 @@ pub fn view<'a>(
|
||||
offline_mode,
|
||||
data_dir,
|
||||
post_preview_widget,
|
||||
style_preview_widget,
|
||||
post_editors,
|
||||
media_editors,
|
||||
template_editors,
|
||||
@@ -165,6 +170,7 @@ pub fn view<'a>(
|
||||
chat_editors,
|
||||
tags_view_state,
|
||||
settings_state,
|
||||
style_view_state,
|
||||
dashboard_state,
|
||||
site_validation_state,
|
||||
duplicates_state,
|
||||
@@ -405,6 +411,7 @@ fn route_content_area<'a>(
|
||||
offline_mode: bool,
|
||||
data_dir: Option<&'a Path>,
|
||||
post_preview_widget: Option<Element<'a, Message>>,
|
||||
style_preview_widget: Option<Element<'a, Message>>,
|
||||
post_editors: &'a HashMap<String, PostEditorState>,
|
||||
media_editors: &'a HashMap<String, MediaEditorState>,
|
||||
template_editors: &'a HashMap<String, TemplateEditorState>,
|
||||
@@ -413,6 +420,7 @@ fn route_content_area<'a>(
|
||||
chat_editors: &'a HashMap<String, ChatEditorState>,
|
||||
tags_view_state: Option<&'a TagsViewState>,
|
||||
settings_state: Option<&'a SettingsViewState>,
|
||||
style_view_state: Option<&'a StyleViewState>,
|
||||
dashboard_state: Option<&'a DashboardState>,
|
||||
site_validation_state: &'a SiteValidationState,
|
||||
duplicates_state: &'a DuplicatesState,
|
||||
@@ -435,6 +443,7 @@ fn route_content_area<'a>(
|
||||
import_editors,
|
||||
tags_view_state,
|
||||
settings_state,
|
||||
style_view_state,
|
||||
dashboard_state,
|
||||
site_validation_state,
|
||||
) {
|
||||
@@ -509,6 +518,13 @@ fn route_content_area<'a>(
|
||||
loading_view(locale)
|
||||
}
|
||||
}
|
||||
ContentRoute::Style => {
|
||||
if let Some(state) = style_view_state {
|
||||
style_view::view(state, locale, style_preview_widget)
|
||||
} else {
|
||||
welcome::view(locale)
|
||||
}
|
||||
}
|
||||
ContentRoute::SiteValidation => site_validation::view(site_validation_state, locale),
|
||||
ContentRoute::FindDuplicates => duplicates::view(duplicates_state, locale),
|
||||
ContentRoute::Documentation => documentation::view(guide_documentation, locale),
|
||||
@@ -533,7 +549,6 @@ fn route_content_area<'a>(
|
||||
loading_view(locale)
|
||||
}
|
||||
}
|
||||
ContentRoute::Placeholder(title) => welcome::tab_placeholder(locale, title, None),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -550,6 +565,7 @@ enum ContentRoute<'a> {
|
||||
Chat(&'a str),
|
||||
Tags,
|
||||
Settings,
|
||||
Style,
|
||||
SiteValidation,
|
||||
FindDuplicates,
|
||||
Documentation,
|
||||
@@ -560,7 +576,6 @@ enum ContentRoute<'a> {
|
||||
MenuEditor,
|
||||
TranslationValidation,
|
||||
GitDiff(&'a str),
|
||||
Placeholder(&'a str),
|
||||
}
|
||||
|
||||
#[expect(
|
||||
@@ -577,6 +592,7 @@ fn route_kind<'a>(
|
||||
import_editors: &'a HashMap<String, crate::views::import_editor::ImportEditorState>,
|
||||
tags_view_state: Option<&'a TagsViewState>,
|
||||
settings_state: Option<&'a SettingsViewState>,
|
||||
style_view_state: Option<&'a StyleViewState>,
|
||||
dashboard_state: Option<&'a DashboardState>,
|
||||
_site_validation_state: &'a SiteValidationState,
|
||||
) -> ContentRoute<'a> {
|
||||
@@ -651,7 +667,13 @@ fn route_kind<'a>(
|
||||
TabType::CliDocumentation => ContentRoute::CliDocumentation,
|
||||
TabType::McpDocumentation => ContentRoute::McpDocumentation,
|
||||
TabType::MenuEditor => ContentRoute::MenuEditor,
|
||||
TabType::Style => ContentRoute::Placeholder(&tab.title),
|
||||
TabType::Style => {
|
||||
if style_view_state.is_some() {
|
||||
ContentRoute::Style
|
||||
} else {
|
||||
ContentRoute::Welcome
|
||||
}
|
||||
}
|
||||
TabType::TranslationValidation => ContentRoute::TranslationValidation,
|
||||
}
|
||||
}
|
||||
@@ -701,35 +723,46 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unsupported_tool_tabs_do_not_fall_back_to_welcome_route() {
|
||||
fn style_tab_routes_to_real_editor_only_with_project_state() {
|
||||
let empty_posts = HashMap::new();
|
||||
let empty_media = HashMap::new();
|
||||
let empty_templates = HashMap::new();
|
||||
let empty_scripts = HashMap::new();
|
||||
let empty_imports = HashMap::new();
|
||||
let site_validation_state = SiteValidationState::default();
|
||||
let unsupported = [TabType::Style];
|
||||
let style_state = StyleViewState::new(Some("blue"));
|
||||
let tabs = vec![tab("style", TabType::Style, "Style")];
|
||||
let route = route_kind(
|
||||
&tabs,
|
||||
Some("style"),
|
||||
&empty_posts,
|
||||
&empty_media,
|
||||
&empty_templates,
|
||||
&empty_scripts,
|
||||
&empty_imports,
|
||||
None,
|
||||
None,
|
||||
Some(&style_state),
|
||||
None,
|
||||
&site_validation_state,
|
||||
);
|
||||
assert!(matches!(route, ContentRoute::Style));
|
||||
|
||||
for tab_type in unsupported {
|
||||
let tabs = vec![tab("tool", tab_type.clone(), "Tool")];
|
||||
let route = route_kind(
|
||||
&tabs,
|
||||
Some("tool"),
|
||||
&empty_posts,
|
||||
&empty_media,
|
||||
&empty_templates,
|
||||
&empty_scripts,
|
||||
&empty_imports,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
&site_validation_state,
|
||||
);
|
||||
match route {
|
||||
ContentRoute::Placeholder(title) => assert_eq!(title, "Tool"),
|
||||
_ => panic!("expected placeholder route for {tab_type:?}"),
|
||||
}
|
||||
}
|
||||
let no_project_route = route_kind(
|
||||
&tabs,
|
||||
Some("style"),
|
||||
&empty_posts,
|
||||
&empty_media,
|
||||
&empty_templates,
|
||||
&empty_scripts,
|
||||
&empty_imports,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
&site_validation_state,
|
||||
);
|
||||
assert!(matches!(no_project_route, ContentRoute::Welcome));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -752,6 +785,7 @@ mod tests {
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
&site_validation,
|
||||
);
|
||||
assert!(matches!(route, ContentRoute::MenuEditor));
|
||||
@@ -781,6 +815,7 @@ mod tests {
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
&site_validation,
|
||||
);
|
||||
assert!(matches!(
|
||||
@@ -814,6 +849,7 @@ mod tests {
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
&site_validation,
|
||||
);
|
||||
assert!(matches!(route, ContentRoute::FindDuplicates));
|
||||
@@ -839,6 +875,7 @@ mod tests {
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
&site_validation_state,
|
||||
);
|
||||
assert!(matches!(route, ContentRoute::GitDiff("git-diff:file.txt")));
|
||||
@@ -864,6 +901,7 @@ mod tests {
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
&site_validation_state,
|
||||
);
|
||||
assert!(matches!(route, ContentRoute::Chat("conversation")));
|
||||
@@ -894,6 +932,7 @@ mod tests {
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
&site_validation_state,
|
||||
);
|
||||
|
||||
@@ -928,6 +967,7 @@ mod tests {
|
||||
&empty_imports,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
Some(&dashboard),
|
||||
&site_validation_state,
|
||||
);
|
||||
@@ -962,6 +1002,7 @@ mod tests {
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
&site_validation_state,
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user