1+ import { ClickBinderDirective } from "../../directives/click-binder.directive" ;
12import { RedoBinderDirective } from "../../directives/redo-binder.directive" ;
23import { UndoBinderDirective } from "../../directives/undo-binder.directive" ;
3- import { NgFor , NgIf } from "@angular/common" ;
4- import { Component , Input , Optional , ViewChild , AfterViewInit , ElementRef } from "@angular/core" ;
4+ import { AsyncPipe , NgFor , NgIf , NgStyle } from "@angular/common" ;
5+ import { Component , AfterViewInit , input , numberAttribute , untracked , inject , viewChild , Signal , computed } from "@angular/core" ;
6+ import { toSignal } from "@angular/core/rxjs-interop" ;
7+ import { DomSanitizer , SafeHtml } from "@angular/platform-browser" ;
58import { RedoNTimes , UndoNTimes , Bindings , Undoable , UndoHistory , UndoHistoryBase } from "interacto" ;
9+ import { throttleTime } from "rxjs" ;
610
711@Component ( {
812 selector : "io-linear-history" ,
@@ -13,73 +17,153 @@ import {RedoNTimes, UndoNTimes, Bindings, Undoable, UndoHistory, UndoHistoryBase
1317 NgFor ,
1418 NgIf ,
1519 UndoBinderDirective ,
16- RedoBinderDirective
20+ RedoBinderDirective ,
21+ AsyncPipe ,
22+ NgStyle ,
23+ ClickBinderDirective
1724 ]
1825} )
1926export class LinearHistoryComponent implements AfterViewInit {
20- @ViewChild ( "undoButtonContainer" )
21- protected undoButtonContainer : ElementRef < HTMLElement > ;
27+ public readonly svgViewportWidth = input ( 50 , { transform : numberAttribute } ) ;
2228
23- @ViewChild ( "redoButtonContainer" )
24- protected redoButtonContainer : ElementRef < HTMLElement > ;
29+ public readonly svgViewportHeight = input ( 50 , { transform : numberAttribute } ) ;
2530
26- @Input ( )
27- @Optional ( )
28- public svgViewportWidth = 50 ;
31+ public readonly svgIconSize = input ( 50 , { transform : numberAttribute } ) ;
2932
30- @Input ( )
31- @Optional ( )
32- public svgViewportHeight = 50 ;
33+ public readonly cmdViewWidth = input ( 50 , { transform : numberAttribute } ) ;
3334
34- @Input ( )
35- @Optional ( )
36- public svgIconSize = 50 ;
35+ public readonly cmdViewHeight = input ( 50 , { transform : numberAttribute } ) ;
3736
38- public constructor ( protected undoHistory : UndoHistory , protected bindings : Bindings < UndoHistoryBase > ) {
37+ protected readonly undoButtonContainer : Signal < HTMLElement > = viewChild . required < HTMLElement > ( "undoButtonContainer" ) ;
38+
39+ protected readonly redoButtonContainer : Signal < HTMLElement > = viewChild . required < HTMLElement > ( "redoButtonContainer" ) ;
40+
41+ protected readonly history : UndoHistory = inject ( UndoHistory ) ;
42+
43+ protected readonly bindings : Bindings < UndoHistoryBase > = inject < Bindings < UndoHistoryBase > > ( Bindings < UndoHistoryBase > ) ;
44+
45+ protected readonly cmdViewWidthPx = computed ( ( ) => `${ String ( this . cmdViewWidth ( ) ) } px` ) ;
46+
47+ protected readonly cmdViewHeightPx = computed ( ( ) => `${ String ( this . cmdViewHeight ( ) ) } px` ) ;
48+
49+ protected readonly thumbnailsUndo : Signal < Array < Promise < unknown > > > ;
50+
51+ protected readonly thumbnailsRedo : Signal < Array < Promise < unknown > > > ;
52+
53+ private readonly sanitizer = inject ( DomSanitizer ) ;
54+
55+ private readonly undos : Signal < [ number , number ] | undefined > ;
56+
57+ protected cache : Record < number , unknown > = { } ;
58+
59+ public constructor ( ) {
60+ this . undos = toSignal < [ number , number ] | undefined > ( this . history . sizeObservable ( ) . pipe ( throttleTime ( 200 ) ) ) ;
61+
62+ this . thumbnailsUndo = computed ( ( ) => {
63+ this . undos ( ) ;
64+ return this . history . getUndo ( ) . map ( async ( entry , index ) =>
65+ this . undoButtonSnapshot ( entry , index ) ) ;
66+ } ) ;
67+
68+ this . thumbnailsRedo = computed ( ( ) => {
69+ const sizes = this . undos ( ) ;
70+ return this . history . getRedo ( ) . map ( async ( entry , index ) =>
71+ this . undoButtonSnapshot ( entry , index + ( sizes ?. [ 0 ] ?? 0 ) ) ) ;
72+ } ) ;
3973 }
4074
4175 public ngAfterViewInit ( ) : void {
4276 this . bindings . buttonBinder ( )
43- . onDynamic ( this . undoButtonContainer )
77+ . onDynamic ( this . undoButtonContainer ( ) )
4478 . toProduce ( i => new UndoNTimes (
45- this . undoHistory ,
79+ this . history ,
4680 parseInt ( i . widget ?. getAttribute ( "data-index" ) ?? "-1" , 10 ) ) )
4781 . bind ( ) ;
4882
4983 this . bindings . buttonBinder ( )
50- . onDynamic ( this . redoButtonContainer )
84+ . onDynamic ( this . redoButtonContainer ( ) )
5185 . toProduce ( i => new RedoNTimes (
52- this . undoHistory ,
86+ this . history ,
5387 parseInt ( i . widget ?. getAttribute ( "data-index" ) ?? "-1" , 10 ) ) )
5488 . bind ( ) ;
5589 }
5690
57- public undoButtonSnapshot ( command : Undoable , button : HTMLButtonElement ) : unknown {
58- const snapshot = command . getVisualSnapshot ( ) ;
91+ protected async undoButtonSnapshot ( command : Undoable , index : number ) : Promise < unknown > {
92+ const snapshot = this . cache [ index ] ?? command . getVisualSnapshot ( ) ;
93+ const txt = command . getUndoName ( ) ;
94+
5995 if ( snapshot === undefined ) {
60- return command . getUndoName ( ) ;
96+ return new Promise < string > ( resolve => {
97+ resolve ( txt ) ;
98+ } ) ;
6199 }
62100
63101 if ( typeof snapshot === "string" ) {
64- return `${ command . getUndoName ( ) } : ${ snapshot } ` ;
102+ return new Promise < string > ( resolve => {
103+ resolve ( `${ txt } : ${ snapshot } ` ) ;
104+ } ) ;
105+ }
106+
107+ if ( snapshot instanceof Promise ) {
108+ return snapshot
109+ . then ( ( res : unknown ) => {
110+ this . cache [ index ] = res ;
111+ return this . undoButtonSnapshot_ ( res , txt ) ;
112+ } ) ;
65113 }
66114
67115 if ( snapshot instanceof SVGElement ) {
68- button . querySelectorAll ( "div" ) [ 0 ] . remove ( ) ;
69-
70- const size = `${ String ( this . svgIconSize ) } px` ;
71- const div = document . createElement ( "div" ) ;
72- div . appendChild ( snapshot ) ;
73- div . style . width = size ;
74- div . style . height = size ;
75- snapshot . setAttribute ( "viewBox" , `0 0 ${ String ( this . svgViewportWidth ) } ${ String ( this . svgViewportHeight ) } ` ) ;
76- snapshot . setAttribute ( "width" , size ) ;
77- snapshot . setAttribute ( "height" , size ) ;
78- button . querySelectorAll ( "div" ) [ 0 ] . remove ( ) ;
79- button . appendChild ( div ) ;
80- return command . getUndoName ( ) ;
116+ return new Promise < Element > ( resolve => {
117+ resolve ( this . configureHtmlSvgTag ( snapshot , true ) ) ;
118+ } ) ;
119+ }
120+
121+ if ( snapshot instanceof Element ) {
122+ return new Promise < Element > ( resolve => {
123+ resolve ( this . configureHtmlSvgTag ( snapshot , false ) ) ;
124+ } ) ;
125+ }
126+
127+ return new Promise < string > ( resolve => {
128+ resolve ( txt ) ;
129+ } ) ;
130+ }
131+
132+ protected getContent ( elt : unknown ) : string | SafeHtml {
133+ if ( typeof elt === "string" ) {
134+ return elt ;
135+ }
136+ if ( elt instanceof Element ) {
137+ return this . sanitizer . bypassSecurityTrustHtml ( elt . outerHTML ) ;
138+ }
139+ return "" ;
140+ }
141+
142+ private configureHtmlSvgTag ( snapshot : Element | SVGElement , svg : boolean ) : Element {
143+ if ( svg ) {
144+ snapshot . setAttribute ( "viewBox" , `0 0 ${ String ( untracked ( this . svgViewportWidth ) ) } ${ String ( untracked ( this . svgViewportHeight ) ) } ` ) ;
145+ }
146+
147+ snapshot . setAttribute ( "pointer-events" , "none" ) ;
148+ snapshot . setAttribute ( "width" , String ( untracked ( this . cmdViewWidthPx ) ) ) ;
149+ snapshot . setAttribute ( "height" , String ( untracked ( this . cmdViewHeightPx ) ) ) ;
150+
151+ return snapshot ;
152+ }
153+
154+ private undoButtonSnapshot_ ( snapshot : unknown , txt : string ) : string | Element {
155+ if ( typeof snapshot === "string" ) {
156+ return `${ txt } : ${ snapshot } ` ;
157+ }
158+
159+ if ( snapshot instanceof SVGElement ) {
160+ return this . configureHtmlSvgTag ( snapshot , true ) ;
161+ }
162+
163+ if ( snapshot instanceof HTMLElement ) {
164+ return this . configureHtmlSvgTag ( snapshot , false ) ;
81165 }
82166
83- return command . getUndoName ( ) ;
167+ return txt ;
84168 }
85169}
0 commit comments