@@ -2,16 +2,17 @@ import * as React from 'react';
22import { Attachment } from 'botframework-directlinejs' ;
33import { AttachmentView } from './Attachment' ;
44import { FormatState } from './Store' ;
5+ import { konsole } from './Chat' ;
56
67export interface CarouselProps {
78 format : FormatState ,
8- measureParentHorizontalOverflow ?: ( ) => number ,
99 attachments : Attachment [ ] ,
1010 onCardAction : ( type : string , value : string ) => void ,
11- onImageLoad : ( ) => void
11+ onImageLoad : ( ) => void
1212}
1313
1414export interface CarouselState {
15+ contentWidth : number ;
1516 previousButtonEnabled : boolean ;
1617 nextButtonEnabled : boolean ;
1718}
@@ -23,14 +24,14 @@ export class Carousel extends React.Component<CarouselProps, CarouselState> {
2324 private scrollSyncTimer : number ;
2425 private scrollDurationTimer : number ;
2526 private animateDiv : HTMLDivElement ;
26- private resizeListener = ( ) => this . resize ( ) ;
27- private scrollEventListener = ( ) => this . onScroll ( ) ;
27+ private scrollEventListener = ( ) => this . onScroll ( ) ;
2828 private scrollAllowInterrupt = true ;
2929
3030 constructor ( props : CarouselProps ) {
3131 super ( props ) ;
3232
3333 this . state = {
34+ contentWidth : undefined ,
3435 previousButtonEnabled : false ,
3536 nextButtonEnabled : false
3637 } ;
@@ -50,33 +51,55 @@ export class Carousel extends React.Component<CarouselProps, CarouselState> {
5051 this . scrollAllowInterrupt = true ;
5152 }
5253
53- private manageScrollButtons ( ) {
54- const previousEnabled = this . scrollDiv . scrollLeft > 0 ;
55- const max = this . scrollDiv . scrollWidth - this . scrollDiv . offsetWidth ;
56- const nextEnabled = this . scrollDiv . scrollLeft < max ;
57-
58- //TODO: both buttons may become disabled when the container is wide, and will not become re-enabled unless a resize event calls manageScrollButtons()
59- const newState : CarouselState = {
60- previousButtonEnabled : previousEnabled ,
61- nextButtonEnabled : nextEnabled
54+ private getScrollButtonState ( ) {
55+ return {
56+ previousButtonEnabled : this . scrollDiv . scrollLeft > 0 ,
57+ nextButtonEnabled : this . scrollDiv . scrollLeft < this . scrollDiv . scrollWidth - this . scrollDiv . offsetWidth
6258 } ;
59+ }
6360
64- this . setState ( newState ) ;
61+ private manageScrollButtons ( ) {
62+ this . setState ( this . getScrollButtonState ( ) ) ;
6563 }
6664
67- private componentDidMount ( ) {
65+ componentDidMount ( ) {
6866 this . manageScrollButtons ( ) ;
6967
7068 this . scrollDiv . addEventListener ( 'scroll' , this . scrollEventListener ) ;
7169
7270 this . scrollDiv . style . marginBottom = - ( this . scrollDiv . offsetHeight - this . scrollDiv . clientHeight ) + 'px' ;
71+ }
72+
73+ componentDidUpdate ( ) {
74+ konsole . log ( 'carousel componentDidUpdate' ) ;
75+
76+ if ( this . props . format . carouselMargin != undefined ) {
77+ //after the attachments have been rendered, we can now measure their actual width
78+ if ( this . state . contentWidth == undefined ) {
79+ this . root . style . width = '' ;
80+ this . setState ( { contentWidth : this . root . offsetWidth } ) ;
81+ } else {
82+ //compare scroll state to desired scroll state
83+ var desiredButtonState = this . getScrollButtonState ( ) ;
84+ if ( desiredButtonState . nextButtonEnabled != this . state . nextButtonEnabled
85+ || desiredButtonState . previousButtonEnabled != this . state . previousButtonEnabled ) {
86+ this . setState ( desiredButtonState ) ;
87+ }
88+ }
89+ }
90+ }
91+
92+ componentWillReceiveProps ( nextProps : CarouselProps ) {
93+ konsole . log ( 'carousel componentWillReceiveProps' ) ;
7394
74- window . addEventListener ( 'resize' , this . resizeListener ) ;
95+ if ( this . props . format . chatWidth != nextProps . format . chatWidth ) {
96+ //this will invalidate the saved measurement, in componentDidUpdate a new measurement will be triggered
97+ this . setState ( { contentWidth : undefined } ) ;
98+ }
7599 }
76100
77- private componentWillUnmount ( ) {
101+ componentWillUnmount ( ) {
78102 this . scrollDiv . removeEventListener ( 'scroll' , this . scrollEventListener ) ;
79- window . removeEventListener ( 'resize' , this . resizeListener ) ;
80103 }
81104
82105 private onScroll ( ) {
@@ -145,20 +168,29 @@ export class Carousel extends React.Component<CarouselProps, CarouselState> {
145168 } , 1 ) ;
146169 }
147170
171+ private getMaxMessageContentWidth ( ) {
172+ if ( this . props . format . chatWidth != undefined && this . props . format . carouselMargin != undefined )
173+ return this . props . format . chatWidth - this . props . format . carouselMargin ;
174+ }
175+
148176 render ( ) {
177+ let style : React . CSSProperties ;
178+ const maxMessageContentWidth = this . getMaxMessageContentWidth ( ) ;
179+
180+ if ( maxMessageContentWidth && this . state . contentWidth > maxMessageContentWidth ) {
181+ style = { width : maxMessageContentWidth }
182+ }
183+
149184 return (
150- < div className = "wc-carousel" ref = { div => this . root = div } >
151- < button disabled = { ! this . state . previousButtonEnabled } className = "scroll previous" onClick = { ( ) => this . scrollBy ( - 1 ) } >
185+ < div className = "wc-carousel" ref = { div => this . root = div } style = { style } >
186+ < button disabled = { ! this . state . previousButtonEnabled } className = "scroll previous" onClick = { ( ) => this . scrollBy ( - 1 ) } >
152187 < svg >
153188 < path d = "M 16.5 22 L 19 19.5 L 13.5 14 L 19 8.5 L 16.5 6 L 8.5 14 L 16.5 22 Z" />
154189 </ svg >
155190 </ button >
156191 < div className = "wc-carousel-scroll-outer" >
157192 < div className = "wc-carousel-scroll" ref = { div => this . scrollDiv = div } >
158- < CarouselAttachments
159- { ... this . props }
160- onImageLoad = { ( ) => this . resize ( ) }
161- />
193+ < CarouselAttachments { ... this . props } />
162194 </ div >
163195 </ div >
164196 < button disabled = { ! this . state . nextButtonEnabled } className = "scroll next" onClick = { ( ) => this . scrollBy ( 1 ) } >
@@ -169,45 +201,29 @@ export class Carousel extends React.Component<CarouselProps, CarouselState> {
169201 </ div >
170202 )
171203 }
172-
173- resize ( ) {
174-
175- //remove the style width so that the actual content can be measured
176- this . root . style . width = '' ;
177-
178- if ( this . props . measureParentHorizontalOverflow ) {
179- const overflow = this . props . measureParentHorizontalOverflow ( ) ;
180- if ( overflow > 0 ) {
181- this . root . style . width = ( this . root . offsetWidth - overflow ) + 'px' ;
182- }
183- }
184-
185- this . manageScrollButtons ( ) ;
186- this . props . onImageLoad ( ) ;
187- }
188204}
189205
190206export interface CarouselAttachmentProps {
191207 format : FormatState
192208 attachments : Attachment [ ]
193209 onCardAction : ( type : string , value : string ) => void
194- onImageLoad : ( ) => void
210+ onImageLoad : ( ) => void
195211}
196212
197213class CarouselAttachments extends React . Component < CarouselAttachmentProps , { } > {
198214
215+ shouldComponentUpdate ( nextProps : CarouselAttachmentProps ) {
216+ return this . props . attachments != this . props . attachments || this . props . format != nextProps . format ;
217+ }
218+
199219 render ( ) {
220+ const { attachments, ... props } = this . props ;
200221 return (
201- < ul > { this . props . attachments . map ( ( attachment , index ) =>
222+ < ul > { this . props . attachments . map ( ( attachment , index ) =>
202223 < li key = { index } className = "wc-carousel-item" >
203- < AttachmentView
204- attachment = { attachment }
205- format = { this . props . format }
206- onCardAction = { this . props . onCardAction }
207- onImageLoad = { ( ) => this . props . onImageLoad ( ) }
208- />
224+ < AttachmentView attachment = { attachment } { ... props } />
209225 </ li >
210- ) } </ ul >
226+ ) } </ ul >
211227 ) ;
212228 }
213229}
0 commit comments