jellyflow_runtime/runtime/connection/
lifecycle.rs1use serde::{Deserialize, Serialize};
2
3use crate::runtime::events::{ConnectDragKind, ConnectEnd, ConnectEndOutcome, ConnectStart};
4use crate::schema::MenuDescriptor;
5use jellyflow_core::core::{CanvasPoint, PortId};
6use jellyflow_core::interaction::NodeGraphConnectionMode;
7
8use super::{ConnectionHandleConnection, ResolvedConnectionTarget};
9
10#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
12#[serde(tag = "kind", content = "data", rename_all = "snake_case")]
13pub enum ConnectionEndIntent {
14 Complete,
16 Cancel,
18 DropOnPane {
20 pointer: CanvasPoint,
21 #[serde(default, skip_serializing_if = "Option::is_none")]
22 menu: Option<MenuDescriptor>,
23 },
24}
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
28#[serde(rename_all = "snake_case")]
29pub enum ConnectionLifecycleState {
30 Started,
31 HoverValid,
32 HoverInvalid,
33 HoverEmpty,
34 Committed,
35 Rejected,
36 Canceled,
37 DroppedOnPane,
38 NoOp,
39}
40
41#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
43pub struct ConnectionLifecycleResult {
44 pub start: ConnectStart,
45 pub state: ConnectionLifecycleState,
46 #[serde(default, skip_serializing_if = "Option::is_none")]
47 pub hover: Option<ResolvedConnectionTarget>,
48 #[serde(default, skip_serializing_if = "Option::is_none")]
49 pub connection: Option<ConnectionHandleConnection>,
50 #[serde(default, skip_serializing_if = "Option::is_none")]
51 pub target: Option<PortId>,
52 pub end: ConnectEnd,
53 #[serde(default, skip_serializing_if = "Option::is_none")]
54 pub dropped_at: Option<CanvasPoint>,
55 #[serde(default, skip_serializing_if = "Option::is_none")]
56 pub dropped_wire_menu: Option<MenuDescriptor>,
57}
58
59impl ConnectionLifecycleResult {
60 pub fn for_hover(start: ConnectStart, hover: Option<ResolvedConnectionTarget>) -> Self {
61 let state = match hover {
62 Some(hover) if hover.is_handle_valid => ConnectionLifecycleState::HoverValid,
63 Some(hover) if hover.target.is_some() => ConnectionLifecycleState::HoverInvalid,
64 _ => ConnectionLifecycleState::HoverEmpty,
65 };
66
67 Self {
68 end: ConnectEnd {
69 kind: start.kind.clone(),
70 mode: start.mode,
71 target: hover.and_then(|hover| hover.target.map(|target| target.handle.port)),
72 outcome: ConnectEndOutcome::NoOp,
73 },
74 start,
75 state,
76 hover,
77 connection: hover.and_then(|hover| hover.connection),
78 target: hover.and_then(|hover| hover.target.map(|target| target.handle.port)),
79 dropped_at: None,
80 dropped_wire_menu: None,
81 }
82 }
83
84 pub fn did_commit(&self) -> bool {
85 matches!(self.state, ConnectionLifecycleState::Committed)
86 }
87
88 pub fn opens_dropped_wire_menu(&self) -> bool {
89 matches!(self.state, ConnectionLifecycleState::DroppedOnPane)
90 }
91}
92
93pub fn resolve_connection_lifecycle(
95 start: ConnectStart,
96 hover: Option<ResolvedConnectionTarget>,
97 intent: ConnectionEndIntent,
98) -> ConnectionLifecycleResult {
99 let (state, connection, target, outcome, dropped_at, dropped_wire_menu) = match intent {
100 ConnectionEndIntent::Cancel => (
101 ConnectionLifecycleState::Canceled,
102 None,
103 None,
104 ConnectEndOutcome::Canceled,
105 None,
106 None,
107 ),
108 ConnectionEndIntent::DropOnPane { pointer, menu } => (
109 ConnectionLifecycleState::DroppedOnPane,
110 None,
111 None,
112 ConnectEndOutcome::OpenInsertNodePicker,
113 Some(pointer),
114 menu,
115 ),
116 ConnectionEndIntent::Complete => resolve_completed_lifecycle(hover),
117 };
118
119 let end = ConnectEnd {
120 kind: start.kind.clone(),
121 mode: start.mode,
122 target,
123 outcome,
124 };
125
126 ConnectionLifecycleResult {
127 start,
128 state,
129 hover,
130 connection,
131 target,
132 end,
133 dropped_at,
134 dropped_wire_menu,
135 }
136}
137
138fn resolve_completed_lifecycle(
139 hover: Option<ResolvedConnectionTarget>,
140) -> (
141 ConnectionLifecycleState,
142 Option<ConnectionHandleConnection>,
143 Option<PortId>,
144 ConnectEndOutcome,
145 Option<CanvasPoint>,
146 Option<MenuDescriptor>,
147) {
148 let Some(hover) = hover else {
149 return (
150 ConnectionLifecycleState::NoOp,
151 None,
152 None,
153 ConnectEndOutcome::NoOp,
154 None,
155 None,
156 );
157 };
158
159 if hover.is_handle_valid {
160 let connection = hover.connection;
161 let target = connection.map(|connection| connection.target.port);
162 return (
163 ConnectionLifecycleState::Committed,
164 connection,
165 target,
166 ConnectEndOutcome::Committed,
167 None,
168 None,
169 );
170 }
171
172 let target = hover.target.map(|target| target.handle.port);
173 let state = if hover.target.is_some() {
174 ConnectionLifecycleState::Rejected
175 } else {
176 ConnectionLifecycleState::NoOp
177 };
178 let outcome = if hover.target.is_some() {
179 ConnectEndOutcome::Rejected
180 } else {
181 ConnectEndOutcome::NoOp
182 };
183
184 (state, hover.connection, target, outcome, None, None)
185}
186
187pub fn new_connection_start(
189 from: PortId,
190 bundle: impl IntoIterator<Item = PortId>,
191 mode: NodeGraphConnectionMode,
192) -> ConnectStart {
193 ConnectStart {
194 kind: ConnectDragKind::New {
195 from,
196 bundle: bundle.into_iter().collect(),
197 },
198 mode,
199 }
200}