@@ -93,6 +93,68 @@ impl CurveFitApp {
9393 }
9494 }
9595
96+ pub ( super ) fn copy_text_to_clipboard ( & mut self , ctx : & egui:: Context , text : String ) {
97+ #[ cfg( not( target_arch = "wasm32" ) ) ]
98+ {
99+ ctx. copy_text ( text) ;
100+ }
101+
102+ #[ cfg( target_arch = "wasm32" ) ]
103+ {
104+ if self . clipboard_copy_web_in_flight {
105+ return ;
106+ }
107+
108+ let promise = match start_write_text_to_web_clipboard ( & text) {
109+ Ok ( promise) => promise,
110+ Err ( error) => {
111+ self . set_clipboard_copy_error ( error) ;
112+ return ;
113+ }
114+ } ;
115+
116+ let ctx = ctx. clone ( ) ;
117+ let result_slot = Rc :: clone ( & self . clipboard_copy_web_result ) ;
118+ self . clipboard_copy_web_in_flight = true ;
119+ self . clipboard_copy_web_result . borrow_mut ( ) . take ( ) ;
120+ wasm_bindgen_futures:: spawn_local ( async move {
121+ let result = wasm_bindgen_futures:: JsFuture :: from ( promise)
122+ . await
123+ . map ( |_| ( ) )
124+ . map_err ( |error| {
125+ format ! (
126+ "Failed to write clipboard text: {}" ,
127+ describe_web_clipboard_js_error( & error)
128+ )
129+ } ) ;
130+ * result_slot. borrow_mut ( ) = Some ( result) ;
131+ ctx. request_repaint ( ) ;
132+ } ) ;
133+ }
134+ }
135+
136+ pub ( super ) fn poll_clipboard_copy ( & mut self , ctx : & egui:: Context ) {
137+ #[ cfg( not( target_arch = "wasm32" ) ) ]
138+ let _ = ctx;
139+
140+ #[ cfg( target_arch = "wasm32" ) ]
141+ {
142+ if !self . clipboard_copy_web_in_flight {
143+ return ;
144+ }
145+
146+ let maybe_result = self . clipboard_copy_web_result . borrow_mut ( ) . take ( ) ;
147+ if let Some ( result) = maybe_result {
148+ self . clipboard_copy_web_in_flight = false ;
149+ if let Err ( error) = result {
150+ self . set_clipboard_copy_error ( error) ;
151+ }
152+ } else {
153+ ctx. request_repaint ( ) ;
154+ }
155+ }
156+ }
157+
96158 pub ( super ) fn handle_points_clipboard_import_result ( & mut self , result : Result < String , String > ) {
97159 let text = match result {
98160 Ok ( text) => text,
@@ -135,6 +197,18 @@ impl CurveFitApp {
135197 ) ) ) ;
136198 }
137199 }
200+
201+ #[ cfg( target_arch = "wasm32" ) ]
202+ fn set_clipboard_copy_error ( & mut self , message : impl AsRef < str > ) {
203+ let message = message. as_ref ( ) ;
204+ if message. starts_with ( CLIPBOARD_COPY_ERROR_PREFIX ) {
205+ self . status = Some ( StatusMessage :: Error ( message. to_owned ( ) ) ) ;
206+ } else {
207+ self . status = Some ( StatusMessage :: Error ( format ! (
208+ "{CLIPBOARD_COPY_ERROR_PREFIX}{message}"
209+ ) ) ) ;
210+ }
211+ }
138212}
139213
140214#[ cfg( not( target_arch = "wasm32" ) ) ]
@@ -155,13 +229,59 @@ fn take_requested_paste_event(ctx: &egui::Context) -> Option<String> {
155229async fn read_text_from_web_clipboard ( ) -> Result < String , String > {
156230 use wasm_bindgen_futures:: JsFuture ;
157231
158- let window = web_sys :: window ( ) . ok_or_else ( || "Window is unavailable" . to_string ( ) ) ?;
232+ let window = web_clipboard_window ( ) ?;
159233 let clipboard = window. navigator ( ) . clipboard ( ) ;
160234 let text = JsFuture :: from ( clipboard. read_text ( ) )
161235 . await
162- . map_err ( |error| format ! ( "Failed to read clipboard text: {error:?}" ) ) ?
236+ . map_err ( |error| {
237+ format ! (
238+ "Failed to read clipboard text: {}" ,
239+ describe_web_clipboard_js_error( & error)
240+ )
241+ } ) ?
163242 . as_string ( )
164243 . ok_or_else ( || "Clipboard did not return text content" . to_string ( ) ) ?;
165244
166245 Ok ( text)
167246}
247+
248+ #[ cfg( target_arch = "wasm32" ) ]
249+ fn start_write_text_to_web_clipboard ( text : & str ) -> Result < web_sys:: js_sys:: Promise , String > {
250+ // Для web Clipboard API запись должна стартовать прямо в обработчике user gesture.
251+ let window = web_clipboard_window ( ) ?;
252+ let clipboard = window. navigator ( ) . clipboard ( ) ;
253+ Ok ( clipboard. write_text ( text) )
254+ }
255+
256+ #[ cfg( target_arch = "wasm32" ) ]
257+ fn web_clipboard_window ( ) -> Result < web_sys:: Window , String > {
258+ let window = web_sys:: window ( ) . ok_or_else ( || "Window is unavailable" . to_string ( ) ) ?;
259+ if !window. is_secure_context ( ) {
260+ return Err (
261+ "Clipboard API is unavailable in non-secure context (HTTPS or localhost required)"
262+ . to_string ( ) ,
263+ ) ;
264+ }
265+ Ok ( window)
266+ }
267+
268+ #[ cfg( target_arch = "wasm32" ) ]
269+ fn describe_web_clipboard_js_error ( error : & wasm_bindgen:: JsValue ) -> String {
270+ if let Some ( message) = error. as_string ( ) {
271+ return message;
272+ }
273+
274+ let message = web_sys:: js_sys:: Reflect :: get ( error, & wasm_bindgen:: JsValue :: from_str ( "message" ) )
275+ . ok ( )
276+ . and_then ( |value| value. as_string ( ) ) ;
277+ let name = web_sys:: js_sys:: Reflect :: get ( error, & wasm_bindgen:: JsValue :: from_str ( "name" ) )
278+ . ok ( )
279+ . and_then ( |value| value. as_string ( ) ) ;
280+
281+ match ( name, message) {
282+ ( Some ( name) , Some ( message) ) => format ! ( "{name}: {message}" ) ,
283+ ( None , Some ( message) ) => message,
284+ ( Some ( name) , None ) => name,
285+ ( None , None ) => format ! ( "{error:?}" ) ,
286+ }
287+ }
0 commit comments