@@ -8,6 +8,11 @@ use crate::{
88 scenario:: { append_annotation, remove_annotation, Shared } ,
99} ;
1010
11+ /// Write `content` to `path`, returning a user-facing error string on failure.
12+ fn write_file ( path : & std:: path:: Path , content : & str ) -> Result < ( ) , String > {
13+ std:: fs:: write ( path, content) . map_err ( |e| format ! ( "write: {e}" ) )
14+ }
15+
1116/// The left-hand "Comments" panel: lists the scenario's annotations, with
1217/// per-comment "go to frame" and "delete" actions.
1318#[ component]
@@ -60,36 +65,58 @@ pub fn AnnotationBox(pointer: String, kind: String, current: Signal<u32>) -> Ele
6065 let shared = use_context :: < Shared > ( ) ;
6166 let mut note = use_signal ( String :: new) ;
6267
63- let submit = move |_| {
64- let text = note ( ) ;
65- if text. trim ( ) . is_empty ( ) {
66- return ;
67- }
68- let frame = current ( ) ;
69- let ( path, raw, view, scene) = {
70- let m = shared. lock ( ) . unwrap ( ) ;
71- let ( view, scene) = match m. tasks . get ( frame as usize ) {
72- Some ( rustmotion:: encode:: video:: FrameTask :: Normal {
73- view_idx,
74- scene_idx,
75- ..
76- } ) => ( * view_idx, * scene_idx) ,
77- _ => ( 0 , 0 ) ,
68+ let submit = {
69+ let shared = shared. clone ( ) ;
70+ move |_| {
71+ let text = note ( ) ;
72+ if text. trim ( ) . is_empty ( ) {
73+ return ;
74+ }
75+ let frame = current ( ) ;
76+ let ( path, raw, view, scene) = {
77+ let m = shared. lock ( ) . unwrap_or_else ( |e| e. into_inner ( ) ) ;
78+ let ( view, scene) = match m. tasks . get ( frame as usize ) {
79+ Some ( rustmotion:: encode:: video:: FrameTask :: Normal {
80+ view_idx,
81+ scene_idx,
82+ ..
83+ } ) => ( * view_idx, * scene_idx) ,
84+ _ => ( 0 , 0 ) ,
85+ } ;
86+ ( m. path . clone ( ) , m. raw . clone ( ) , view, scene)
7887 } ;
79- ( m. path . clone ( ) , m. raw . clone ( ) , view, scene)
80- } ;
81- let ann = serde_json:: json!( {
82- "id" : annotation_id( ) , "note" : text, "status" : "open" , "frame" : frame,
83- "view" : view, "scene" : scene,
84- "target" : { "pointer" : pointer, "kind" : kind }
85- } ) ;
86- let raw = append_annotation ( raw, ann) ;
87- // Annotations live in the scenario JSON; HTML sources have no place to
88- // store them, so skip the write rather than overwrite the HTML.
89- if let ( Some ( path) , Ok ( t) ) = ( path, serde_json:: to_string_pretty ( & raw ) ) {
90- if !rustmotion:: loader:: is_html_path ( & path) {
91- let _ = std:: fs:: write ( & path, t) ;
92- note. set ( String :: new ( ) ) ;
88+ let ann = serde_json:: json!( {
89+ "id" : annotation_id( ) , "note" : text, "status" : "open" , "frame" : frame,
90+ "view" : view, "scene" : scene,
91+ "target" : { "pointer" : pointer, "kind" : kind }
92+ } ) ;
93+ let updated = append_annotation ( raw, ann) ;
94+ // Annotations live in the scenario JSON; HTML sources have no place to
95+ // store them, so skip the write rather than overwrite the HTML.
96+ if let Some ( path) = path {
97+ if !rustmotion:: loader:: is_html_path ( & path) {
98+ match serde_json:: to_string_pretty ( & updated) {
99+ Ok ( t) => {
100+ let write_result = write_file ( & path, & t) ;
101+ let mut m = shared. lock ( ) . unwrap_or_else ( |e| e. into_inner ( ) ) ;
102+ match write_result {
103+ Ok ( ( ) ) => {
104+ m. write_error = None ;
105+ note. set ( String :: new ( ) ) ;
106+ }
107+ Err ( e) => {
108+ m. write_error = Some ( e) ;
109+ m. generation = m. generation . wrapping_add ( 1 ) ;
110+ }
111+ }
112+ }
113+ Err ( e) => {
114+ let mut m = shared. lock ( ) . unwrap_or_else ( |e| e. into_inner ( ) ) ;
115+ m. write_error = Some ( format ! ( "json: {e}" ) ) ;
116+ m. generation = m. generation . wrapping_add ( 1 ) ;
117+ }
118+ }
119+ }
93120 }
94121 }
95122 } ;
@@ -118,13 +145,20 @@ pub fn AnnotationBox(pointer: String, kind: String, current: Signal<u32>) -> Ele
118145/// Remove an annotation by id from the scenario file (the watcher reloads).
119146fn delete_annotation ( shared : & Shared , id : & str ) {
120147 let ( path, raw) = {
121- let m = shared. lock ( ) . unwrap ( ) ;
148+ let m = shared. lock ( ) . unwrap_or_else ( |e| e . into_inner ( ) ) ;
122149 ( m. path . clone ( ) , m. raw . clone ( ) )
123150 } ;
124- let raw = remove_annotation ( raw, id) ;
125- if let ( Some ( path) , Ok ( t ) ) = ( path, serde_json :: to_string_pretty ( & raw ) ) {
151+ let updated = remove_annotation ( raw, id) ;
152+ if let Some ( path) = path {
126153 if !rustmotion:: loader:: is_html_path ( & path) {
127- let _ = std:: fs:: write ( & path, t) ;
154+ let write_result = serde_json:: to_string_pretty ( & updated)
155+ . map_err ( |e| format ! ( "json: {e}" ) )
156+ . and_then ( |t| write_file ( & path, & t) ) ;
157+ if let Err ( e) = write_result {
158+ let mut m = shared. lock ( ) . unwrap_or_else ( |e2| e2. into_inner ( ) ) ;
159+ m. write_error = Some ( e) ;
160+ m. generation = m. generation . wrapping_add ( 1 ) ;
161+ }
128162 }
129163 }
130164}
0 commit comments