@@ -22,6 +22,7 @@ class AnnotationManager {
2222 this . _onChangeFilter = options . onChangeFilter ;
2323 this . _onSave = options . onSave ;
2424 this . _onDelete = options . onDelete ;
25+ this . _onChangeHistory = options . onChangeHistory ;
2526 this . _adjustTextAnnotationPosition = options . adjustTextAnnotationPosition ;
2627 this . render = ( ) => {
2728 options . onRender ( [ ...this . _annotations ] ) ;
@@ -36,6 +37,7 @@ class AnnotationManager {
3637
3738 this . _undoStack = [ ] ;
3839 this . _redoStack = [ ] ;
40+ this . _lastHistoryPointID = 0 ;
3941
4042 this . _annotations . sort ( ( a , b ) => ( a . sortIndex > b . sortIndex ) - ( a . sortIndex < b . sortIndex ) ) ;
4143
@@ -61,7 +63,14 @@ class AnnotationManager {
6163
6264 // Called when deletions come from the client side
6365 unsetAnnotations ( ids ) {
66+ // Deletions we haven't applied yet are outside changes that our history
67+ // can no longer be replayed over. Ones we have applied are our own
68+ // deletions coming back to us, and undoing them is still valid.
69+ let externalIDs = ids . filter ( id => this . _annotations . some ( x => x . id === id ) ) ;
6470 this . _annotations = this . _annotations . filter ( x => ! ids . includes ( x . id ) ) ;
71+ if ( externalIDs . length ) {
72+ this . _clearInterferingHistory ( externalIDs ) ;
73+ }
6574 this . render ( ) ;
6675 }
6776
@@ -95,7 +104,7 @@ class AnnotationManager {
95104 annotation . position = roundPositionValues ( annotation . position ) ;
96105
97106 let changedAnnotations = new Map ( [ [ annotation . id , annotation ] ] ) ;
98- this . _applyChanges ( changedAnnotations ) ;
107+ this . _applyChanges ( changedAnnotations , 'add-annotations' , 1 ) ;
99108 return annotation ;
100109 }
101110
@@ -170,7 +179,7 @@ class AnnotationManager {
170179 annotation . position = roundPositionValues ( annotation . position ) ;
171180 changedAnnotations . set ( annotation . id , annotation ) ;
172181 }
173- this . _applyChanges ( changedAnnotations ) ;
182+ this . _applyChanges ( changedAnnotations , 'update-annotations' , changedAnnotations . size ) ;
174183 }
175184
176185 deleteAnnotations ( ids ) {
@@ -182,7 +191,7 @@ class AnnotationManager {
182191 return 0 ;
183192 }
184193 let changedAnnotations = new Map ( ids . map ( id => [ id , null ] ) ) ;
185- this . _applyChanges ( changedAnnotations ) ;
194+ this . _applyChanges ( changedAnnotations , 'delete-annotations' , changedAnnotations . size ) ;
186195 return changedAnnotations . size ;
187196 }
188197
@@ -209,7 +218,7 @@ class AnnotationManager {
209218 annotation = { ...annotation , type, dateModified, id : this . _generateObjectKey ( ) } ;
210219 changedAnnotations . set ( annotation . id , annotation ) ;
211220 }
212- this . _applyChanges ( changedAnnotations ) ;
221+ this . _applyChanges ( changedAnnotations , 'convert-annotations' , annotations . length ) ;
213222 }
214223
215224 mergeAnnotations ( ids ) {
@@ -289,7 +298,7 @@ class AnnotationManager {
289298
290299 let changedAnnotations = new Map ( annotations . map ( x => [ x . id , null ] ) ) ;
291300 changedAnnotations . set ( annotation . id , annotation ) ;
292- this . _applyChanges ( changedAnnotations ) ;
301+ this . _applyChanges ( changedAnnotations , 'merge-annotations' , annotations . length ) ;
293302
294303 return annotation ;
295304 }
@@ -307,7 +316,12 @@ class AnnotationManager {
307316 return randomstring ;
308317 }
309318
310- _applyChanges ( changedAnnotations ) {
319+ /**
320+ * @param {Map | null } changedAnnotations Annotation ID -> new annotation, or null to delete
321+ * @param {string } action Action that produced the changes, for the history point
322+ * @param {number } count Number of annotations the action was performed on
323+ */
324+ _applyChanges ( changedAnnotations , action , count ) {
311325 if ( ! changedAnnotations . size ) {
312326 return ;
313327 }
@@ -320,7 +334,7 @@ class AnnotationManager {
320334 }
321335 this . _unsavedAnnotations . set ( id , changedAnnotation ) ;
322336 }
323- this . _historySave ( changedAnnotations ) ;
337+ this . _historySave ( changedAnnotations , action , count ) ;
324338 annotations = new Map ( [ ...annotations , ...changedAnnotations ] ) ;
325339 this . _annotations = [ ...annotations . values ( ) ] . filter ( x => x ) ;
326340 this . _annotations . sort ( ( a , b ) => ( a . sortIndex > b . sortIndex ) - ( a . sortIndex < b . sortIndex ) ) ;
@@ -458,7 +472,7 @@ class AnnotationManager {
458472 this . render ( ) ;
459473 }
460474
461- _historySave ( changedAnnotations ) {
475+ _historySave ( changedAnnotations , action , count ) {
462476 if ( ! changedAnnotations . size ) {
463477 return ;
464478 }
@@ -478,10 +492,10 @@ class AnnotationManager {
478492 let disableTextualJoin = true ;
479493 if (
480494 prevPoint && point
481- && prevPoint . size === 1 && point . size === 1 && oldAnnotations . size === 1
495+ && prevPoint . annotations . size === 1 && point . annotations . size === 1 && oldAnnotations . size === 1
482496 ) {
483- let [ id1 , annotation1 ] = [ ...prevPoint ] [ 0 ] ;
484- let [ id2 , annotation2 ] = [ ...point ] [ 0 ] ;
497+ let [ id1 , annotation1 ] = [ ...prevPoint . annotations ] [ 0 ] ;
498+ let [ id2 , annotation2 ] = [ ...point . annotations ] [ 0 ] ;
485499 let [ id3 , annotation3 ] = [ ...oldAnnotations ] [ 0 ] ;
486500 if ( id1 === id2 && id2 === id3 ) {
487501 disableJoin = false ;
@@ -502,45 +516,66 @@ class AnnotationManager {
502516 }
503517
504518 if ( ! point || disableJoin || Date . now ( ) - this . _lastChange > 500 && disableTextualJoin ) {
505- point = new Map ( ) ;
519+ point = {
520+ id : ++ this . _lastHistoryPointID ,
521+ // Bumped whenever an additional change is joined into the point,
522+ // so an external history can tell that it moved to the top
523+ revision : 0 ,
524+ action,
525+ count,
526+ annotations : new Map ( )
527+ } ;
506528 this . _undoStack . push ( point ) ;
507529 }
530+ else {
531+ // The point keeps the action it was created with and now covers this
532+ // change as well
533+ point . revision ++ ;
534+ }
508535 for ( let [ id , annotation ] of oldAnnotations ) {
509536 if ( annotation ) {
510537 annotation = JSON . parse ( JSON . stringify ( annotation ) ) ;
511538 delete annotation . image ;
512539 }
513- point . set ( id , annotation ) ;
540+ point . annotations . set ( id , annotation ) ;
514541 }
515542
516543 this . _lastChange = Date . now ( ) ;
517544 this . _redoStack = [ ] ;
545+ this . _notifyChangeHistory ( ) ;
518546 }
519547
520548 remapHistory ( mapping ) {
521549 for ( let [ oldID , newID ] of mapping ) {
522- for ( let point of this . _undoStack ) {
523- if ( point . has ( oldID ) ) {
524- let annotation = point . get ( oldID ) ;
550+ for ( let { annotations } of [ ... this . _undoStack , ... this . _redoStack ] ) {
551+ if ( annotations . has ( oldID ) ) {
552+ let annotation = annotations . get ( oldID ) ;
525553 if ( annotation ) {
526554 annotation . id = newID ;
527555 }
528- point . delete ( oldID ) ;
529- point . set ( newID , annotation ) ;
556+ annotations . delete ( oldID ) ;
557+ annotations . set ( newID , annotation ) ;
530558 }
531559 }
560+ }
561+ }
532562
533- for ( let point of this . _redoStack ) {
534- if ( point . has ( oldID ) ) {
535- let annotation = point . get ( oldID ) ;
536- if ( annotation ) {
537- annotation . id = newID ;
538- }
539- point . delete ( oldID ) ;
540- point . set ( newID , annotation ) ;
541- }
542- }
563+ /**
564+ * Report the current history to the client, which can then present the
565+ * reader's actions as part of the application-wide undo history, and
566+ * step through them with undo() and redo()
567+ */
568+ _notifyChangeHistory ( ) {
569+ if ( ! this . _onChangeHistory ) {
570+ return ;
543571 }
572+ let describe = stack => stack . map (
573+ ( { id, revision, action, count } ) => ( { id, revision, action, count } )
574+ ) ;
575+ this . _onChangeHistory ( {
576+ undoSteps : describe ( this . _undoStack ) ,
577+ redoSteps : describe ( this . _redoStack )
578+ } ) ;
544579 }
545580
546581 get canUndo ( ) {
@@ -557,12 +592,12 @@ class AnnotationManager {
557592 return false ;
558593 }
559594 let mapping = new Map ( ) ;
560- let redoPoint = new Map ( ) ;
595+ let redoPoint = { ... undoPoint , annotations : new Map ( ) } ;
561596 let allAnnotations = new Map ( this . _annotations . map ( x => [ x . id , x ] ) ) ;
562- for ( let [ id , annotation ] of undoPoint ) {
597+ for ( let [ id , annotation ] of undoPoint . annotations ) {
563598 annotation = annotation && { ...annotation } ;
564599 let prevAnnotation = allAnnotations . get ( id ) ;
565- redoPoint . set ( id , prevAnnotation ) ;
600+ redoPoint . annotations . set ( id , prevAnnotation ) ;
566601 if ( annotation ) {
567602 annotation . dateModified = ( new Date ( ) ) . toISOString ( ) ;
568603 }
@@ -581,6 +616,7 @@ class AnnotationManager {
581616 this . _annotations . sort ( ( a , b ) => ( a . sortIndex > b . sortIndex ) - ( a . sortIndex < b . sortIndex ) ) ;
582617 this . _triggerSaving ( ) ;
583618 this . render ( ) ;
619+ this . _notifyChangeHistory ( ) ;
584620 return true ;
585621 }
586622
@@ -590,12 +626,12 @@ class AnnotationManager {
590626 return false ;
591627 }
592628 let mapping = new Map ( ) ;
593- let undoPoint = new Map ( ) ;
629+ let undoPoint = { ... redoPoint , annotations : new Map ( ) } ;
594630 let allAnnotations = new Map ( this . _annotations . map ( x => [ x . id , x ] ) ) ;
595- for ( let [ id , annotation ] of redoPoint ) {
631+ for ( let [ id , annotation ] of redoPoint . annotations ) {
596632 annotation = annotation && { ...annotation } ;
597633 let prevAnnotation = allAnnotations . get ( id ) ;
598- undoPoint . set ( id , prevAnnotation ) ;
634+ undoPoint . annotations . set ( id , prevAnnotation ) ;
599635 if ( annotation ) {
600636 annotation . dateModified = ( new Date ( ) ) . toISOString ( ) ;
601637 }
@@ -614,22 +650,24 @@ class AnnotationManager {
614650 this . _annotations . sort ( ( a , b ) => ( a . sortIndex > b . sortIndex ) - ( a . sortIndex < b . sortIndex ) ) ;
615651 this . _triggerSaving ( ) ;
616652 this . render ( ) ;
653+ this . _notifyChangeHistory ( ) ;
617654 return true ;
618655 }
619656
620657 _clearInterferingHistory ( affectedAnnotationIDs ) {
621658 for ( let i = this . _undoStack . length - 1 ; i >= 0 ; i -- ) {
622- if ( affectedAnnotationIDs . some ( id => this . _undoStack [ i ] . has ( id ) ) ) {
659+ if ( affectedAnnotationIDs . some ( id => this . _undoStack [ i ] . annotations . has ( id ) ) ) {
623660 this . _undoStack = this . _undoStack . slice ( i + 1 ) ;
624661 break ;
625662 }
626663 }
627664 for ( let i = 0 ; i < this . _redoStack . length ; i ++ ) {
628- if ( affectedAnnotationIDs . some ( id => this . _redoStack [ i ] . has ( id ) ) ) {
665+ if ( affectedAnnotationIDs . some ( id => this . _redoStack [ i ] . annotations . has ( id ) ) ) {
629666 this . _redoStack = this . _redoStack . slice ( 0 , Math . max ( 0 , i - 1 ) ) ;
630667 break ;
631668 }
632669 }
670+ this . _notifyChangeHistory ( ) ;
633671 }
634672}
635673
0 commit comments