@@ -10,6 +10,7 @@ import ErrorMessage from "./ErrorMessage";
1010import PencilIcon from "./icons/PencilIcon" ;
1111import TrashIcon from "./icons/TrashIcon" ;
1212import VisibleIcon from "./icons/VisibleIcon" ;
13+ import DisclosureIcon from "./icons/DisclosureIcon" ;
1314import Admin from "../models/Admin" ;
1415import * as PropTypes from "prop-types" ;
1516
@@ -76,11 +77,15 @@ export interface ExtraFormSectionProps<T, U> {
7677
7778 GenericEditableConfigList allows subclasses to define additional props. Subclasses of
7879 EditableConfigList cannot change the props and do not have to specify a type for them. */
80+ interface EditableConfigListState {
81+ expandedItems : Record < string , boolean > ;
82+ }
83+
7984export abstract class GenericEditableConfigList <
8085 T ,
8186 U ,
8287 V extends EditableConfigListProps < T >
83- > extends React . Component < V > {
88+ > extends React . Component < V , EditableConfigListState > {
8489 context : { admin : Admin } ;
8590 static contextTypes = {
8691 admin : PropTypes . object . isRequired ,
@@ -106,12 +111,18 @@ export abstract class GenericEditableConfigList<
106111 extraFormKey ?: string ;
107112 private editFormRef = React . createRef < any > ( ) ;
108113
114+ state : EditableConfigListState = {
115+ expandedItems : { } ,
116+ } ;
117+
109118 constructor ( props ) {
110119 super ( props ) ;
111120 this . editItem = this . editItem . bind ( this ) ;
112121 this . save = this . save . bind ( this ) ;
113122 this . label = this . label . bind ( this ) ;
114123 this . renderLi = this . renderLi . bind ( this ) ;
124+ this . toggleLibraries = this . toggleLibraries . bind ( this ) ;
125+ this . toggleAllLibraries = this . toggleAllLibraries . bind ( this ) ;
115126 }
116127
117128 UNSAFE_componentWillMount ( ) {
@@ -221,38 +232,76 @@ export abstract class GenericEditableConfigList<
221232
222233 renderLi ( item , index ) : JSX . Element {
223234 const AdditionalContent = this . AdditionalContent || null ;
235+ const libraries : Array < { short_name : string } > | undefined =
236+ item ?. libraries ;
237+ const libraryCount = Array . isArray ( libraries ) ? libraries . length : null ;
238+ const itemKey = String ( item [ this . identifierKey ] ) ;
239+ const isExpanded = libraryCount > 0 && ! ! this . state . expandedItems [ itemKey ] ;
224240
225241 return (
226242 < li key = { index } >
227- < a
228- className = "btn small edit-item"
229- href = { this . urlBase + "edit/" + item [ this . identifierKey ] }
230- >
231- { this . canEdit ( item ) ? (
232- < span >
233- Edit < PencilIcon />
234- </ span >
235- ) : (
243+ < div className = "item-header" >
244+ < a
245+ className = "btn small edit-item"
246+ href = { this . urlBase + "edit/" + item [ this . identifierKey ] }
247+ >
248+ { this . canEdit ( item ) ? (
249+ < span >
250+ Edit < PencilIcon />
251+ </ span >
252+ ) : (
253+ < span >
254+ View < VisibleIcon />
255+ </ span >
256+ ) }
257+ </ a >
258+
259+ < h3 >
260+ { libraryCount !== null && (
261+ < button
262+ className = "library-toggle"
263+ onClick = { ( e ) =>
264+ e . altKey
265+ ? this . toggleAllLibraries ( )
266+ : this . toggleLibraries ( itemKey )
267+ }
268+ aria-expanded = { isExpanded }
269+ disabled = { libraryCount === 0 }
270+ >
271+ < DisclosureIcon expanded = { isExpanded } />
272+ </ button >
273+ ) }
236274 < span >
237- View < VisibleIcon />
275+ { this . label ( item ) }
276+ { libraryCount !== null && (
277+ < span className = "library-count" >
278+ { " " }
279+ (
280+ { libraryCount === 0
281+ ? "no libraries"
282+ : libraryCount === 1
283+ ? "1 library"
284+ : `${ libraryCount } libraries` }
285+ )
286+ </ span >
287+ ) }
238288 </ span >
289+ </ h3 >
290+
291+ { this . canDelete ( ) && (
292+ < Button
293+ className = "danger delete-item small"
294+ callback = { ( ) => this . delete ( item ) }
295+ content = {
296+ < span >
297+ Delete
298+ < TrashIcon />
299+ </ span >
300+ }
301+ />
239302 ) }
240- </ a >
241-
242- < h3 > { this . label ( item ) } </ h3 >
243-
244- { this . canDelete ( ) && (
245- < Button
246- className = "danger delete-item small"
247- callback = { ( ) => this . delete ( item ) }
248- content = {
249- < span >
250- Delete
251- < TrashIcon />
252- </ span >
253- }
254- />
255- ) }
303+ </ div >
304+ { isExpanded && this . renderAssociatedLibraries ( item ) }
256305 { AdditionalContent && (
257306 < AdditionalContent
258307 type = { this . itemTypeName }
@@ -265,6 +314,63 @@ export abstract class GenericEditableConfigList<
265314 ) ;
266315 }
267316
317+ toggleLibraries ( itemKey : string ) : void {
318+ this . setState ( ( prev ) => ( {
319+ expandedItems : {
320+ ...prev . expandedItems ,
321+ [ itemKey ] : ! prev . expandedItems [ itemKey ] ,
322+ } ,
323+ } ) ) ;
324+ }
325+
326+ toggleAllLibraries ( ) : void {
327+ const items : any [ ] = ( this . props . data as any ) ?. [ this . listDataKey ] || [ ] ;
328+ const itemsWithLibraries = items . filter (
329+ ( item ) => Array . isArray ( item . libraries ) && item . libraries . length > 0
330+ ) ;
331+ if ( itemsWithLibraries . length === 0 ) return ;
332+
333+ const anyCollapsed = itemsWithLibraries . some (
334+ ( item ) => ! this . state . expandedItems [ String ( item [ this . identifierKey ] ) ]
335+ ) ;
336+
337+ const newExpandedItems : Record < string , boolean > = { } ;
338+ if ( anyCollapsed ) {
339+ for ( const item of itemsWithLibraries ) {
340+ newExpandedItems [ String ( item [ this . identifierKey ] ) ] = true ;
341+ }
342+ }
343+ this . setState ( { expandedItems : newExpandedItems } ) ;
344+ }
345+
346+ renderAssociatedLibraries ( item : any ) : JSX . Element {
347+ const libraries : Array < { short_name : string } > = item . libraries ;
348+ const allLibraries : Array < {
349+ short_name : string ;
350+ name ?: string ;
351+ uuid ?: string ;
352+ } > = ( this . props . data as any ) ?. allLibraries || [ ] ;
353+
354+ return (
355+ < ul className = "associated-libraries" >
356+ { libraries . map ( ( lib ) => {
357+ const libraryData = allLibraries . find (
358+ ( l ) => l . short_name === lib . short_name
359+ ) ;
360+ const name = libraryData ?. name || lib . short_name ;
361+ const href = libraryData ?. uuid
362+ ? `/admin/web/config/libraries/edit/${ libraryData . uuid } `
363+ : null ;
364+ return (
365+ < li key = { lib . short_name } >
366+ { href ? < a href = { href } > { name } </ a > : name }
367+ </ li >
368+ ) ;
369+ } ) }
370+ </ ul >
371+ ) ;
372+ }
373+
268374 label ( item ) : string {
269375 return item [ this . labelKey ] ;
270376 }
0 commit comments