mirror of
https://github.com/ZSeven-W/openpencil.git
synced 2026-06-01 03:14:29 +07:00
fix(desktop): retry generated image placeholders
This commit is contained in:
parent
554184281c
commit
621e71bc39
2 changed files with 100 additions and 14 deletions
|
|
@ -5,6 +5,7 @@ use std::sync::mpsc::{self, Receiver, TryRecvError};
|
|||
use std::time::Duration;
|
||||
|
||||
use jian_ops_schema::node::{PenNode, TextContent};
|
||||
use jian_ops_schema::sizing::{SizingBehavior, SizingKeyword};
|
||||
use jian_ops_schema::style::{ImageFillBody, ImageFillMode, PenFill};
|
||||
use op_editor_core::{walkers, EditorState, NodeId, PenNodeExt as _};
|
||||
|
||||
|
|
@ -88,9 +89,14 @@ impl ImageSearchSession {
|
|||
let job = self.jobs.swap_remove(i);
|
||||
let id = job.node_id.as_str().to_string();
|
||||
self.in_flight.remove(&id);
|
||||
self.completed.insert(id);
|
||||
if let Some(url) = url {
|
||||
changed |= apply_result(state, &job.node_id, &url);
|
||||
if apply_result(state, &job.node_id, &url) {
|
||||
changed = true;
|
||||
} else {
|
||||
self.completed.insert(id);
|
||||
}
|
||||
} else {
|
||||
self.completed.insert(id);
|
||||
}
|
||||
}
|
||||
Err(TryRecvError::Empty) => {
|
||||
|
|
@ -228,10 +234,7 @@ fn is_image_area_frame_by_heuristic(node: &PenNode) -> bool {
|
|||
if !has_image_area_keyword(name) {
|
||||
return false;
|
||||
}
|
||||
if !matches!(node.width_px(), Some(w) if w >= 80.0) {
|
||||
return false;
|
||||
}
|
||||
if !matches!(node.height_px(), Some(h) if h >= 60.0) {
|
||||
if !is_image_area_size(&frame.container.width, &frame.container.height) {
|
||||
return false;
|
||||
}
|
||||
if !matches!(frame.container.fill.as_deref(), Some([PenFill::Solid(_)])) {
|
||||
|
|
@ -253,10 +256,7 @@ fn is_image_area_rectangle_by_heuristic(node: &PenNode) -> bool {
|
|||
if !has_image_area_keyword(name) {
|
||||
return false;
|
||||
}
|
||||
if !matches!(node.width_px(), Some(w) if w >= 80.0) {
|
||||
return false;
|
||||
}
|
||||
if !matches!(node.height_px(), Some(h) if h >= 60.0) {
|
||||
if !is_image_area_size(&rect.container.width, &rect.container.height) {
|
||||
return false;
|
||||
}
|
||||
if !matches!(rect.container.fill.as_deref(), Some([PenFill::Solid(_)])) {
|
||||
|
|
@ -268,6 +268,20 @@ fn is_image_area_rectangle_by_heuristic(node: &PenNode) -> bool {
|
|||
matches!(children.as_slice(), [] | [PenNode::IconFont(_)])
|
||||
}
|
||||
|
||||
fn is_image_area_size(width: &Option<SizingBehavior>, height: &Option<SizingBehavior>) -> bool {
|
||||
let (width_ok, width_concrete) = image_area_dimension_ok(width, 80.0);
|
||||
let (height_ok, height_concrete) = image_area_dimension_ok(height, 60.0);
|
||||
width_ok && height_ok && (width_concrete || height_concrete)
|
||||
}
|
||||
|
||||
fn image_area_dimension_ok(size: &Option<SizingBehavior>, min_px: f64) -> (bool, bool) {
|
||||
match size {
|
||||
Some(SizingBehavior::Number(px)) if *px >= min_px => (true, true),
|
||||
Some(SizingBehavior::Keyword(SizingKeyword::FillContainer)) => (true, false),
|
||||
_ => (false, false),
|
||||
}
|
||||
}
|
||||
|
||||
fn has_image_area_keyword(name: &str) -> bool {
|
||||
name.split(|c: char| !c.is_ascii_alphanumeric())
|
||||
.map(str::to_ascii_lowercase)
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use jian_ops_schema::node::base::PenNodeBase;
|
|||
use jian_ops_schema::node::{
|
||||
ContainerProps, FrameNode, ImageNode, PenNode, RectangleNode, TextContent, TextNode,
|
||||
};
|
||||
use jian_ops_schema::sizing::SizingBehavior;
|
||||
use jian_ops_schema::sizing::{SizingBehavior, SizingKeyword};
|
||||
use jian_ops_schema::style::{ImageFillMode, PenFill, SolidFillBody};
|
||||
|
||||
fn image_node(id: &str, src: &str, query: Option<&str>) -> PenNode {
|
||||
|
|
@ -107,6 +107,22 @@ fn frame_node(
|
|||
}
|
||||
|
||||
fn rectangle_node(id: &str, name: &str, fill: Option<Vec<PenFill>>) -> PenNode {
|
||||
rectangle_node_with_sizing(
|
||||
id,
|
||||
name,
|
||||
fill,
|
||||
Some(SizingBehavior::Number(240.0)),
|
||||
Some(SizingBehavior::Number(160.0)),
|
||||
)
|
||||
}
|
||||
|
||||
fn rectangle_node_with_sizing(
|
||||
id: &str,
|
||||
name: &str,
|
||||
fill: Option<Vec<PenFill>>,
|
||||
width: Option<SizingBehavior>,
|
||||
height: Option<SizingBehavior>,
|
||||
) -> PenNode {
|
||||
PenNode::Rectangle(RectangleNode {
|
||||
base: PenNodeBase {
|
||||
id: id.to_string(),
|
||||
|
|
@ -114,8 +130,8 @@ fn rectangle_node(id: &str, name: &str, fill: Option<Vec<PenFill>>) -> PenNode {
|
|||
..Default::default()
|
||||
},
|
||||
container: ContainerProps {
|
||||
width: Some(SizingBehavior::Number(240.0)),
|
||||
height: Some(SizingBehavior::Number(160.0)),
|
||||
width,
|
||||
height,
|
||||
fill,
|
||||
..Default::default()
|
||||
},
|
||||
|
|
@ -253,6 +269,25 @@ fn collect_targets_includes_solid_rectangle_image_areas() {
|
|||
assert_eq!(targets[0].query, "Latte Image");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn collect_targets_includes_fill_width_rectangle_image_areas() {
|
||||
let mut state = EditorState::default();
|
||||
state.active_children_mut().clear();
|
||||
state.active_children_mut().push(rectangle_node_with_sizing(
|
||||
"photo",
|
||||
"Latte Image",
|
||||
Some(vec![solid_fill()]),
|
||||
Some(SizingBehavior::Keyword(SizingKeyword::FillContainer)),
|
||||
Some(SizingBehavior::Number(180.0)),
|
||||
));
|
||||
|
||||
let targets = collect_targets(&state, &HashSet::new());
|
||||
|
||||
assert_eq!(targets.len(), 1);
|
||||
assert_eq!(targets[0].node_id.as_str(), "photo");
|
||||
assert_eq!(targets[0].query, "Latte Image");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn apply_result_repaints_placeholder_frame_with_image_fill_and_clears_children() {
|
||||
let mut state = EditorState::default();
|
||||
|
|
@ -341,7 +376,7 @@ fn poll_into_applies_finished_job_to_placeholder_frame() {
|
|||
assert!(session.poll_into(&mut state));
|
||||
assert!(!session.is_pending());
|
||||
assert!(session.in_flight.is_empty());
|
||||
assert!(session.completed.contains("photo"));
|
||||
assert!(!session.completed.contains("photo"));
|
||||
|
||||
let PenNode::Frame(frame) = &state.active_children()[0] else {
|
||||
panic!("expected frame");
|
||||
|
|
@ -353,6 +388,43 @@ fn poll_into_applies_finished_job_to_placeholder_frame() {
|
|||
assert_eq!(frame.children.as_deref(), Some(&[][..]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn successful_apply_does_not_suppress_later_unfilled_retry() {
|
||||
let mut state = EditorState::default();
|
||||
state.active_children_mut().clear();
|
||||
state.active_children_mut().push(rectangle_node(
|
||||
"photo",
|
||||
"Latte Image",
|
||||
Some(vec![solid_fill()]),
|
||||
));
|
||||
|
||||
let (tx, rx) = std::sync::mpsc::channel();
|
||||
tx.send(Some("https://example.com/photo.jpg".to_string()))
|
||||
.unwrap();
|
||||
let mut session = ImageSearchSession {
|
||||
in_flight: HashSet::from(["photo".to_string()]),
|
||||
completed: HashSet::new(),
|
||||
jobs: vec![ImageSearchJob {
|
||||
node_id: NodeId::new("photo"),
|
||||
rx,
|
||||
}],
|
||||
};
|
||||
|
||||
assert!(session.poll_into(&mut state));
|
||||
|
||||
let PenNode::Rectangle(rect) = &mut state.active_children_mut()[0] else {
|
||||
panic!("expected rectangle");
|
||||
};
|
||||
rect.container.fill = Some(vec![solid_fill()]);
|
||||
|
||||
let mut known = session.completed.clone();
|
||||
known.extend(session.in_flight.iter().cloned());
|
||||
let targets = collect_targets(&state, &known);
|
||||
|
||||
assert_eq!(targets.len(), 1);
|
||||
assert_eq!(targets[0].node_id.as_str(), "photo");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn openverse_credentials_require_both_fields() {
|
||||
let mut state = EditorState::default();
|
||||
|
|
|
|||
Loading…
Reference in a new issue