Fix style swatches and live preview modes.
This commit is contained in:
@@ -178,7 +178,7 @@ async fn handle_style_preview(
|
|||||||
let language = escape_html(metadata.main_language.as_deref().unwrap_or("en"));
|
let language = escape_html(metadata.main_language.as_deref().unwrap_or("en"));
|
||||||
let description = escape_html(metadata.description.as_deref().unwrap_or(&metadata.name));
|
let description = escape_html(metadata.description.as_deref().unwrap_or(&metadata.name));
|
||||||
let html = format!(
|
let html = format!(
|
||||||
"<!doctype html><html lang=\"{language}\"{mode_attributes}><head><meta charset=\"utf-8\" /><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" /><title>{project_name}</title><link rel=\"stylesheet\" href=\"{stylesheet}\" /><link rel=\"stylesheet\" href=\"/assets/bds.css\" /></head><body><main class=\"container\"><nav><ul><li><strong>{project_name}</strong></li></ul></nav><article><header><h1>{project_name}</h1></header><p>{description}</p><progress value=\"70\" max=\"100\"></progress><input type=\"text\" value=\"{project_name}\" aria-label=\"{project_name}\" /><button type=\"button\">{project_name}</button></article></main></body></html>"
|
"<!doctype html><html lang=\"{language}\"{mode_attributes}><head><meta charset=\"utf-8\" /><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" /><title>{project_name}</title><link rel=\"stylesheet\" href=\"{stylesheet}\" /><link rel=\"stylesheet\" href=\"/assets/bds.css\" /></head><body><main class=\"container\"><nav><ul><li><strong>{project_name}</strong></li></ul><ul><li><button type=\"button\">{project_name}</button></li></ul></nav><article><p>{description}</p><progress value=\"70\" max=\"100\"></progress><input type=\"text\" value=\"{project_name}\" aria-label=\"{project_name}\" /></article></main></body></html>"
|
||||||
);
|
);
|
||||||
Html(html).into_response()
|
Html(html).into_response()
|
||||||
}
|
}
|
||||||
@@ -1083,14 +1083,35 @@ mod tests {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(stylesheet.status().is_success());
|
assert!(stylesheet.status().is_success());
|
||||||
let stylesheet = stylesheet.text().unwrap();
|
let stylesheet = stylesheet.text().unwrap();
|
||||||
|
let light_body = client
|
||||||
|
.get(format!(
|
||||||
|
"http://{PREVIEW_HOST}:{PREVIEW_PORT}/__style-preview?theme=amber&mode=light"
|
||||||
|
))
|
||||||
|
.send()
|
||||||
|
.unwrap()
|
||||||
|
.text()
|
||||||
|
.unwrap();
|
||||||
|
let auto_body = client
|
||||||
|
.get(format!(
|
||||||
|
"http://{PREVIEW_HOST}:{PREVIEW_PORT}/__style-preview?theme=amber&mode=auto"
|
||||||
|
))
|
||||||
|
.send()
|
||||||
|
.unwrap()
|
||||||
|
.text()
|
||||||
|
.unwrap();
|
||||||
server.stop().unwrap();
|
server.stop().unwrap();
|
||||||
|
|
||||||
assert!(body.contains("<article>"));
|
assert!(body.contains("<article>"));
|
||||||
assert!(body.contains("Blog"));
|
assert!(body.contains("Blog"));
|
||||||
|
assert!(body.find("<button").unwrap() < body.find("<article>").unwrap());
|
||||||
assert!(body.contains("href=\"/assets/pico.amber.min.css\""));
|
assert!(body.contains("href=\"/assets/pico.amber.min.css\""));
|
||||||
assert!(body.contains("data-theme=\"dark\""));
|
assert!(body.contains("data-theme=\"dark\""));
|
||||||
assert!(body.contains("data-mode=\"dark\""));
|
assert!(body.contains("data-mode=\"dark\""));
|
||||||
assert!(!body.contains("data-theme=\"amber\""));
|
assert!(!body.contains("data-theme=\"amber\""));
|
||||||
|
assert!(light_body.contains("data-theme=\"light\""));
|
||||||
|
assert!(light_body.contains("data-mode=\"light\""));
|
||||||
|
assert!(!auto_body.contains("data-theme="));
|
||||||
|
assert!(!auto_body.contains("data-mode="));
|
||||||
assert!(stylesheet.contains("--pico-primary-background:#ffbf00"));
|
assert!(stylesheet.contains("--pico-primary-background:#ffbf00"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -637,6 +637,11 @@ struct PreviewSession {
|
|||||||
struct EmbeddedPreviewState {
|
struct EmbeddedPreviewState {
|
||||||
controller: WebViewController,
|
controller: WebViewController,
|
||||||
current_url: Option<String>,
|
current_url: Option<String>,
|
||||||
|
creation_pending: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn should_start_embedded_preview_creation(active: bool, pending: bool) -> bool {
|
||||||
|
!active && !pending
|
||||||
}
|
}
|
||||||
|
|
||||||
fn persist_media_editor_state_impl(
|
fn persist_media_editor_state_impl(
|
||||||
@@ -8712,6 +8717,7 @@ impl BdsApp {
|
|||||||
self.embedded_style_preview = Some(EmbeddedPreviewState {
|
self.embedded_style_preview = Some(EmbeddedPreviewState {
|
||||||
controller: WebViewController::new(WebViewConfig::default().url(url.clone())),
|
controller: WebViewController::new(WebViewConfig::default().url(url.clone())),
|
||||||
current_url: Some(url.clone()),
|
current_url: Some(url.clone()),
|
||||||
|
creation_pending: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -8719,9 +8725,13 @@ impl BdsApp {
|
|||||||
return window::get_oldest().map(Message::MainWindowLoaded);
|
return window::get_oldest().map(Message::MainWindowLoaded);
|
||||||
};
|
};
|
||||||
if let Some(preview) = &mut self.embedded_style_preview
|
if let Some(preview) = &mut self.embedded_style_preview
|
||||||
&& !preview.controller.is_active()
|
&& should_start_embedded_preview_creation(
|
||||||
|
preview.controller.is_active(),
|
||||||
|
preview.creation_pending,
|
||||||
|
)
|
||||||
{
|
{
|
||||||
preview.controller = WebViewController::new(WebViewConfig::default().url(url));
|
preview.controller = WebViewController::new(WebViewConfig::default().url(url));
|
||||||
|
preview.creation_pending = true;
|
||||||
return preview
|
return preview
|
||||||
.controller
|
.controller
|
||||||
.create_task(window_id, Message::EmbeddedStylePreviewReady);
|
.create_task(window_id, Message::EmbeddedStylePreviewReady);
|
||||||
@@ -8762,6 +8772,7 @@ impl BdsApp {
|
|||||||
self.embedded_preview = Some(EmbeddedPreviewState {
|
self.embedded_preview = Some(EmbeddedPreviewState {
|
||||||
controller: WebViewController::new(WebViewConfig::default().url(url.clone())),
|
controller: WebViewController::new(WebViewConfig::default().url(url.clone())),
|
||||||
current_url: Some(url.clone()),
|
current_url: Some(url.clone()),
|
||||||
|
creation_pending: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -8770,9 +8781,13 @@ impl BdsApp {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if let Some(preview) = &mut self.embedded_preview
|
if let Some(preview) = &mut self.embedded_preview
|
||||||
&& !preview.controller.is_active()
|
&& should_start_embedded_preview_creation(
|
||||||
|
preview.controller.is_active(),
|
||||||
|
preview.creation_pending,
|
||||||
|
)
|
||||||
{
|
{
|
||||||
preview.controller = WebViewController::new(WebViewConfig::default().url(url));
|
preview.controller = WebViewController::new(WebViewConfig::default().url(url));
|
||||||
|
preview.creation_pending = true;
|
||||||
return preview
|
return preview
|
||||||
.controller
|
.controller
|
||||||
.create_task(window_id, Message::EmbeddedPreviewReady);
|
.create_task(window_id, Message::EmbeddedPreviewReady);
|
||||||
@@ -9426,6 +9441,7 @@ mod tests {
|
|||||||
persist_post_editor_preview_state_impl, persist_post_editor_state_impl,
|
persist_post_editor_preview_state_impl, persist_post_editor_state_impl,
|
||||||
remote_error_closes_connection, save_editor_settings_state_impl,
|
remote_error_closes_connection, save_editor_settings_state_impl,
|
||||||
save_script_editor_state_impl, save_template_editor_state_impl,
|
save_script_editor_state_impl, save_template_editor_state_impl,
|
||||||
|
should_start_embedded_preview_creation,
|
||||||
};
|
};
|
||||||
use crate::i18n::t;
|
use crate::i18n::t;
|
||||||
use crate::platform::menu::MenuAction;
|
use crate::platform::menu::MenuAction;
|
||||||
@@ -10954,6 +10970,13 @@ mod tests {
|
|||||||
assert!(!state.can_apply());
|
assert!(!state.can_apply());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn embedded_preview_creation_starts_only_when_idle() {
|
||||||
|
assert!(should_start_embedded_preview_creation(false, false));
|
||||||
|
assert!(!should_start_embedded_preview_creation(false, true));
|
||||||
|
assert!(!should_start_embedded_preview_creation(true, false));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn applying_style_persists_project_metadata_emits_event_and_updates_badge() {
|
fn applying_style_persists_project_metadata_emits_event_and_updates_badge() {
|
||||||
let (db, project, tmp) = setup();
|
let (db, project, tmp) = setup();
|
||||||
|
|||||||
@@ -11,6 +11,9 @@ impl BdsApp {
|
|||||||
])
|
])
|
||||||
}
|
}
|
||||||
Message::EmbeddedPreviewReady(result) => {
|
Message::EmbeddedPreviewReady(result) => {
|
||||||
|
if let Some(preview) = &mut self.embedded_preview {
|
||||||
|
preview.creation_pending = false;
|
||||||
|
}
|
||||||
match result {
|
match result {
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
let visible = self.active_post_uses_embedded_preview();
|
let visible = self.active_post_uses_embedded_preview();
|
||||||
@@ -29,6 +32,9 @@ impl BdsApp {
|
|||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
Message::EmbeddedStylePreviewReady(result) => {
|
Message::EmbeddedStylePreviewReady(result) => {
|
||||||
|
if let Some(preview) = &mut self.embedded_style_preview {
|
||||||
|
preview.creation_pending = false;
|
||||||
|
}
|
||||||
match result {
|
match result {
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
let visible = self.active_style_uses_embedded_preview();
|
let visible = self.active_style_uses_embedded_preview();
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use iced::widget::text::Shaping;
|
use iced::widget::text::Shaping;
|
||||||
use iced::widget::{Column, Space, button, column, container, row, scrollable, text};
|
use iced::widget::{Column, Space, button, column, container, row, scrollable, text};
|
||||||
use iced::{Background, Border, Color, Element, Length, Theme};
|
use iced::{Background, Border, Color, Element, Length, Radians, Theme, gradient};
|
||||||
|
|
||||||
use bds_core::i18n::UiLocale;
|
use bds_core::i18n::UiLocale;
|
||||||
|
|
||||||
@@ -194,11 +194,24 @@ fn theme_button_style(selected: bool, status: button::Status) -> button::Style {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn swatch(color: &'static str) -> Element<'static, Message> {
|
fn theme_accent_background(theme: &StyleTheme) -> Background {
|
||||||
container(Space::new(Length::Fill, Length::Fixed(16.0)))
|
gradient::Linear::new(Radians(3.0 * std::f32::consts::FRAC_PI_4))
|
||||||
.width(Length::Fill)
|
.add_stop(0.0, parse_hex_color(theme.accent_color))
|
||||||
|
.add_stop(1.0, parse_hex_color(theme.dark_bg_color))
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn swatch(background: Background, width_portion: u16) -> Element<'static, Message> {
|
||||||
|
container(Space::new(Length::Fill, Length::Fill))
|
||||||
|
.width(Length::FillPortion(width_portion))
|
||||||
|
.height(Length::Fixed(30.0))
|
||||||
.style(move |_: &Theme| container::Style {
|
.style(move |_: &Theme| container::Style {
|
||||||
background: Some(Background::Color(parse_hex_color(color))),
|
background: Some(background),
|
||||||
|
border: Border {
|
||||||
|
color: Color::from_rgba8(0xff, 0xff, 0xff, 0.08),
|
||||||
|
width: 1.0,
|
||||||
|
radius: 5.0.into(),
|
||||||
|
},
|
||||||
..container::Style::default()
|
..container::Style::default()
|
||||||
})
|
})
|
||||||
.into()
|
.into()
|
||||||
@@ -210,11 +223,11 @@ fn theme_button<'a>(theme: &StyleTheme, selected_theme: &str) -> Element<'a, Mes
|
|||||||
button(
|
button(
|
||||||
column![
|
column![
|
||||||
row![
|
row![
|
||||||
swatch(theme.accent_color),
|
swatch(theme_accent_background(theme), 2),
|
||||||
swatch(theme.light_bg_color),
|
swatch(Background::Color(parse_hex_color(theme.light_bg_color)), 1),
|
||||||
swatch(theme.dark_bg_color),
|
swatch(Background::Color(parse_hex_color(theme.dark_bg_color)), 1),
|
||||||
]
|
]
|
||||||
.spacing(2),
|
.spacing(4),
|
||||||
text(display_theme_name(theme.name))
|
text(display_theme_name(theme.name))
|
||||||
.size(12)
|
.size(12)
|
||||||
.shaping(Shaping::Advanced),
|
.shaping(Shaping::Advanced),
|
||||||
@@ -382,15 +395,42 @@ mod tests {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn theme_swatches_use_distinct_accent_gradients() {
|
||||||
|
let backgrounds = THEMES
|
||||||
|
.iter()
|
||||||
|
.map(theme_accent_background)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
backgrounds
|
||||||
|
.iter()
|
||||||
|
.all(|background| matches!(background, Background::Gradient(_)))
|
||||||
|
);
|
||||||
|
for (index, background) in backgrounds.iter().enumerate() {
|
||||||
|
assert!(!backgrounds[..index].contains(background));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn preview_url_tracks_selection_and_local_mode() {
|
fn preview_url_tracks_selection_and_local_mode() {
|
||||||
let mut state = StyleViewState::new(Some("blue"));
|
let mut state = StyleViewState::new(Some("blue"));
|
||||||
state.select_theme("pumpkin");
|
state.select_theme("pumpkin");
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
state.preview_url(),
|
||||||
|
"http://127.0.0.1:4123/__style-preview?theme=pumpkin&mode=auto"
|
||||||
|
);
|
||||||
state.set_preview_mode(PreviewMode::Light);
|
state.set_preview_mode(PreviewMode::Light);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
state.preview_url(),
|
state.preview_url(),
|
||||||
"http://127.0.0.1:4123/__style-preview?theme=pumpkin&mode=light"
|
"http://127.0.0.1:4123/__style-preview?theme=pumpkin&mode=light"
|
||||||
);
|
);
|
||||||
|
state.set_preview_mode(PreviewMode::Dark);
|
||||||
|
assert_eq!(
|
||||||
|
state.preview_url(),
|
||||||
|
"http://127.0.0.1:4123/__style-preview?theme=pumpkin&mode=dark"
|
||||||
|
);
|
||||||
assert_eq!(state.applied_theme, "blue");
|
assert_eq!(state.applied_theme, "blue");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user