Skip to content
This repository was archived by the owner on Jul 19, 2022. It is now read-only.

Commit 8f06574

Browse files
committed
Catalog: Support searching for users
Add user search support to the catalog and a user icon
1 parent b0c2fe6 commit 8f06574

File tree

5 files changed

+183
-54
lines changed

5 files changed

+183
-54
lines changed

src/UI/Icon.elm

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,3 +379,12 @@ clipboard =
379379
[ path [ fill "currentColor", fillRule "evenodd", d "M8 2.25C8 2.11193 7.88807 2 7.75 2H6.25C6.11193 2 6 2.11193 6 2.25V2.75C6 2.88807 6.11193 3 6.25 3H7.75C7.88807 3 8 2.88807 8 2.75V2.25ZM6 1C5.44772 1 5 1.44772 5 2V3C5 3.55228 5.44772 4 6 4H8C8.55228 4 9 3.55228 9 3V2C9 1.44772 8.55228 1 8 1H6Z" ] []
380380
, path [ fill "currentColor", fillRule "evenodd", d "M3 2.5C3 2.22386 3.22386 2 3.5 2C3.77614 2 4 2.22386 4 2.5V10.5C4 10.7761 4.22386 11 4.5 11H9.5C9.77614 11 10 10.7761 10 10.5V2.5C10 2.22386 10.2239 2 10.5 2C10.7761 2 11 2.22386 11 2.5V11C11 11.5523 10.5523 12 10 12H4C3.44772 12 3 11.5523 3 11V2.5Z" ] []
381381
]
382+
383+
384+
user : Icon msg
385+
user =
386+
Icon "user"
387+
[]
388+
[ path [ fill "currentColor", d "M7 7C3.97669 7 1.47565 9.60877 1.06051 13.0021C0.99344 13.5503 1.44772 14 2 14H12C12.5523 14 13.0066 13.5503 12.9395 13.0021C12.5243 9.60877 10.0233 7 7 7Z" ] []
389+
, circle [ fill "currentColor", cx "7", cy "3", r "3" ] []
390+
]

src/UnisonShare/Catalog.elm

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -87,25 +87,6 @@ fromList items =
8787
-- HELPERS
8888

8989

90-
{-| Fuzzy search through a flattened catalog by project name and category
91-
-}
92-
search : Catalog -> String -> List ( ProjectListing, String )
93-
search catalog_ query =
94-
let
95-
flat ( category, projects ) acc =
96-
acc ++ List.map (\p -> ( p, category )) projects
97-
98-
normalize ( p, c ) =
99-
p
100-
|> Project.slugString
101-
|> (++) c
102-
in
103-
catalog_
104-
|> toList
105-
|> List.foldl flat []
106-
|> Fuzzy.filter normalize query
107-
108-
10990
isEmpty : Catalog -> Bool
11091
isEmpty (Catalog dict) =
11192
OrderedDict.isEmpty dict

src/UnisonShare/Page/Catalog.elm

Lines changed: 115 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -4,35 +4,45 @@ import Api
44
import Env exposing (Env)
55
import Html exposing (Html, div, h1, input, strong, table, tbody, td, text, tr)
66
import Html.Attributes exposing (autofocus, class, classList, placeholder)
7-
import Html.Events exposing (onBlur, onClick, onFocus, onInput)
7+
import Html.Events exposing (onBlur, onFocus, onInput, onMouseDown)
88
import Http
99
import KeyboardShortcut exposing (KeyboardShortcut(..))
1010
import KeyboardShortcut.Key as Key exposing (Key(..))
1111
import KeyboardShortcut.KeyboardEvent as KeyboardEvent exposing (KeyboardEvent)
12+
import List.Extra as ListE
13+
import Maybe.Extra as MaybeE
1214
import Perspective
1315
import Project exposing (ProjectListing)
1416
import RemoteData exposing (RemoteData(..), WebData)
1517
import SearchResults exposing (SearchResults(..))
18+
import Simple.Fuzzy as Fuzzy
1619
import Task
1720
import UI
1821
import UI.Card as Card
1922
import UI.Click as Click
2023
import UI.Icon as Icon
2124
import UI.PageLayout as PageLayout exposing (PageLayout)
2225
import UnisonShare.Catalog as Catalog exposing (Catalog)
26+
import UnisonShare.Catalog.CatalogMask exposing (CatalogMask)
2327
import UnisonShare.Route as Route
28+
import UnisonShare.User as User exposing (Username)
2429

