@@ -108,26 +108,44 @@ const IconItemContainer = styled.div`
108108 }
109109` ;
110110
111+ class Icon {
112+ readonly title : string ;
113+ constructor ( readonly def : IconDefinition , readonly names : string [ ] ) {
114+ this . title = def . iconName . split ( "-" ) . map ( _ . upperFirst ) . join ( " " ) ;
115+ }
116+ getView ( ) {
117+ return < FontAwesomeIcon icon = { this . def } style = { { width : "1em" , height : "1em" } } /> ;
118+ }
119+ }
120+
121+ let allIcons : Record < string , Icon > | undefined = undefined ;
122+
111123async function getAllIcons ( ) {
124+ if ( allIcons !== undefined ) {
125+ return allIcons ;
126+ }
112127 const [ { far } , { fas } ] = await Promise . all ( [
113128 import ( "@fortawesome/free-regular-svg-icons" ) ,
114129 import ( "@fortawesome/free-solid-svg-icons" ) ,
115130 ] ) ;
116-
117- return Object . fromEntries (
118- Object . entries ( { solid : fas , regular : far } ) . flatMap ( ( [ type , pack ] ) =>
119- Object . entries ( pack ) . map ( ( [ k , v ] ) => [ type + "/" + ( k . startsWith ( "fa" ) ? k . slice ( 2 ) : k ) , v ] )
120- )
121- ) ;
122- }
123-
124- function getName ( path : string ) {
125- return path . slice ( path . lastIndexOf ( "/" ) + 1 ) ;
126- }
127-
128- async function getAllPaths ( ) {
129- const allIcons = await getAllIcons ( ) ;
130- return Object . values ( _ . groupBy ( Object . keys ( allIcons ) , getName ) ) . flatMap ( ( v ) => v ) ;
131+ const ret : Record < string , Icon > = { } ;
132+ for ( const [ type , pack ] of Object . entries ( { solid : fas , regular : far } ) ) {
133+ const list = Object . entries ( pack ) ;
134+ for ( const [ k , def ] of list ) {
135+ ret [ type + "/" + def . iconName ] = new Icon ( def , [ def . iconName ] ) ;
136+ }
137+ for ( const [ k , def ] of list ) {
138+ const name = k . startsWith ( "fa" ) ? k . slice ( 2 ) : k ;
139+ ret [ type + "/" + def . iconName ] . names . push ( name ) ;
140+ // for compatibility of old data
141+ const key = type + "/" + name ;
142+ if ( ret [ key ] === undefined ) {
143+ ret [ key ] = new Icon ( def , [ ] ) ;
144+ }
145+ }
146+ }
147+ allIcons = ret ;
148+ return ret ;
131149}
132150
133151export const iconPrefix = "/icon:" ;
@@ -136,62 +154,43 @@ export function removeQuote(value?: string) {
136154 return value ? ( value . startsWith ( '"' ) && value . endsWith ( '"' ) ? value . slice ( 1 , - 1 ) : value ) : "" ;
137155}
138156
139- export function getIconPath ( value ?: string ) {
157+ function getIconKey ( value ?: string ) {
140158 const v = removeQuote ( value ) ;
141159 return v . startsWith ( iconPrefix ) ? v . slice ( iconPrefix . length ) : "" ;
142160}
143161
144- function getIconViewByPath ( allIcons : Record < string , IconDefinition > , path : string ) {
145- if ( ! path ) {
146- return ;
147- }
148- const icon = allIcons [ path ] ;
149- if ( icon !== undefined ) {
150- return < FontAwesomeIcon icon = { icon } style = { { width : "1em" , height : "1em" } } /> ;
151- }
152- }
153-
154- export function useIconViewByPath ( path : string ) {
155- const [ view , setView ] = useState < ReactNode > ( null ) ;
162+ export function useIcon ( value ?: string ) {
163+ const key = getIconKey ( value ) ;
164+ const [ icon , setIcon ] = useState < Icon | undefined > ( undefined ) ;
156165 useEffect ( ( ) => {
157- getAllIcons ( ) . then ( ( icons ) => {
158- setView ( getIconViewByPath ( icons , path ) ) ;
159- } ) ;
160- } , [ path ] ) ;
161- return view ;
166+ getAllIcons ( ) . then ( ( icons ) => setIcon ( icons [ key ] ) ) ;
167+ } , [ key ] ) ;
168+ return icon ;
162169}
163170
164- async function getIconViewByValue ( value ?: string ) {
165- const icons = await getAllIcons ( ) ;
166- return getIconViewByPath ( icons , getIconPath ( value ) ) ;
167- }
168-
169- export function useIconViewByValue ( value ?: string ) {
170- const [ view , setView ] = useState < ReactNode > ( null ) ;
171- useEffect ( ( ) => {
172- getIconViewByValue ( value ) . then ( ( v ) => setView ( v ) ) ;
173- } , [ value ] ) ;
174- return view ;
175- }
176-
177- export function getDescription ( path : string ) {
178- return getName ( path )
179- . replace ( / [ A - Z ] [ a - z ] + / g, ( s ) => " " + s + " " )
180- . replace ( / [ a - z ] [ A - Z ] / g, ( s ) => s [ 0 ] + " " + s [ 1 ] )
181- . replace ( / [ ] + / g, ( s ) => " " )
182- . trim ( ) ;
183- }
184-
185- function search ( allPaths : string [ ] , searchText : string , searchKeywords ?: Record < string , string > ) {
171+ function search (
172+ allIcons : Record < string , Icon > ,
173+ searchText : string ,
174+ searchKeywords ?: Record < string , string >
175+ ) {
186176 const tokens = searchText
187177 . toLowerCase ( )
188178 . split ( / \s + / g)
189179 . filter ( ( t ) => t ) ;
190- return allPaths . filter ( ( path ) => {
191- const name = getName ( path ) ;
192- const desc = name . toLowerCase ( ) + " " + ( searchKeywords ?. [ name ] ?? "" ) ;
193- return tokens . every ( ( t ) => desc . includes ( t ) ) ;
194- } ) ;
180+ return _ . sortBy (
181+ Object . entries ( allIcons ) . filter ( ( [ key , icon ] ) => {
182+ if ( icon . names . length === 0 ) {
183+ return false ;
184+ }
185+ let text = icon . names
186+ . flatMap ( ( name ) => [ name , searchKeywords ?. [ name ] ] )
187+ . filter ( ( t ) => t )
188+ . join ( " " ) ;
189+ text = ( icon . title + " " + text ) . toLowerCase ( ) ;
190+ return tokens . every ( ( t ) => text . includes ( t ) ) ;
191+ } ) ,
192+ ( [ key , icon ] ) => icon . title
193+ ) ;
195194}
196195
197196const IconPopup = ( props : {
@@ -201,40 +200,38 @@ const IconPopup = (props: {
201200 searchKeywords ?: Record < string , string > ;
202201} ) => {
203202 const [ searchText , setSearchText ] = useState ( "" ) ;
204- const [ allPaths , setAllPaths ] = useState < string [ ] > ( [ ] ) ;
205- const [ allIcons , setAllIcons ] = useState < Record < string , IconDefinition > > ( { } ) ;
203+ const [ allIcons , setAllIcons ] = useState < Record < string , Icon > > ( { } ) ;
206204 const searchResults = useMemo (
207- ( ) => search ( allPaths , searchText , props . searchKeywords ) ,
208- [ searchText , allPaths ]
205+ ( ) => search ( allIcons , searchText , props . searchKeywords ) ,
206+ [ searchText , allIcons ]
209207 ) ;
210208 const onChangeRef = useRef ( props . onChange ) ;
211209 onChangeRef . current = props . onChange ;
212- const onChangeIcon = useCallback ( ( path : string ) => onChangeRef . current ( iconPrefix + path ) , [ ] ) ;
210+ const onChangeIcon = useCallback ( ( key : string ) => onChangeRef . current ( iconPrefix + key ) , [ ] ) ;
213211 const columnNum = 8 ;
214212
215213 useEffect ( ( ) => {
216- getAllPaths ( ) . then ( setAllPaths ) ;
217214 getAllIcons ( ) . then ( setAllIcons ) ;
218215 } , [ ] ) ;
219216
220217 const rowRenderer = useCallback (
221218 ( p : ListRowProps ) => (
222219 < IconRow key = { p . key } style = { p . style } >
223- { searchResults . slice ( p . index * columnNum , ( p . index + 1 ) * columnNum ) . map ( ( path ) => (
220+ { searchResults . slice ( p . index * columnNum , ( p . index + 1 ) * columnNum ) . map ( ( [ key , icon ] ) => (
224221 < Tooltip
225- key = { path }
226- title = { getDescription ( path ) }
222+ key = { key }
223+ title = { icon . title }
227224 placement = "bottom"
228225 align = { { offset : [ 0 , - 7 , 0 , 0 ] } }
229226 destroyTooltipOnHide
230227 >
231228 < IconItemContainer
232229 tabIndex = { 0 }
233230 onClick = { ( ) => {
234- onChangeIcon ( path ) ;
231+ onChangeIcon ( key ) ;
235232 } }
236233 >
237- { getIconViewByPath ( allIcons , path ) }
234+ { icon . getView ( ) }
238235 </ IconItemContainer >
239236 </ Tooltip >
240237 ) ) }
0 commit comments