fix another round of fixes of M3
This commit is contained in:
@@ -1,11 +1,13 @@
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use iced::widget::{button, column, container, row, scrollable, text, text_input, text_editor, Space};
|
||||
use iced::widget::{button, column, container, row, scrollable, text, text_input, Space};
|
||||
use iced::widget::text::{Shaping, Wrapping};
|
||||
use iced::{Color, Element, Length, Theme};
|
||||
|
||||
use bds_core::i18n::{self, UiLocale};
|
||||
use bds_core::model::{Post, PostStatus, PostTranslation};
|
||||
use bds_editor::{CodeEditor, EditorBuffer, EditorMessage, highlighter};
|
||||
|
||||
use crate::app::Message;
|
||||
use crate::components::inputs;
|
||||
@@ -37,7 +39,7 @@ pub struct PostEditorState {
|
||||
pub slug: String,
|
||||
pub excerpt: String,
|
||||
pub content: String,
|
||||
pub editor_content: text_editor::Content,
|
||||
pub editor_buffer: RefCell<EditorBuffer>,
|
||||
pub tags: Vec<String>,
|
||||
pub categories: Vec<String>,
|
||||
pub author: String,
|
||||
@@ -83,7 +85,7 @@ impl Clone for PostEditorState {
|
||||
slug: self.slug.clone(),
|
||||
excerpt: self.excerpt.clone(),
|
||||
content: self.content.clone(),
|
||||
editor_content: text_editor::Content::with_text(&self.content),
|
||||
editor_buffer: RefCell::new(EditorBuffer::new(&self.content)),
|
||||
tags: self.tags.clone(),
|
||||
categories: self.categories.clone(),
|
||||
author: self.author.clone(),
|
||||
@@ -133,7 +135,7 @@ impl PostEditorState {
|
||||
slug: post.slug.clone(),
|
||||
excerpt: post.excerpt.clone().unwrap_or_default(),
|
||||
content: content.clone(),
|
||||
editor_content: text_editor::Content::with_text(&content),
|
||||
editor_buffer: RefCell::new(EditorBuffer::new(&content)),
|
||||
tags: post.tags.clone(),
|
||||
categories: post.categories.clone(),
|
||||
author: post.author.clone().unwrap_or_default(),
|
||||
@@ -192,7 +194,7 @@ impl PostEditorState {
|
||||
self.title = saved.title;
|
||||
self.excerpt = saved.excerpt;
|
||||
self.content = saved.content.clone();
|
||||
self.editor_content = text_editor::Content::with_text(&saved.content);
|
||||
self.editor_buffer = RefCell::new(EditorBuffer::new(&saved.content));
|
||||
self.status = saved.status;
|
||||
self.is_dirty = saved.is_dirty;
|
||||
}
|
||||
@@ -201,14 +203,14 @@ impl PostEditorState {
|
||||
self.title = draft.title.clone();
|
||||
self.excerpt = draft.excerpt.clone();
|
||||
self.content = draft.content.clone();
|
||||
self.editor_content = text_editor::Content::with_text(&draft.content);
|
||||
self.editor_buffer = RefCell::new(EditorBuffer::new(&draft.content));
|
||||
self.is_dirty = draft.is_dirty;
|
||||
} else {
|
||||
// No translation yet — blank fields
|
||||
self.title = String::new();
|
||||
self.excerpt = String::new();
|
||||
self.content = String::new();
|
||||
self.editor_content = text_editor::Content::with_text("");
|
||||
self.editor_buffer = RefCell::new(EditorBuffer::new(""));
|
||||
self.is_dirty = false;
|
||||
}
|
||||
|
||||
@@ -262,7 +264,7 @@ pub enum PostEditorMsg {
|
||||
TitleChanged(String),
|
||||
SlugChanged(String),
|
||||
ExcerptChanged(String),
|
||||
ContentAction(text_editor::Action),
|
||||
ContentChanged(String),
|
||||
AuthorChanged(String),
|
||||
TemplateSlugChanged(String),
|
||||
ToggleDoNotTranslate(bool),
|
||||
@@ -461,13 +463,17 @@ pub fn view<'a>(
|
||||
};
|
||||
|
||||
// ── Content section (fills remaining space) ──
|
||||
let content_placeholder = t(locale, "editor.contentPlaceholder");
|
||||
let content_label = inputs::section_header(&t(locale, "editor.content"));
|
||||
let editor_widget = text_editor(&state.editor_content)
|
||||
.placeholder(content_placeholder)
|
||||
.on_action(|action| Message::PostEditor(PostEditorMsg::ContentAction(action)))
|
||||
.height(Length::Fill)
|
||||
.style(editor_style);
|
||||
let editor_widget: Element<'a, Message> = CodeEditor::new(
|
||||
&state.editor_buffer,
|
||||
highlighter(),
|
||||
"md",
|
||||
)
|
||||
.on_change(|msg| match msg {
|
||||
EditorMessage::ContentChanged(s) => Message::PostEditor(PostEditorMsg::ContentChanged(s)),
|
||||
EditorMessage::SaveRequested => Message::PostEditor(PostEditorMsg::Save),
|
||||
})
|
||||
.into();
|
||||
|
||||
// ── Footer ──
|
||||
let footer = row![
|
||||
@@ -505,28 +511,6 @@ pub fn view<'a>(
|
||||
.into()
|
||||
}
|
||||
|
||||
/// Dark editor style for the text_editor widget.
|
||||
fn editor_style(_theme: &Theme, status: text_editor::Status) -> text_editor::Style {
|
||||
let bg = Color::from_rgb(0.10, 0.11, 0.14);
|
||||
let border_color = match status {
|
||||
text_editor::Status::Focused => Color::from_rgb(0.30, 0.50, 0.80),
|
||||
text_editor::Status::Hovered => Color::from_rgb(0.30, 0.32, 0.40),
|
||||
_ => Color::from_rgb(0.22, 0.24, 0.30),
|
||||
};
|
||||
text_editor::Style {
|
||||
background: iced::Background::Color(bg),
|
||||
border: iced::Border {
|
||||
radius: 4.0.into(),
|
||||
width: 1.0,
|
||||
color: border_color,
|
||||
},
|
||||
icon: Color::from_rgb(0.50, 0.52, 0.58),
|
||||
placeholder: Color::from_rgb(0.40, 0.42, 0.48),
|
||||
value: Color::from_rgb(0.85, 0.87, 0.92),
|
||||
selection: Color::from_rgba(0.30, 0.50, 0.80, 0.40),
|
||||
}
|
||||
}
|
||||
|
||||
/// Chip input: shows existing chips as removable buttons + a text input to add new ones.
|
||||
fn chip_input_field<'a>(
|
||||
label: &str,
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
use iced::widget::{button, column, container, row, scrollable, text, text_input, Space};
|
||||
use std::cell::RefCell;
|
||||
|
||||
use iced::widget::{button, column, container, row, scrollable, text, Space};
|
||||
use iced::{Color, Element, Length, Theme};
|
||||
|
||||
use bds_core::i18n::UiLocale;
|
||||
use bds_core::model::{Script, ScriptKind, ScriptStatus};
|
||||
use bds_editor::{CodeEditor, EditorBuffer, EditorMessage, highlighter};
|
||||
|
||||
use crate::app::Message;
|
||||
use crate::components::inputs;
|
||||
use crate::i18n::t;
|
||||
|
||||
/// State for an open script editor.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ScriptEditorState {
|
||||
pub script_id: String,
|
||||
pub title: String,
|
||||
@@ -18,6 +20,7 @@ pub struct ScriptEditorState {
|
||||
pub entrypoint: String,
|
||||
pub enabled: bool,
|
||||
pub content: String,
|
||||
pub editor_buffer: RefCell<EditorBuffer>,
|
||||
pub status: ScriptStatus,
|
||||
pub version: i32,
|
||||
pub created_at: i64,
|
||||
@@ -27,6 +30,37 @@ pub struct ScriptEditorState {
|
||||
pub is_dirty: bool,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for ScriptEditorState {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("ScriptEditorState")
|
||||
.field("script_id", &self.script_id)
|
||||
.field("title", &self.title)
|
||||
.finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for ScriptEditorState {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
script_id: self.script_id.clone(),
|
||||
title: self.title.clone(),
|
||||
slug: self.slug.clone(),
|
||||
kind: self.kind.clone(),
|
||||
entrypoint: self.entrypoint.clone(),
|
||||
enabled: self.enabled,
|
||||
content: self.content.clone(),
|
||||
editor_buffer: RefCell::new(EditorBuffer::new(&self.content)),
|
||||
status: self.status.clone(),
|
||||
version: self.version,
|
||||
created_at: self.created_at,
|
||||
updated_at: self.updated_at,
|
||||
discovered_entrypoints: self.discovered_entrypoints.clone(),
|
||||
validation_error: self.validation_error.clone(),
|
||||
is_dirty: self.is_dirty,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ScriptEditorState {
|
||||
pub fn from_script(script: &Script) -> Self {
|
||||
let content = script.content.clone().unwrap_or_default();
|
||||
@@ -38,7 +72,8 @@ impl ScriptEditorState {
|
||||
kind: script.kind.clone(),
|
||||
entrypoint: script.entrypoint.clone(),
|
||||
enabled: script.enabled,
|
||||
content,
|
||||
content: content.clone(),
|
||||
editor_buffer: RefCell::new(EditorBuffer::new(&content)),
|
||||
status: script.status.clone(),
|
||||
version: script.version,
|
||||
created_at: script.created_at,
|
||||
@@ -157,16 +192,20 @@ pub fn view<'a>(
|
||||
.spacing(16)
|
||||
.width(Length::Fill);
|
||||
|
||||
// Content editor (placeholder — will use CodeEditor with Lua syntax)
|
||||
// Content editor (CodeEditor with Lua syntax highlighting)
|
||||
let content_section: Element<'a, Message> = column![
|
||||
inputs::section_header(&t(locale, "editor.content")),
|
||||
container(
|
||||
text_input("", &state.content)
|
||||
.on_input(|s| Message::ScriptEditor(ScriptEditorMsg::ContentChanged(s)))
|
||||
.size(14)
|
||||
)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fixed(300.0)),
|
||||
Element::from(
|
||||
CodeEditor::new(
|
||||
&state.editor_buffer,
|
||||
highlighter(),
|
||||
"lua",
|
||||
)
|
||||
.on_change(|msg| match msg {
|
||||
EditorMessage::ContentChanged(s) => Message::ScriptEditor(ScriptEditorMsg::ContentChanged(s)),
|
||||
EditorMessage::SaveRequested => Message::ScriptEditor(ScriptEditorMsg::Save),
|
||||
})
|
||||
),
|
||||
]
|
||||
.spacing(8)
|
||||
.width(Length::Fill)
|
||||
@@ -193,24 +232,31 @@ pub fn view<'a>(
|
||||
]
|
||||
.padding(8);
|
||||
|
||||
let body = scrollable(
|
||||
// Top pane: header + metadata (scrollable for overflow)
|
||||
let top_pane = scrollable(
|
||||
column![
|
||||
header,
|
||||
meta_row1,
|
||||
meta_row2,
|
||||
content_section,
|
||||
validation,
|
||||
footer,
|
||||
]
|
||||
.spacing(12)
|
||||
.padding(16)
|
||||
.width(Length::Fill)
|
||||
);
|
||||
)
|
||||
.height(Length::Shrink);
|
||||
|
||||
container(body)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.into()
|
||||
// Full layout: top pane (shrink), content (fill), validation + footer (shrink)
|
||||
column![
|
||||
top_pane,
|
||||
content_section,
|
||||
validation,
|
||||
footer,
|
||||
]
|
||||
.spacing(4)
|
||||
.padding([0, 16])
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.into()
|
||||
}
|
||||
|
||||
fn status_badge<'a>(status: &ScriptStatus) -> Element<'a, Message> {
|
||||
|
||||
@@ -154,7 +154,7 @@ fn view_cloud<'a>(state: &'a TagsViewState, locale: UiLocale) -> Element<'a, Mes
|
||||
})
|
||||
.collect();
|
||||
|
||||
let cloud = iced::widget::Column::with_children(chips).spacing(6);
|
||||
let cloud = row(chips).spacing(6).wrap();
|
||||
|
||||
scrollable(
|
||||
container(cloud)
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
use iced::widget::{button, column, container, row, scrollable, text, text_input, Space};
|
||||
use std::cell::RefCell;
|
||||
|
||||
use iced::widget::{button, column, container, row, scrollable, text, Space};
|
||||
use iced::{Color, Element, Length, Theme};
|
||||
|
||||
use bds_core::i18n::UiLocale;
|
||||
use bds_core::model::{Template, TemplateKind, TemplateStatus};
|
||||
use bds_editor::{CodeEditor, EditorBuffer, EditorMessage, highlighter};
|
||||
|
||||
use crate::app::Message;
|
||||
use crate::components::inputs;
|
||||
use crate::i18n::t;
|
||||
|
||||
/// State for an open template editor.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TemplateEditorState {
|
||||
pub template_id: String,
|
||||
pub title: String,
|
||||
@@ -17,6 +19,7 @@ pub struct TemplateEditorState {
|
||||
pub kind: TemplateKind,
|
||||
pub enabled: bool,
|
||||
pub content: String,
|
||||
pub editor_buffer: RefCell<EditorBuffer>,
|
||||
pub status: TemplateStatus,
|
||||
pub version: i32,
|
||||
pub created_at: i64,
|
||||
@@ -25,15 +28,46 @@ pub struct TemplateEditorState {
|
||||
pub is_dirty: bool,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for TemplateEditorState {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("TemplateEditorState")
|
||||
.field("template_id", &self.template_id)
|
||||
.field("title", &self.title)
|
||||
.finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for TemplateEditorState {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
template_id: self.template_id.clone(),
|
||||
title: self.title.clone(),
|
||||
slug: self.slug.clone(),
|
||||
kind: self.kind.clone(),
|
||||
enabled: self.enabled,
|
||||
content: self.content.clone(),
|
||||
editor_buffer: RefCell::new(EditorBuffer::new(&self.content)),
|
||||
status: self.status.clone(),
|
||||
version: self.version,
|
||||
created_at: self.created_at,
|
||||
updated_at: self.updated_at,
|
||||
validation_error: self.validation_error.clone(),
|
||||
is_dirty: self.is_dirty,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TemplateEditorState {
|
||||
pub fn from_template(tpl: &Template) -> Self {
|
||||
let content = tpl.content.clone().unwrap_or_default();
|
||||
Self {
|
||||
template_id: tpl.id.clone(),
|
||||
title: tpl.title.clone(),
|
||||
slug: tpl.slug.clone(),
|
||||
kind: tpl.kind.clone(),
|
||||
enabled: tpl.enabled,
|
||||
content: tpl.content.clone().unwrap_or_default(),
|
||||
content: content.clone(),
|
||||
editor_buffer: RefCell::new(EditorBuffer::new(&content)),
|
||||
status: tpl.status.clone(),
|
||||
version: tpl.version,
|
||||
created_at: tpl.created_at,
|
||||
@@ -136,16 +170,20 @@ pub fn view<'a>(
|
||||
);
|
||||
let meta_row2 = row![kind_select, enabled_check].spacing(16).width(Length::Fill);
|
||||
|
||||
// Content editor (text area placeholder — will use CodeEditor widget for liquid)
|
||||
// Content editor (CodeEditor with Liquid/HTML syntax highlighting)
|
||||
let content_section: Element<'a, Message> = column![
|
||||
inputs::section_header(&t(locale, "editor.content")),
|
||||
container(
|
||||
text_input("", &state.content)
|
||||
.on_input(|s| Message::TemplateEditor(TemplateEditorMsg::ContentChanged(s)))
|
||||
.size(14)
|
||||
)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fixed(300.0)),
|
||||
Element::from(
|
||||
CodeEditor::new(
|
||||
&state.editor_buffer,
|
||||
highlighter(),
|
||||
"liquid",
|
||||
)
|
||||
.on_change(|msg| match msg {
|
||||
EditorMessage::ContentChanged(s) => Message::TemplateEditor(TemplateEditorMsg::ContentChanged(s)),
|
||||
EditorMessage::SaveRequested => Message::TemplateEditor(TemplateEditorMsg::Save),
|
||||
})
|
||||
),
|
||||
]
|
||||
.spacing(8)
|
||||
.width(Length::Fill)
|
||||
@@ -172,24 +210,31 @@ pub fn view<'a>(
|
||||
]
|
||||
.padding(8);
|
||||
|
||||
let body = scrollable(
|
||||
// Top pane: header + metadata (scrollable for overflow)
|
||||
let top_pane = scrollable(
|
||||
column![
|
||||
header,
|
||||
meta_row1,
|
||||
meta_row2,
|
||||
content_section,
|
||||
validation,
|
||||
footer,
|
||||
]
|
||||
.spacing(12)
|
||||
.padding(16)
|
||||
.width(Length::Fill)
|
||||
);
|
||||
)
|
||||
.height(Length::Shrink);
|
||||
|
||||
container(body)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.into()
|
||||
// Full layout: top pane (shrink), content (fill), validation + footer (shrink)
|
||||
column![
|
||||
top_pane,
|
||||
content_section,
|
||||
validation,
|
||||
footer,
|
||||
]
|
||||
.spacing(4)
|
||||
.padding([0, 16])
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.into()
|
||||
}
|
||||
|
||||
fn status_badge<'a>(status: &TemplateStatus) -> Element<'a, Message> {
|
||||
|
||||
Reference in New Issue
Block a user