2530

2631

2732
-- MODEL
2833

2934

30-
type alias SearchResult =
31-
( ProjectListing, String )
35+
type Match
36+
= UserMatch Username
37+
| ProjectMatch ProjectListing String
3238

3339

3440
type alias CatalogSearchResults =
35-
SearchResults SearchResult
41+
SearchResults Match
42+
43+
44+
45+
-- TODO: Rename
3646

3747

3848
type alias CatalogSearch =
@@ -43,6 +53,7 @@ type alias LoadedModel =
4353
{ search : CatalogSearch
4454
, hasFocus : Bool
4555
, catalog : Catalog
56+
, usernames : List Username
4657
, keyboardShortcut : KeyboardShortcut.Model
4758
}
4859

@@ -53,14 +64,14 @@ type alias Model =
5364

5465
init : Env -> ( Model, Cmd Msg )
5566
init env =
56-
( Loading, fetchCatalog env )
67+
( Loading, fetch env )
5768

5869

5970
{-| Fetch the Catalog in sequence by first fetching the doc, then the
6071
projectListings and finally merging them into a Catalog
6172
-}
62-
fetchCatalog : Env -> Cmd Msg
63-
fetchCatalog env =
73+
fetch : Env -> Cmd Msg
74+
fetch env =
6475
let
6576
perspective =
6677
Perspective.toCodebasePerspective env.perspective
@@ -73,8 +84,7 @@ fetchCatalog env =
7384
|> Api.toTask env.apiBasePath Project.decodeListings
7485
|> Task.map (\projects -> ( catalog, projects ))
7586
)
76-
|> Task.map (\( cm, ps ) -> Catalog.catalog cm ps)
77-
|> Task.attempt FetchCatalogFinished
87+
|> Task.attempt FetchFinished
7888

7989

8090

@@ -86,25 +96,34 @@ type Msg
8696
| UpdateFocus Bool
8797
| ClearQuery
8898
| SelectProject ProjectListing
89-
| FetchCatalogFinished (Result Http.Error Catalog)
99+
| SelectUser Username
100+
| FetchFinished (Result Http.Error ( CatalogMask, List ProjectListing ))
90101
| Keydown KeyboardEvent
91102
| KeyboardShortcutMsg KeyboardShortcut.Msg
92103

93104

94105
update : Env -> Msg -> Model -> ( Model, Cmd Msg )
95106
update env msg model =
96107
case ( msg, model ) of
97-
( FetchCatalogFinished catalogResult, _ ) ->
98-
case catalogResult of
108+
( FetchFinished result, _ ) ->
109+
case result of
99110
Err e ->
100111
( Failure e, Cmd.none )
101112

102-
Ok catalog ->
113+
Ok ( mask, listings ) ->
103114
let
115+
usernames =
116+
listings
117+
|> List.map (.owner >> Project.ownerToString)
118+
|> ListE.unique
119+
|> List.map User.usernameFromString
120+
|> MaybeE.values
121+
104122
initModel =
105123
{ search = { query = "", results = SearchResults.empty }
106124
, hasFocus = True
107-
, catalog = catalog
125+
, catalog = Catalog.catalog mask listings
126+
, usernames = usernames
108127
, keyboardShortcut = KeyboardShortcut.init env.operatingSystem
109128
}
110129
in
@@ -121,7 +140,7 @@ update env msg model =
121140

