22import { useUniqueIdWithMock , View } from "@khanacademy/wonder-blocks-core" ;
33import { OptionItem , MultiSelect } from "@khanacademy/wonder-blocks-dropdown" ;
44import { Strut } from "@khanacademy/wonder-blocks-layout" ;
5+ import SearchField from "@khanacademy/wonder-blocks-search-field" ;
56import Spacing from "@khanacademy/wonder-blocks-spacing" ;
67import Switch from "@khanacademy/wonder-blocks-switch" ;
78import { color } from "@khanacademy/wonder-blocks-tokens" ;
89import { css , StyleSheet } from "aphrodite" ;
910import * as React from "react" ;
10- import { useState } from "react" ;
11+ import { useEffect , useMemo , useState } from "react" ;
1112
1213import { Renderer } from "../packages/perseus/src" ;
1314import * as grapher from "../packages/perseus/src/widgets/__testdata__/grapher.testdata" ;
@@ -76,34 +77,79 @@ const styles = StyleSheet.create({
7677
7778export function Gallery ( ) {
7879 const ids = useUniqueIdWithMock ( ) ;
80+ const params = useMemo (
81+ ( ) => new URLSearchParams ( window . location . search ) ,
82+ [ ] ,
83+ ) ;
84+
85+ const [ isMobile , setIsMobile ] = useState ( params . get ( "mobile" ) === "true" ) ;
86+ const [ mafsFlags , setMafsFlags ] = useState < Array < string > > (
87+ params . get ( "flags" ) ?. split ( "," ) || [ ] ,
88+ ) ;
89+ const [ search , setSearch ] = useState < string > ( params . get ( "search" ) || "" ) ;
7990
80- const [ isMobile , setIsMobile ] = useState ( false ) ;
81- const [ mafsFlags , setMafsFlags ] = useState < Array < string > > ( [ ] ) ;
91+ useEffect ( ( ) => {
92+ const url = new URL ( window . location . href ) ;
93+ if ( isMobile ) {
94+ url . searchParams . set ( "mobile" , "true" ) ;
95+ } else {
96+ url . searchParams . delete ( "mobile" ) ;
97+ }
98+ if ( mafsFlags . length === 0 ) {
99+ url . searchParams . delete ( "flags" ) ;
100+ } else {
101+ url . searchParams . set ( "flags" , mafsFlags . join ( "," ) ) ;
102+ }
103+ if ( ! search ) {
104+ url . searchParams . delete ( "search" ) ;
105+ } else {
106+ url . searchParams . set ( "search" , search ) ;
107+ }
108+ window . history . replaceState ( { } , "" , url . toString ( ) ) ;
109+ } , [ isMobile , mafsFlags , params , search ] ) ;
82110
83111 const mafsFlagsObject = mafsFlags . reduce ( ( acc , flag ) => {
84112 acc [ flag ] = true ;
85113 return acc ;
86114 } , { } ) ;
87115
116+ const mobileId = ids . get ( "mobile" ) ;
117+ const flagsId = ids . get ( "flags" ) ;
118+ const searchId = ids . get ( "search" ) ;
119+
88120 return (
89121 < View className = { css ( styles . page ) } >
90122 < header className = { css ( styles . header ) } >
91123 < Switch
92- id = { ids . get ( "mobile" ) }
124+ id = { mobileId }
93125 checked = { isMobile }
94126 onChange = { setIsMobile }
95127 />
96128 < Strut size = { Spacing . xSmall_8 } />
97- < label htmlFor = { ids . get ( "mobile" ) } > Mobile</ label >
129+ < label htmlFor = { mobileId } > Mobile</ label >
98130 < Strut size = { Spacing . medium_16 } />
99- < MultiSelect onChange = { setMafsFlags } selectedValues = { mafsFlags } >
131+ < MultiSelect
132+ id = { flagsId }
133+ onChange = { setMafsFlags }
134+ selectedValues = { mafsFlags }
135+ >
100136 < OptionItem value = "segment" label = "Segment" />
101137 < OptionItem value = "linear" label = "Linear" />
102138 < OptionItem value = "linear-system" label = "Linear System" />
103139 < OptionItem value = "point" label = "Point" />
104140 < OptionItem value = "ray" label = "Ray" />
105141 < OptionItem value = "polygon" label = "Polygon" />
106142 </ MultiSelect >
143+ < Strut size = { Spacing . xSmall_8 } />
144+ < label htmlFor = { flagsId } > Mafs Flags</ label >
145+ < Strut size = { Spacing . medium_16 } />
146+ < SearchField
147+ id = { searchId }
148+ value = { search }
149+ onChange = { setSearch }
150+ />
151+ < Strut size = { Spacing . xSmall_8 } />
152+ < label htmlFor = { searchId } > Search Types</ label >
107153 < Strut size = { Spacing . medium_16 } />
108154 < nav >
109155 < a href = "#flipbook" > Flipbook</ a >
@@ -114,16 +160,22 @@ export function Gallery() {
114160 style = { styles . cards }
115161 className = { isMobile ? "perseus-mobile" : "" }
116162 >
117- { questions . map ( ( question , i ) => (
118- < QuestionRenderer
119- key = { i }
120- question = { question }
121- apiOptions = { {
122- isMobile,
123- flags : { mafs : mafsFlagsObject } ,
124- } }
125- />
126- ) ) }
163+ { questions
164+ . filter ( ( question ) =>
165+ search
166+ ? graphTypeContainsText ( question , search )
167+ : true ,
168+ )
169+ . map ( ( question , i ) => (
170+ < QuestionRenderer
171+ key = { i }
172+ question = { question }
173+ apiOptions = { {
174+ isMobile,
175+ flags : { mafs : mafsFlagsObject } ,
176+ } }
177+ />
178+ ) ) }
127179 </ View >
128180 </ main >
129181 </ View >
@@ -150,3 +202,31 @@ function QuestionRenderer({question, apiOptions = {}}: QuestionRendererProps) {
150202 </ div >
151203 ) ;
152204}
205+
206+ const graphTypeContainsText = (
207+ question : PerseusRenderer ,
208+ search : string ,
209+ ) : boolean => {
210+ const widgetKey = Object . keys ( question . widgets ) [ 0 ] ;
211+ const widget = question . widgets [ widgetKey ] ;
212+ switch ( widget . type ) {
213+ case "grapher" :
214+ if (
215+ widget . options . availableTypes . some ( ( type : string ) =>
216+ type . includes ( search ) ,
217+ )
218+ ) {
219+ return true ;
220+ }
221+ return false ;
222+ case "interactive-graph" :
223+ if ( widget . options . graph . type . includes ( search ) ) {
224+ return true ;
225+ }
226+ return false ;
227+ case "number-line" :
228+ return widget . type . includes ( search ) ;
229+ default :
230+ return false ;
231+ }
232+ } ;
0 commit comments