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