122141
else
123142
query
124-
|> Catalog.search m.catalog
143+
|> search m.catalog m.usernames
125144
|> SearchResults.fromList
126145
in
127146
( Success { m | search = { query = query, results = searchResults } }, Cmd.none )
@@ -132,6 +151,9 @@ update env msg model =
132151
( SelectProject project, Success m ) ->
133152
( Success m, Route.navigateToProject env.navKey project )
134153

154+
( SelectUser username, Success m ) ->
155+
( Success m, Route.navigateToUsername env.navKey username )
156+
135157
( Keydown event, Success m ) ->
136158
let
137159
( keyboardShortcut, kCmd ) =
@@ -174,8 +196,7 @@ update env msg model =
174196
navigate =
175197
matches
176198
|> SearchResults.focus
177-
|> Tuple.first
178-
|> Route.navigateToProject env.navKey
199+
|> matchToNavigate env
179200
in
180201
( Success newModel, Cmd.batch [ cmd, navigate ] )
181202

@@ -185,8 +206,7 @@ update env msg model =
185206
let
186207
navigate =
187208
SearchResults.getAt (n - 1) m.search.results
188-
|> Maybe.map Tuple.first
189-
|> Maybe.map (Route.navigateToProject env.navKey)
209+
|> Maybe.map (matchToNavigate env)
190210
|> Maybe.withDefault Cmd.none
191211
in
192212
( Success newModel, Cmd.batch [ cmd, navigate ] )
@@ -209,8 +229,49 @@ update env msg model =
209229

210230

211231
mapSearch : (CatalogSearchResults -> CatalogSearchResults) -> CatalogSearch -> CatalogSearch
212-
mapSearch f search =
213-
{ search | results = f search.results }
232+
mapSearch f search_ =
233+
{ search_ | results = f search_.results }
234+
235+
236+
matchToNavigate : Env -> Match -> Cmd Msg
237+
matchToNavigate env match =
238+
case match of
239+
UserMatch username ->
240+
Route.navigateToUsername env.navKey username
241+
242+
ProjectMatch project _ ->
243+
Route.navigateToProject env.navKey project
244+
245+
246+
toMatches : Catalog -> List Username -> List Match
247+
toMatches catalog users =
248+
let
249+
flat ( category, projects ) acc =
250+
acc ++ List.map (\p -> ProjectMatch p category) projects
251+
252+
projectMatches =
253+
catalog
254+
|> Catalog.toList
255+
|> List.foldl flat []
256+
257+
userMatches =
258+
List.map UserMatch users
259+
in
260+
userMatches ++ projectMatches
261+
262+
263+
search : Catalog -> List Username -> String -> List Match
264+
search catalog users query =
265+
let
266+
normalize m =
267+
case m of
268+
UserMatch u ->
269+
User.usernameToString u
270+
271+
ProjectMatch p _ ->
272+
Project.slugString p
273+
in
274+
Fuzzy.filter normalize query (toMatches catalog users)
214275

215276

216277

@@ -238,8 +299,11 @@ viewCategory ( category, projects ) =
238299
|> Card.view
239300

240301

