1
- import React from "react" ;
1
+ import React , { ReactNode } from "react" ;
2
2
import { ListGroup , ListGroupItem } from "reactstrap" ;
3
3
import { ContentDTO , ContentSummaryDTO } from "../../../IsaacApiTypes" ;
4
4
import { Link } from "react-router-dom" ;
5
5
import { DOCUMENT_TYPE , documentTypePathPrefix } from "../../services/constants" ;
6
6
import { connect } from "react-redux" ;
7
7
import { logAction } from "../../state/actions" ;
8
8
import { SITE , SITE_SUBJECT } from "../../services/siteConstants" ;
9
+ import { sortByNumberStringValue , sortByStringValue } from "../../services/sorting" ;
9
10
10
11
interface RelatedContentProps {
11
12
content : ContentSummaryDTO [ ] ;
12
13
parentPage : ContentDTO ;
13
14
logAction : ( eventDetails : object ) => void ;
14
15
}
15
16
17
+ type RenderItemFunction = ( contentSummary : ContentSummaryDTO , openInNewTab ?: boolean ) => ReactNode ;
18
+
16
19
function getEventDetails ( contentSummary : ContentSummaryDTO , parentPage : ContentDTO ) {
17
20
const eventDetails : any = { } ;
18
21
@@ -48,43 +51,51 @@ function getURLForContent(content: ContentSummaryDTO) {
48
51
return `/${ documentTypePathPrefix [ content . type as DOCUMENT_TYPE ] } /${ content . id } `
49
52
}
50
53
51
- export const RelatedContentComponent = ( { content , parentPage , logAction } : RelatedContentProps ) => {
52
- const concepts = content
53
- . filter ( ( contentSummary ) => contentSummary . type == DOCUMENT_TYPE . CONCEPT ) ;
54
+ function renderQuestions ( allQuestions : ContentSummaryDTO [ ] , renderItem : RenderItemFunction ) {
55
+ const evenQuestions = allQuestions . filter ( ( q , i ) => i % 2 == 0 ) ;
56
+ const oddQuestions = allQuestions . filter ( ( q , i ) => i % 2 == 1 ) ;
54
57
55
- const questions = content
56
- . filter ( ( contentSummary ) => contentSummary . type == DOCUMENT_TYPE . QUESTION )
57
- . sort ( ( a , b ) => {
58
- if ( a . level === b . level ) return ( ( a . title || '' ) . localeCompare ( ( b . title || '' ) , undefined , { numeric : true , sensitivity : 'base' } ) ) ;
59
- const aInt = parseInt ( a . level || '-1' ) ;
60
- const bInt = parseInt ( b . level || '-1' ) ;
61
- return aInt > bInt ? 1 : aInt != bInt ? - 1 : 0 ;
62
- } ) ;
63
-
64
- const makeListGroupItem = ( contentSummary : ContentSummaryDTO ) => (
65
- < ListGroupItem key = { getURLForContent ( contentSummary ) } className = "w-100 mr-lg-3" >
66
- < Link to = { getURLForContent ( contentSummary ) }
67
- onClick = { ( ) => { logAction ( getEventDetails ( contentSummary , parentPage ) ) } }
68
- >
69
- { contentSummary . title }
70
- { SITE_SUBJECT === SITE . PHY && contentSummary . level && contentSummary . level != '0' &&
71
- " (Level " + contentSummary . level + ")"
72
- }
73
- </ Link >
74
- </ ListGroupItem >
75
- ) ;
58
+ if ( allQuestions . length == 0 ) return null ;
59
+ return < div className = "d-flex align-items-stretch flex-wrap no-print" >
60
+ < div className = "w-100 d-flex" >
61
+ < div className = "flex-fill simple-card my-3 p-3 text-wrap" >
62
+ < div className = "related-questions related-title" >
63
+ < h5 className = "my-2" > Related questions</ h5 >
64
+ </ div >
65
+ < hr />
66
+ { /* Large devices - multi column */ }
67
+ < div className = "d-none d-lg-flex" >
68
+ < ListGroup className = "w-50" >
69
+ { evenQuestions . map ( contentSummary => renderItem ( contentSummary , SITE_SUBJECT == SITE . CS ) ) }
70
+ </ ListGroup >
71
+ < ListGroup className = "w-50" >
72
+ { oddQuestions . map ( contentSummary => renderItem ( contentSummary , SITE_SUBJECT == SITE . CS ) ) }
73
+ </ ListGroup >
74
+ </ div >
75
+ { /* Small devices - single column */ }
76
+ < div className = "d-lg-none" >
77
+ < ListGroup >
78
+ { allQuestions . map ( contentSummary => renderItem ( contentSummary , SITE_SUBJECT == SITE . CS ) ) }
79
+ </ ListGroup >
80
+ </ div >
81
+ </ div >
82
+ </ div >
83
+ </ div >
84
+ }
76
85
86
+ function renderConceptsAndQuestions ( concepts : ContentSummaryDTO [ ] , questions : ContentSummaryDTO [ ] , renderItem : RenderItemFunction ) {
87
+ if ( concepts . length == 0 && questions . length == 0 ) return null ;
77
88
return < div className = "d-flex align-items-stretch flex-wrap no-print" >
78
89
< div className = "w-100 w-lg-50 d-flex" >
79
90
< div className = "flex-fill simple-card mr-lg-3 my-3 p-3 text-wrap" >
80
91
< div className = "related-concepts related-title" >
81
- < h5 className = "mb-2" > Related concepts </ h5 >
92
+ < h5 className = "mb-2" > Related Concepts </ h5 >
82
93
</ div >
83
94
< hr />
84
95
< div className = "d-lg-flex" >
85
96
< ListGroup className = "mr-lg-3" >
86
97
{ concepts . length > 0 ?
87
- concepts . map ( contentSummary => makeListGroupItem ( contentSummary ) ) :
98
+ concepts . map ( contentSummary => renderItem ( contentSummary ) ) :
88
99
< div className = "mt-2 ml-3" > There are no related concepts</ div >
89
100
}
90
101
</ ListGroup >
@@ -94,20 +105,51 @@ export const RelatedContentComponent = ({content, parentPage, logAction}: Relate
94
105
< div className = "w-100 w-lg-50 d-flex" >
95
106
< div className = "flex-fill simple-card ml-lg-3 my-3 p-3 text-wrap" >
96
107
< div className = "related-questions related-title" >
97
- < h5 className = "mb-2" > Related questions </ h5 >
108
+ < h5 className = "mb-2" > Related Questions </ h5 >
98
109
</ div >
99
110
< hr />
100
111
< div className = "d-lg-flex" >
101
112
< ListGroup className = "mr-lg-3" >
102
113
{ questions . length > 0 ?
103
- questions . map ( contentSummary => makeListGroupItem ( contentSummary ) ) :
114
+ questions . map ( contentSummary => renderItem ( contentSummary , SITE_SUBJECT == SITE . CS ) ) :
104
115
< div className = "mt-2 ml-3" > There are no related questions</ div >
105
116
}
106
117
</ ListGroup >
107
118
</ div >
108
119
</ div >
109
120
</ div >
110
121
</ div >
122
+ }
123
+
124
+ export const RelatedContentComponent = ( { content, parentPage, logAction} : RelatedContentProps ) => {
125
+ // level, difficulty, title; all ascending (reverse the calls for required ordering)
126
+ const sortedContent = content
127
+ . sort ( sortByStringValue ( "title" ) )
128
+ . sort ( sortByNumberStringValue ( "difficulty" ) )
129
+ . sort ( sortByNumberStringValue ( "level" ) ) ;
130
+
131
+ const concepts = sortedContent
132
+ . filter ( ( contentSummary ) => contentSummary . type == DOCUMENT_TYPE . CONCEPT ) ;
133
+ const questions = sortedContent
134
+ . filter ( ( contentSummary ) => contentSummary . type == DOCUMENT_TYPE . QUESTION ) ;
135
+
136
+ const makeListGroupItem : RenderItemFunction = ( contentSummary : ContentSummaryDTO , openInNewTab ?: boolean ) => (
137
+ < ListGroupItem key = { getURLForContent ( contentSummary ) } className = "w-100 mr-lg-3" >
138
+ < Link
139
+ to = { getURLForContent ( contentSummary ) }
140
+ onClick = { ( ) => { logAction ( getEventDetails ( contentSummary , parentPage ) ) } }
141
+ target = { openInNewTab ? "_blank" : undefined }
142
+ >
143
+ { contentSummary . title }
144
+ { SITE_SUBJECT === SITE . PHY && contentSummary . level && contentSummary . level != '0' && " (Level " + contentSummary . level + ")" }
145
+ </ Link >
146
+ </ ListGroupItem >
147
+ ) ;
148
+
149
+ return {
150
+ [ SITE . PHY ] : renderConceptsAndQuestions ( concepts , questions , makeListGroupItem ) ,
151
+ [ SITE . CS ] : renderQuestions ( questions , makeListGroupItem )
152
+ } [ SITE_SUBJECT ] ;
111
153
} ;
112
154
113
155
export const RelatedContent = connect ( null , { logAction : logAction } ) ( RelatedContentComponent ) ;
0 commit comments