@@ -1,26 +1,27 @@
11port module Main exposing (main)
22
33import Animator
4+import Browser.Events exposing (onAnimationFrameDelta)
45import Dict exposing (Dict)
56import Element exposing (..)
67import Element.Background as Background
78import Element.Border as Border
8−import Element.Events as Events
9+import Element.Events as Events exposing (onClick)
910import Element.Font as Font
1011import Element.Input as Input
1112import Element.Keyed as Keyed
12−import Heroicons.Outline
1313import Html.Attributes
14−import Html.Events
1514import Json.Decode as JD
1615import List.Extra
17−import Svg.Attributes exposing (opacity)
16+import Set exposing (Set)
1817import Time exposing (Posix)
1918import Ur
2019import Ur.Cmd
2120import Ur.Deconstructor as D
21+import Ur.Jam exposing (isSig)
2222import Ur.Run
2323import Ur.Sub
24+import Ur.Types exposing (Noun(..))
2425
2526
2627url : String
@@ -28,18 +29,36 @@ url =
2829 "http://localhost:8080"
2930
3031
32+type SearchEngineState
33+ = Loading
34+ | Failed
35+ | Completed
36+
37+
38+deconstructSearchEngineState : D.Deconstructor SearchEngineState
39+deconstructSearchEngineState =
40+ D.oneOf
41+ [ D.const D.cord "loading" |> D.map (\_ -> Loading)
42+ , D.const D.cord "failed" |> D.map (\_ -> Failed)
43+ , D.const D.cord "completed" |> D.map (\_ -> Completed)
44+ ]
45+
46+
3147type alias Model =
3248 { ship : Maybe String
3349 , search : String
3450 , initiatedSearch : Maybe String
3551 , searchResults : List SearchResult
3652 , resultTimelines : Dict String ( Animator.Timeline Int, Animator.Timeline Bool )
53+ , engines : List ( String, SearchEngineState )
54+ , selectedEngines : Set String
3755 }
3856
3957
4058type alias SearchResult =
4159 { title : String
4260 , link : String
61+ , engines : Set String
4362 }
4463
4564
@@ -48,9 +67,10 @@ type Msg
4867 | GotShipName String
4968 | UpdateSearch String
5069 | Search String
51− | UpdateSearchResults (List SearchResult)
70+ | UpdateSearchResults (List SearchResult) (List ( String, SearchEngineState ))
5271 | OpenResult String
5372 | Tick Posix
73+ | ToggleSearchEngine String
5474
5575
5676main : Ur.Run.Program Model Msg
@@ -59,9 +79,11 @@ main =
5979 { init =
6080 ( { ship = Nothing
6181 , search = ""
62− , initiatedSearch = Nothing
82+ , initiatedSearch = Just "urbit"
6383 , searchResults = []
6484 , resultTimelines = Dict.empty
85+ , engines = []
86+ , selectedEngines = Set.empty
6587 }
6688 , Cmd.batch
6789 [ Ur.logIn url "lidlut-tabwed-pillex-ridrup"
@@ -83,13 +105,19 @@ main =
83105 , path = [ "search", query ]
84106 , ship = ship_
85107 , deconstructor =
86− D.list (D.cell D.cord D.cord)
108+ D.cell
109+ (D.list (D.cell D.cord deconstructSearchEngineState))
110+ (D.list (D.cell (D.list D.cord) (D.cell D.cord D.cord)))
87111 |> D.map
88− (List.map
89− (\( title, link ) ->
90− { title = title, link = link }
91− )
92− >> UpdateSearchResults
112+ (\( engines, results ) ->
113+ UpdateSearchResults
114+ (List.map
115+ (\( resultEngines, ( title, link ) ) ->
116+ { engines = Set.fromList resultEngines, title = title, link = link }
117+ )
118+ results
119+ )
120+ engines
93121 )
94122 }
95123
@@ -100,6 +128,42 @@ main =
100128 }
101129
102130
131+debug : (String -> String) -> D.Deconstructor a -> D.Deconstructor a
132+debug log f noun =
133+ case f noun of
134+ Just x ->
135+ Just x
136+
137+ Nothing ->
138+ let
139+ _ =
140+ log (prettyNoun noun)
141+ in
142+ Nothing
143+
144+
145+prettyNoun : Noun -> String
146+prettyNoun noun =
147+ let
148+ go isRhs n =
149+ case n of
150+ Atom a ->
151+ if isSig a then
152+ "~"
153+
154+ else
155+ "@"
156+
157+ Cell ( lhs, rhs ) ->
158+ if isRhs then
159+ go False lhs ++ " " ++ go True rhs
160+
161+ else
162+ "[" ++ go False lhs ++ " " ++ go True rhs ++ "]"
163+ in
164+ go False noun
165+
166+
103167view : Model -> Element Msg
104168view model =
105169 case model.initiatedSearch of
@@ -110,10 +174,65 @@ view model =
110174 ]
111175
112176 Just query ->
113− column [ width fill, padding 8, spacing 16 ]
114− [ searchView model
115− , Keyed.column [ width fill ]
177+ column [ width fill, spacing 16 ]
178+ [ row [ spacing 16, width fill ]
179+ [ searchView model |> el [ padding 8 ]
180+ , model.engines
181+ |> List.filter (\( _, state ) -> state /= Failed)
182+ |> List.map
183+ (\( name, state ) ->
184+ text ("%" ++ name)
185+ |> el
186+ ([ Font.size 43
187+ , Font.bold
188+ ]
189+ ++ (if Set.isEmpty model.selectedEngines then
190+ []
191+
192+ else if Set.member name model.selectedEngines then
193+ []
194+
195+ else
196+ [ Font.color (rgb 0.8 0.8 0.8)
197+ ]
198+ )
199+ ++ (case state of
200+ Loading ->
201+ [ Html.Attributes.class "bobbing" |> htmlAttribute
202+ , Font.color (rgb 0.8 0.8 0.8)
203+ ]
204+
205+ Completed ->
206+ [ mouseOver
207+ [ Font.color (rgb 0.6 0.6 0.6)
208+ ]
209+ , pointer
210+ , Events.onClick (ToggleSearchEngine name)
211+ ]
212+
213+ Failed ->
214+ []
215+ )
216+ )
217+ )
218+ |> row
219+ [ spacing 16
220+ , clipX
221+ , scrollbarX
222+ , width fill
223+ , height (px 62)
224+ ]
225+ ]
226+ , Keyed.column [ width fill, padding 8 ]
116227 (model.searchResults
228+ |> List.filter
229+ (\{ engines } ->
230+ if Set.isEmpty model.selectedEngines then
231+ True
232+
233+ else
234+ Set.intersect engines model.selectedEngines |> Set.isEmpty |> not
235+ )
117236 |> List.map
118237 (\{ title, link } ->
119238 ( link
@@ -172,7 +291,7 @@ view model =
172291searchView : { a | search : String } -> Element Msg
173292searchView model =
174293 row [ spacing 8 ]
175− [ Input.text []
294+ [ Input.text [ width (px 250) ]
176295 { onChange = UpdateSearch
177296 , placeholder = Nothing
178297 , text = model.search
@@ -214,7 +333,7 @@ update msg model =
214333 Search query ->
215334 ( { model | initiatedSearch = Just query }, Ur.Cmd.none )
216335
217− UpdateSearchResults results ->
336+ UpdateSearchResults results engines ->
218337 let
219338 foo =
220339 List.Extra.zip (List.range 0 (List.length results - 1)) results
@@ -231,7 +350,13 @@ update msg model =
231350 )
232351 |> Dict.fromList
233352 in
234− ( { model | resultTimelines = foo, searchResults = results }, Ur.Cmd.none )
353+ ( { model
354+ | resultTimelines = foo
355+ , searchResults = results
356+ , engines = engines
357+ }
358+ , Ur.Cmd.none
359+ )
235360
236361 OpenResult url_ ->
237362 ( model, Ur.Cmd.none )
@@ -239,6 +364,13 @@ update msg model =
239364 Tick time ->
240365 ( Animator.update time (animator model) model, Ur.Cmd.none )
241366
367+ ToggleSearchEngine name ->
368+ if Set.member name model.selectedEngines then
369+ ( { model | selectedEngines = Set.remove name model.selectedEngines }, Ur.Cmd.none )
370+
371+ else
372+ ( { model | selectedEngines = Set.insert name model.selectedEngines }, Ur.Cmd.none )
373+
242374
243375animator : Model -> Animator.Animator Model
244376animator model =
@@ -273,6 +405,11 @@ result f g res =
273405 f a
274406
275407
408+listsHaveElementsInCommon : List comparable -> List comparable -> Bool
409+listsHaveElementsInCommon a b =
410+ Set.intersect (Set.fromList a) (Set.fromList b) |> Set.isEmpty |> not
411+
412+
276413port createEventSource : String -> Cmd msg
277414
278415