241-
viewMatch : KeyboardShortcut.Model -> SearchResult -> Bool -> Maybe Key -> Html Msg
242-
viewMatch keyboardShortcut ( project, category ) isFocused shortcut =
302+
{-| View a match in the dropdown list. Use `onMouseDown` instead of `onClick`
303+
to avoid competing with `onBlur` on the input
304+
-}
305+
viewMatch : KeyboardShortcut.Model -> Match -> Bool -> Maybe Key -> Html Msg
306+
viewMatch keyboardShortcut match isFocused shortcut =
243307
let
244308
shortcutIndicator =
245309
if isFocused then
@@ -253,14 +317,31 @@ viewMatch keyboardShortcut ( project, category ) isFocused shortcut =
253317
Just key ->
254318
KeyboardShortcut.view keyboardShortcut (Sequence (Just Key.Semicolon) key)
255319
in
256-
tr
257-
[ classList [ ( "search-result", True ), ( "focused", isFocused ) ]
258-
, onClick (SelectProject project)
259-
]
260-
[ td [ class "project-name" ] [ Project.viewProjectListing Click.Disabled project ]
261-
, td [ class "category" ] [ text category ]
262-
, td [] [ div [ class "shortcut" ] [ shortcutIndicator ] ]
263-
]
320+
case match of
321+
UserMatch username ->
322+
tr
323+
[ classList [ ( "search-result", True ), ( "focused", isFocused ) ]
324+
, onMouseDown (SelectUser username)
325+
]
326+
[ td [ class "match-name" ]
327+
[ div [ class "user-listing" ]
328+
[ div [ class "avatar" ] [ Icon.view Icon.user ]
329+
, text (User.usernameToString username)
330+
]
331+
]
332+
, td [ class "category" ] [ text "User" ]
333+
, td [] [ div [ class "shortcut" ] [ shortcutIndicator ] ]
334+
]
335+
336+
ProjectMatch project category ->
337+
tr
338+
[ classList [ ( "search-result", True ), ( "focused", isFocused ) ]
339+
, onMouseDown (SelectProject project)
340+
]
341+
[ td [ class "match-name" ] [ Project.viewProjectListing Click.Disabled project ]
342+
, td [ class "category" ] [ text category ]
343+
, td [] [ div [ class "shortcut" ] [ shortcutIndicator ] ]
344+
]
264345

265346

266347
indexToShortcut : Int -> Maybe Key
@@ -276,7 +357,7 @@ indexToShortcut index =
276357
n |> String.fromInt |> Key.fromString |> Just
277358

278359

279-
viewMatches : KeyboardShortcut.Model -> SearchResults.Matches SearchResult -> Html Msg
360+
viewMatches : KeyboardShortcut.Model -> SearchResults.Matches Match -> Html Msg
280361
viewMatches keyboardShortcut matches =
281362
let
282363
matchItems =
@@ -295,7 +376,7 @@ viewSearchResults keyboardShortcut { query, results } =
295376
resultsPane =
296377
case results of
297378
Empty ->
298-
div [ class "empty-state" ] [ text ("No matching projects found for \"" ++ query ++ "\"") ]
379+
div [ class "empty-state" ] [ text ("No matches found for \"" ++ query ++ "\"") ]
299380

300381
SearchResults matches ->
301382
viewMatches keyboardShortcut matches
@@ -347,7 +428,7 @@ viewLoaded model =
347428
[ div [ class "search-field" ]
348429
[ Icon.view Icon.search
349430
, input
350-
[ placeholder "Search for projects"
431+
[ placeholder "Search for projects and users"
351432
, onInput UpdateQuery
352433
, autofocus True
353434
, onBlur (UpdateFocus False)

src/UnisonShare/Route.elm

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ module UnisonShare.Route exposing
1010
, navigateToLatestCodebase
1111
, navigateToPerspective
1212
, navigateToProject
13+
, navigateToUser
14+
, navigateToUsername
1315
, perspectiveParams
1416
, replacePerspective
1517
, toDefinition
@@ -328,6 +330,16 @@ navigateToProject navKey project =
328330
navigate navKey (forProject project)
329331

330332

333+
navigateToUser : Nav.Key -> User.User a -> Cmd msg
334+
navigateToUser navKey user_ =
335+
navigate navKey (forUser user_)
336+
337+
338+
navigateToUsername : Nav.Key -> User.Username -> Cmd msg
339+
navigateToUsername navKey username_ =
340+
navigate navKey (User username_)
341+
342+
331343

332344
-- TODO: this should go away in UnisonShare
333345

0 commit comments

Comments
 (0)