@@ -55,18 +55,21 @@ type TldrawPreviewProps = {
5555 store : TLStore ;
5656 file : TFile ;
5757 assetStore : ObsidianTLAssetStore ;
58+ canvasUuid : string ;
5859} ;
5960
6061export const TldrawPreviewComponent = ( {
6162 store,
6263 file,
6364 assetStore,
65+ canvasUuid,
6466} : TldrawPreviewProps ) => {
6567 const containerRef = useRef < HTMLDivElement > ( null ) ;
6668 const [ currentStore , setCurrentStore ] = useState < TLStore > ( store ) ;
6769 const [ isReady , setIsReady ] = useState ( false ) ;
6870 const isCreatingRelationRef = useRef ( false ) ;
6971 const saveTimeoutRef = useRef < NodeJS . Timeout > ( null ) ;
72+ const isSavingRef = useRef < boolean > ( false ) ;
7073 const lastShiftClickRef = useRef < number > ( 0 ) ;
7174 const SHIFT_CLICK_DEBOUNCE_MS = 300 ; // Prevent double clicks within 300ms
7275 const lastSavedDataRef = useRef < string > ( "" ) ;
@@ -99,10 +102,21 @@ export const TldrawPreviewComponent = ({
99102 } , [ ] ) ;
100103
101104 const saveChanges = useCallback ( async ( ) => {
105+ // Prevent concurrent saves
106+ if ( isSavingRef . current ) {
107+ return ;
108+ }
109+
110+ if ( ! canvasUuid ) {
111+ return ;
112+ }
113+
114+ isSavingRef . current = true ;
115+
102116 const newData = getTLDataTemplate ( {
103117 pluginVersion : plugin . manifest . version ,
104118 tldrawFile : createRawTldrawFile ( currentStore ) ,
105- uuid : window . crypto . randomUUID ( ) ,
119+ uuid : canvasUuid ,
106120 } ) ;
107121 const stringifiedData = JSON . stringify ( newData , null , "\t" ) ;
108122
@@ -131,8 +145,21 @@ export const TldrawPreviewComponent = ({
131145 ) ,
132146 ) ;
133147
134- if ( ! verifyMatch || verifyMatch [ 1 ] ?. trim ( ) !== stringifiedData . trim ( ) ) {
135- throw new Error ( "Failed to verify saved TLDraw data" ) ;
148+ if ( ! verifyMatch ) {
149+ throw new Error (
150+ "Failed to verify saved TLDraw data: Could not find data block" ,
151+ ) ;
152+ }
153+
154+ const savedData = JSON . parse ( verifyMatch [ 1 ] ?. trim ( ) ?? "{}" ) as TLData ;
155+ const expectedData = JSON . parse (
156+ stringifiedData ?. trim ( ) ?? "{}" ,
157+ ) as TLData ;
158+
159+ if ( JSON . stringify ( savedData ) !== JSON . stringify ( expectedData ) ) {
160+ console . warn (
161+ "Saved data differs from expected (this is normal during concurrent operations)" ,
162+ ) ;
136163 }
137164
138165 lastSavedDataRef . current = stringifiedData ;
@@ -155,18 +182,26 @@ export const TldrawPreviewComponent = ({
155182 setCurrentStore ( newStore ) ;
156183 }
157184 }
158- } , [ file , plugin , currentStore , assetStore ] ) ;
185+ isSavingRef . current = false ;
186+ } , [ file , plugin , currentStore , assetStore , canvasUuid ] ) ;
159187
160188 useEffect ( ( ) => {
161189 const unsubscribe = currentStore . listen (
162190 ( ) => {
163191 if ( saveTimeoutRef . current ) {
164192 clearTimeout ( saveTimeoutRef . current ) ;
165193 }
166- saveTimeoutRef . current = setTimeout (
167- ( ) => void saveChanges ( ) ,
168- DEFAULT_SAVE_DELAY ,
169- ) ;
194+ saveTimeoutRef . current = setTimeout ( ( ) => {
195+ // If a save is already in progress, schedule another save after it completes
196+ if ( isSavingRef . current ) {
197+ saveTimeoutRef . current = setTimeout (
198+ ( ) => void saveChanges ( ) ,
199+ DEFAULT_SAVE_DELAY ,
200+ ) ;
201+ } else {
202+ void saveChanges ( ) ;
203+ }
204+ } , DEFAULT_SAVE_DELAY ) ;
170205 } ,
171206 { source : "user" , scope : "document" } ,
172207 ) ;
@@ -366,7 +401,7 @@ export const TldrawPreviewComponent = ({
366401 < TldrawUiMenuItem
367402 id = "discourse-node"
368403 icon = "discourseNodeIcon"
369- label = "Discourse Node "
404+ label = "Discourse Graph "
370405 onSelect = { ( ) => {
371406 if ( editorRef . current ) {
372407 editorRef . current . setCurrentTool ( "discourse-node" ) ;
0 commit comments