summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYorhel <git@yorhel.nl>2020-11-01 13:53:44 +0100
committerYorhel <git@yorhel.nl>2020-11-01 13:53:46 +0100
commit180c4579b4af37a506e80c308fcfdd4ee5d3e177 (patch)
tree8af0919d3146edc9e6d44749c5710c2a01366d1b
parent23a098bc705bd38c1f7e59a78ef402da12287e6d (diff)
Advsearch: Change model to be suitable for advanced search
Turns out my initial idea with a seperate FQuery type wasn't going to work out all that well, and it's easier to handle this by treating nested And/Or queries as just another field. The nested field type is going to be responsible for some extra tasks. Also added a global 'Data' type to pass around in various functions. This would be needed at some point anyway, but now that nested fields can create/manage subfields, we'll need to be able pass around the global object identifiers thing already. And while I was fixing this model, I had a brilliant idea regarding the UI. I was originally planning an explicit split between "quick selection mode" and "advanced search mode", where you could only build nested queries in the latter. But it's actually pretty trivial to make the advanced search mode look like the quick selection mode in the simple case. Buttons to change nesting type and to add more fields will result in a mode natural flow between the two modes. I've not implemented the UI for that yet, though. ...to be continued
-rw-r--r--data/style.css6
-rw-r--r--elm/AdvSearch/Fields.elm278
-rw-r--r--elm/AdvSearch/Main.elm116
-rw-r--r--elm/AdvSearch/Query.elm8
-rw-r--r--elm/AdvSearch/Set.elm18
5 files changed, 221 insertions, 205 deletions
diff --git a/data/style.css b/data/style.css
index a754315d..2472748b 100644
--- a/data/style.css
+++ b/data/style.css
@@ -1114,9 +1114,9 @@ p.filselect i { font-style: normal }
/****** Advanced Search *******/
-.advsearch { max-width: 800px; margin: 0 auto }
-.advsearch .quickselect { width: 100%; display: flex; flex-wrap: wrap }
-.advsearch .quickselect > * { margin: 5px; width: 150px }
+.advsearch { max-width: 800px; margin: 0 auto; display: flex; flex-wrap: wrap; justify-content: center }
+.advsearch .advrow { display: flex; flex-wrap: wrap }
+.advsearch .advrow > * { margin: 5px; width: 150px }
.advsearch .advheader { box-sizing: border-box; background-color: $_blendbg$; padding: 3px; width: 100%; margin-bottom: 2px }
.advsearch .advheader > h3 { text-align: center; font-weight: bold; font-size: inherit; margin-bottom: 3px }
.advsearch .advheader .opts { display: flex; justify-content: space-between }
diff --git a/elm/AdvSearch/Fields.elm b/elm/AdvSearch/Fields.elm
index 80e31ba0..f33b5bde 100644
--- a/elm/AdvSearch/Fields.elm
+++ b/elm/AdvSearch/Fields.elm
@@ -3,12 +3,84 @@ module AdvSearch.Fields exposing (..)
import Html exposing (..)
import Html.Attributes exposing (..)
import Array as A
+import Lib.Util exposing (..)
import Lib.DropDown as DD
import Lib.Api as Api
import AdvSearch.Set as AS
import AdvSearch.Query exposing (..)
+-- "Nested" fields are a container for other fields.
+
+type NestType = NAnd | NOr
+
+type alias NestModel =
+ { ntype : NestType
+ , ftype : FieldType
+ , fields : List Field
+ , add : DD.Config NestMsg
+ }
+
+
+type NestMsg
+ = NAddToggle Bool
+ | NField Int FieldMsg
+
+
+nestInit : NestType -> FieldType -> Data -> (Data, NestModel)
+nestInit n f dat = ({dat | objid = dat.objid+1 },
+ { ntype = n
+ , ftype = f
+ , fields = []
+ , add = DD.init ("advsearch_field"++String.fromInt dat.objid) NAddToggle
+ })
+
+
+nestUpdate : Data -> NestMsg -> NestModel -> (Data, NestModel, Cmd NestMsg)
+nestUpdate dat msg model =
+ case msg of
+ NAddToggle b -> (dat, { model | add = DD.toggle model.add b }, Cmd.none)
+ NField n m ->
+ case List.head (List.drop n model.fields) of
+ Nothing -> (dat, model, Cmd.none)
+ Just f ->
+ let (ndat, nf, nc) = fieldUpdate dat m f
+ in (ndat, { model | fields = modidx n (always nf) model.fields }, Cmd.map (NField n) nc)
+
+
+nestToQuery : NestModel -> Maybe Query
+nestToQuery model =
+ case (model.ntype, List.filterMap fieldToQuery model.fields) of
+ (_, [] ) -> Nothing
+ (_, [x]) -> Just x
+ (NAnd, xs ) -> Just (QAnd xs)
+ (NOr, xs ) -> Just (QOr xs)
+
+
+nestFromQuery : NestType -> FieldType -> Data -> Query -> Maybe (Data, NestModel)
+nestFromQuery ntype ftype dat q =
+ let init l =
+ let (ndat,m) = nestInit ntype ftype dat
+ (ndat2,fl) = List.foldr (\f (d,a) -> let (nd,fm) = fieldFromQuery ftype d f in (nd,(fm::a))) (ndat,[]) l
+ in Just (ndat2, { m | fields = fl })
+ in case (ntype, q) of
+ (NAnd, QAnd l) -> init l
+ (NOr, QOr l) -> init l
+ _ -> Nothing
+
+
+nestView : NestModel -> Html NestMsg
+nestView model =
+ -- TODO: Dropdown to switch between and/or
+ -- TODO: Button to add more fields
+ -- TODO: Buttons to move and remove fields
+ -- TODO: Use a multi-row layout if there are nested filters
+ div [ class "advrow" ] <| List.indexedMap (\i f -> Html.map (NField i) (fieldView f)) model.fields
+
+
+
+
+
-- Generic field abstraction.
-- (this is where typeclasses would have been *awesome*)
--
@@ -20,7 +92,8 @@ import AdvSearch.Query exposing (..)
type alias Field = (Int, DD.Config FieldMsg, FieldModel) -- The Int is the index into 'fields'
type FieldModel
- = FMCustom Query -- A read-only placeholder for Query values that failed to parse into a Field
+ = FMCustom Query -- A read-only placeholder for Query values that failed to parse into a Field
+ | FMNest NestModel
| FMLang (AS.Model String)
| FMOLang (AS.Model String)
| FMPlatform (AS.Model String)
@@ -28,6 +101,7 @@ type FieldModel
type FieldMsg
= FSCustom () -- Not actually used at the moment
+ | FSNest NestMsg
| FSLang (AS.Msg String)
| FSOLang (AS.Msg String)
| FSPlatform (AS.Msg String)
@@ -38,38 +112,51 @@ type FieldType = V
type alias FieldDesc =
{ ftype : FieldType
- , title : String -- How it's listed in the advanced search field selection menu (must be unique for the given ftype).
- , quick : Maybe Int -- Whether it should be included in the quick search mode and in which order.
- , init : FieldModel -- How to initialize an empty field
- , fromQuery : Query -> Maybe FieldModel -- How to initialize the field from a query
+ , title : String -- How it's listed in the field selection menu.
+ , quick : Maybe Int -- Whether it should be included in the default set of fields ("quick mode") and in which order.
+ , init : Data -> (Data, FieldModel) -- How to initialize an empty field
+ , fromQuery : Data -> Query -> Maybe (Data, FieldModel) -- How to initialize the field from a query
}
-- XXX: Should this be lazily initialized instead? May impact JS load time like this.
fields : A.Array FieldDesc
fields =
- let f ftype title quick wrap init fromq = { ftype = ftype, title = title, quick = quick, init = wrap init, fromQuery = Maybe.map wrap << fromq }
+ let f ftype title quick wrap init fromq =
+ { ftype = ftype
+ , title = title
+ , quick = quick
+ , init = \d -> (Tuple.mapSecond wrap (init d))
+ , fromQuery = \d q -> Maybe.map (Tuple.mapSecond wrap) (fromq d q)
+ }
in A.fromList
- -- T TITLE QUICK WRAP INIT FROM_QUERY
- [ f V "Language" (Just 1) FMLang AS.init AS.langFromQuery
- , f V "Original language" (Just 2) FMOLang AS.init AS.olangFromQuery
- , f V "Platform" (Just 3) FMPlatform AS.init AS.platformFromQuery
- , f V "Length" (Just 4) FMLength AS.init AS.lengthFromQuery
- -- Custom field not included, that's only ever initialized in fqueryFromQuery
+ -- IMPORTANT: This list is processed in reverse order when reading a Query
+ -- into Fields, so "catch all" fields must be listed first. In particular,
+ -- FMNest with and/or should go before everything else.
+
+ -- T TITLE QUICK WRAP INIT FROM_QUERY
+ [ f V "And" Nothing FMNest (nestInit NAnd V) (nestFromQuery NAnd V)
+ , f V "Or" Nothing FMNest (nestInit NOr V) (nestFromQuery NOr V)
+
+ , f V "Language" (Just 1) FMLang AS.init AS.langFromQuery
+ , f V "Original language" (Just 2) FMOLang AS.init AS.olangFromQuery
+ , f V "Platform" (Just 3) FMPlatform AS.init AS.platformFromQuery
+ , f V "Length" (Just 4) FMLength AS.init AS.lengthFromQuery
]
--- XXX: This needs a 'data' argument for global data such as a tag info cache
-fieldUpdate : FieldMsg -> Field -> (Field, Cmd FieldMsg)
-fieldUpdate msg_ (num, dd, model) =
- let map1 f m = ((num, dd, (f m)), Cmd.none)
+fieldUpdate : Data -> FieldMsg -> Field -> (Data, Field, Cmd FieldMsg)
+fieldUpdate dat msg_ (num, dd, model) =
+ let maps f m = (dat, (num, dd, (f m)), Cmd.none) -- Simple version: update function returns a Model
+ mapf fm fc (d,m,c) = (d, (num, dd, (fm m)), Cmd.map fc c) -- Full version: update function returns (Data, Model, Cmd)
in case (msg_, model) of
- (FSLang msg, FMLang m) -> map1 FMLang (AS.update msg m)
- (FSOLang msg, FMOLang m) -> map1 FMOLang (AS.update msg m)
- (FSPlatform msg, FMPlatform m) -> map1 FMPlatform (AS.update msg m)
- (FSLength msg, FMLength m) -> map1 FMLength (AS.update msg m)
- (FToggle b, _) -> ((num, DD.toggle dd b, model), Cmd.none)
- _ -> ((num, dd, model), Cmd.none)
+ (FSNest msg, FMNest m) -> mapf FMNest FSNest (nestUpdate dat msg m)
+ (FSLang msg, FMLang m) -> maps FMLang (AS.update msg m)
+ (FSOLang msg, FMOLang m) -> maps FMOLang (AS.update msg m)
+ (FSPlatform msg, FMPlatform m) -> maps FMPlatform (AS.update msg m)
+ (FSLength msg, FMLength m) -> maps FMLength (AS.update msg m)
+ (FToggle b, _) -> (dat, (num, DD.toggle dd b, model), Cmd.none)
+ _ -> (dat, (num, dd, model), Cmd.none)
fieldView : Field -> Html FieldMsg
@@ -77,6 +164,7 @@ fieldView (_, dd, model) =
let v f (lbl,cont) = div [ class "elm_dd_input" ] [ DD.view dd Api.Normal (Html.map f lbl) <| \() -> List.map (Html.map f) (cont ()) ]
in case model of
FMCustom m -> v FSCustom (text "Unrecognized query", \() -> [text ""]) -- TODO: Display the Query
+ FMNest m -> Html.map FSNest (nestView m)
FMLang m -> v FSLang (AS.langView False m)
FMOLang m -> v FSOLang (AS.langView True m)
FMPlatform m -> v FSPlatform (AS.platformView m)
@@ -87,122 +175,46 @@ fieldToQuery : Field -> Maybe Query
fieldToQuery (_, _, model) =
case model of
FMCustom m -> Just m
+ FMNest m -> nestToQuery m
FMLang m -> AS.toQuery (QStr "lang" ) m
FMOLang m -> AS.toQuery (QStr "olang") m
FMPlatform m -> AS.toQuery (QStr "platform") m
FMLength m -> AS.toQuery (QInt "length") m
-fieldInit : Int -> Int -> Field
-fieldInit n ddid =
- case A.get n fields of
- Just f -> (n, DD.init ("advsearch_field" ++ String.fromInt ddid) FToggle, f.init)
- Nothing -> (-1, DD.init "" FToggle, FMCustom (QAnd [])) -- Shouldn't happen.
-
-
-fieldFromQuery : FieldType -> Int -> Query -> Maybe Field
-fieldFromQuery ftype ddid q =
- Tuple.first <| A.foldl (\f a ->
- let inc = Tuple.mapSecond (\n -> n+1) a
- in if Tuple.first a /= Nothing || f.ftype /= ftype then inc
- else case f.fromQuery q of
- Nothing -> inc
- Just m -> (Just (Tuple.second a, DD.init ("advsearch_field" ++ String.fromInt ddid) FToggle, m), 0)
- ) (Nothing,0) fields
-
-
-
+fieldCreate : Int -> (Data,FieldModel) -> (Data,Field)
+fieldCreate fid (dat,fm) =
+ ( {dat | objid = dat.objid + 1}
+ , (fid, DD.init ("advsearch_field" ++ String.fromInt dat.objid) FToggle, fm)
+ )
--- A Query made up of Fields. This is a higher-level and type-safe alternative
--- to Query and is what the main UI works with. An FQuery does not always
--- correspond to a Query as Fields can have empty (= nothing to filter on) or
--- invalid states. A Query does always have a corresponding FQuery - the Custom
--- field type is used as fallback in case no other Field types matches.
--- Nodes in the FQuery tree are identified by their path: a list of integers
--- that index into the list. E.g.:
---
--- FAnd -- path = []
--- [ FField 1 -- path = [0]
--- , FOr -- path = [1]
--- [ FField 2 ] -- path = [1,0]
--- ]
---
--- (Alternative strategy is to throw all FQuery nodes into a Dict and have
--- FAnd/FOr refer to a list of keys instead. Not sure which strategy is more
--- convenient. Arrays may be more efficient than Lists for some operations)
-
-type FQuery
- = FAnd (List FQuery)
- | FOr (List FQuery)
- | FField Field
-
-
-fqueryToQuery : FQuery -> Maybe Query
-fqueryToQuery fq =
- let lst wrap l =
- case List.filterMap fqueryToQuery l of
- [] -> Nothing
- [x] -> Just x
- xs -> Just (wrap xs)
- in case fq of
- FAnd l -> lst QAnd l
- FOr l -> lst QOr l
- FField f -> fieldToQuery f
-
-
--- This algorithm is kind of slow. It walks the Query tree and tries every possible Field for each Query found.
-fqueryFromQuery : FieldType -> Int -> Query -> (Int, FQuery)
-fqueryFromQuery ftype ddid q =
- let lst wrap l = Tuple.mapSecond wrap <| List.foldr (\oq (did,nl) -> let (ndid, fq) = fqueryFromQuery ftype did oq in (ndid, fq::nl)) (ddid,[]) l
- in case fieldFromQuery ftype ddid q of
- Just fq -> (ddid+1, FField fq)
- Nothing ->
- case q of
- QAnd l -> lst FAnd l
- QOr l -> lst FOr l
- _ -> (ddid+1, FField (-1, DD.init ("advsearch_field" ++ String.fromInt ddid) FToggle, FMCustom q))
-
-
--- Update a node at the given path (unused)
---fqueryUpdate : List Int -> (FQuery -> FQuery) -> FQuery -> FQuery
---fqueryUpdate path f q =
--- case path of
--- [] -> f q
--- x::xs ->
--- case q of
--- FAnd l -> FAnd (List.indexedMap (\i e -> if i == x then fqueryUpdate xs f e else e) l)
--- FOr l -> FOr (List.indexedMap (\i e -> if i == x then fqueryUpdate xs f e else e) l)
--- _ -> q
-
-
--- Replace an existing node at the given path
-fquerySet : List Int -> FQuery -> FQuery -> FQuery
-fquerySet path new q =
- case path of
- [] -> new
- x::xs ->
- case q of
- FAnd l -> FAnd (List.indexedMap (\i e -> if i == x then fquerySet xs new e else e) l)
- FOr l -> FOr (List.indexedMap (\i e -> if i == x then fquerySet xs new e else e) l)
- _ -> q
-
-
--- Get the node at the given path
-fqueryGet : List Int -> FQuery -> Maybe FQuery
-fqueryGet path q =
- case path of
- [] -> Just q
- x::xs ->
- case q of
- FAnd l -> List.drop x l |> List.head |> Maybe.andThen (fqueryGet xs)
- FOr l -> List.drop x l |> List.head |> Maybe.andThen (fqueryGet xs)
- _ -> Nothing
-
-
-fquerySub : List Int -> (List Int -> FieldMsg -> a) -> FQuery -> Sub a
-fquerySub path wrap q =
- case q of
- FAnd l -> Sub.batch <| List.indexedMap (\i -> fquerySub (i::path) wrap) l
- FOr l -> Sub.batch <| List.indexedMap (\i -> fquerySub (i::path) wrap) l
- FField (_,dd,_) -> Sub.map (wrap (List.reverse path)) (DD.sub dd)
+fieldInit : Int -> Data -> (Data,Field)
+fieldInit n dat =
+ case A.get n fields of
+ Just f -> fieldCreate n (f.init dat)
+ Nothing -> fieldCreate -1 (dat, FMCustom (QAnd [])) -- Shouldn't happen.
+
+
+fieldFromQuery : FieldType -> Data -> Query -> (Data,Field)
+fieldFromQuery ftype dat q =
+ let (field, _) =
+ A.foldr (\f (af,n) ->
+ case (if af /= Nothing || f.ftype /= ftype then Nothing else f.fromQuery dat q) of
+ Nothing -> (af,n-1)
+ Just ret -> (Just (fieldCreate n ret), 0)
+ ) (Nothing,A.length fields-1) fields
+ in case field of
+ Just ret -> ret
+ Nothing -> fieldCreate -1 (dat, FMCustom q)
+
+
+fieldSub : Field -> Sub FieldMsg
+fieldSub (_,dd,fm) =
+ case fm of
+ FMNest m ->
+ Sub.batch
+ <| DD.sub dd
+ :: Sub.map FSNest (DD.sub m.add)
+ :: List.indexedMap (\i -> Sub.map (FSNest << NField i) << fieldSub) m.fields
+ _ -> DD.sub dd
diff --git a/elm/AdvSearch/Main.elm b/elm/AdvSearch/Main.elm
index a86438fb..6ac77813 100644
--- a/elm/AdvSearch/Main.elm
+++ b/elm/AdvSearch/Main.elm
@@ -17,88 +17,84 @@ main = Browser.element
{ init = \e -> (init e, Cmd.none)
, view = view
, update = update
- , subscriptions = \m -> fquerySub [] Field m.query
+ , subscriptions = \m -> Sub.map Field (fieldSub m.query)
}
type alias Model =
- { query : FQuery
+ { query : Field
, ftype : FieldType
- , ddid : Int
+ , data : Data
}
type Msg
- = Field (List Int) FieldMsg
-
-
--- Add "default" set of filters if they aren't present yet and sort the list
-normalizeForQuick : Model -> Model
-normalizeForQuick model =
- let present = List.foldr (\f a ->
- case f of
- FField (n,_,_) -> Set.insert n a
- _ -> a
- ) Set.empty
- defaults pres = A.foldl (\f (al,did,an) ->
+ = Field FieldMsg
+
+
+-- Add default set of fields (if they aren't present yet) and sort the list
+normalize : Model -> Model
+normalize model =
+ let present = List.foldl (\(n,_,_) a -> Set.insert n a) Set.empty
+ defaults pres = A.foldl (\f (al,dat,an) ->
if f.ftype == model.ftype && f.quick /= Nothing && not (Set.member an pres)
- then (FField (fieldInit an did) :: al, did+1, an+1)
- else (al,did,an+1)
- ) ([],model.ddid,0) fields
- cmp a b =
- case (a,b) of -- Sort active filters before empty ones, then order by 'quick', fallback to title
- (FField (an,add,am), FField (bn,bdd,bm)) ->
- let aq = fieldToQuery (an,add,am) /= Nothing
- bq = fieldToQuery (bn,bdd,bm) /= Nothing
- af = A.get an fields
- bf = A.get bn fields
- ao = Maybe.andThen (\d -> d.quick) af |> Maybe.withDefault 9999
- bo = Maybe.andThen (\d -> d.quick) bf |> Maybe.withDefault 9999
- at = Maybe.map (\d -> d.title) af |> Maybe.withDefault ""
- bt = Maybe.map (\d -> d.title) bf |> Maybe.withDefault ""
- in if aq && not bq then LT else if not aq && bq then GT
- else if ao /= bo then compare ao bo else compare at bt
- _ -> EQ
- norm l =
- let (nl,did,_) = defaults (present l)
- in { model | query = FAnd (List.sortWith cmp (nl++l)), ddid = did }
+ then let (ndat, nf) = fieldInit an dat
+ in (nf::al, ndat, an+1)
+ else (al,dat,an+1)
+ ) ([],model.data,0) fields
+ cmp (an,add,am) (bn,bdd,bm) = -- Sort active filters before empty ones, then order by 'quick', fallback to title
+ let aq = fieldToQuery (an,add,am) /= Nothing
+ bq = fieldToQuery (bn,bdd,bm) /= Nothing
+ af = A.get an fields
+ bf = A.get bn fields
+ ao = Maybe.andThen (\d -> d.quick) af |> Maybe.withDefault 9999
+ bo = Maybe.andThen (\d -> d.quick) bf |> Maybe.withDefault 9999
+ at = Maybe.map (\d -> d.title) af |> Maybe.withDefault ""
+ bt = Maybe.map (\d -> d.title) bf |> Maybe.withDefault ""
+ in if aq && not bq then LT else if not aq && bq then GT
+ else if ao /= bo then compare ao bo else compare at bt
in case model.query of
- FAnd l -> norm l
- FField f -> norm [FField f]
+ (qid, qdd, FMNest qm) ->
+ let (nl, dat, _) = defaults (present qm.fields)
+ nqm = { qm | fields = List.sortWith cmp (nl++qm.fields) }
+ in { model | query = (qid, qdd, FMNest nqm), data = dat }
_ -> model
init : JE.Value -> Model
init arg =
- let (ddid, query) = JD.decodeValue decodeQuery arg |> Result.toMaybe |> Maybe.map (fqueryFromQuery V 1) |> Maybe.withDefault (0, FAnd [])
- in normalizeForQuick
- { query = query
- , ftype = V
- , ddid = ddid
- }
+ let dat = { objid = 0 }
+ (ndat, query) = JD.decodeValue decodeQuery arg |> Result.toMaybe |> Maybe.withDefault (QAnd []) |> fieldFromQuery V dat
+
+ -- We always want the top-level query to be a Nest type.
+ nquery = case query of
+ (_,_,FMNest _) -> query
+ _ -> let (_,m) = fieldCreate -1 (Tuple.mapSecond (\nm -> FMNest {nm|fields=[query]}) (nestInit NAnd V ndat)) in m
+
+ -- Is this a "simple" query? i.e. one that consists of at most a single level of nesting
+ isSimple = case nquery of
+ (_,_,FMNest m) -> List.all (\f -> case f of
+ (_,_,FMNest _) -> False
+ _ -> True) m.fields
+ _ -> True
+
+ model = { query = nquery
+ , ftype = V
+ , data = { ndat | objid = ndat.objid + 2 } -- +2 for the creation of nQuery
+ }
+ in if isSimple then normalize model else model
update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
case msg of
- Field path m ->
- case fqueryGet path model.query of
- Just (FField f) -> let (nf, nc) = fieldUpdate m f in ({ model | query = fquerySet path (FField nf) model.query }, Cmd.map (Field path) nc)
- _ -> (model, Cmd.none)
+ Field m ->
+ let (ndat, nm, nc) = fieldUpdate model.data m model.query
+ in ({ model | data = ndat, query = nm }, Cmd.map Field nc)
view : Model -> Html Msg
view model = div [ class "advsearch" ]
- [ input [ type_ "hidden", id "f", name "f", value <| Maybe.withDefault "" <| Maybe.map (\v -> JE.encode 0 (encodeQuery v)) (fqueryToQuery model.query) ] []
- , div [ class "quickselect" ] <|
- (case model.query of
- FField f -> [Html.map (Field []) (fieldView f)]
- FOr _ -> []
- FAnd l -> List.indexedMap (\i f -> Html.map (Field [i]) (fieldView f)) <| List.filterMap (\q ->
- case q of
- FField f -> Just f
- _ -> Nothing) l
- ) ++
- --, input [ type_ "button", class "submit", value "Advanced mode" ] [] -- TODO: Advanced mode where you can construct arbitrary queries.
- [ input [ type_ "submit", class "submit", value "Search" ] []
- ]
+ [ input [ type_ "hidden", id "f", name "f", value <| Maybe.withDefault "" <| Maybe.map (\v -> JE.encode 0 (encodeQuery v)) (fieldToQuery model.query) ] []
+ , Html.map Field (fieldView model.query)
+ , input [ type_ "submit", class "submit", value "Search" ] []
]
diff --git a/elm/AdvSearch/Query.elm b/elm/AdvSearch/Query.elm
index b2ea12ac..e504a3de 100644
--- a/elm/AdvSearch/Query.elm
+++ b/elm/AdvSearch/Query.elm
@@ -63,3 +63,11 @@ decodeQuery = JD.index 0 JD.string |> JD.andThen (\s ->
, JD.map2 (QQuery s) (JD.index 1 decodeOp) (JD.index 2 decodeQuery)
]
)
+
+
+
+-- Global data that's passed around for Fields
+-- (defined here because everything imports this module)
+type alias Data =
+ { objid : Int -- Incremental integer for global identifiers
+ }
diff --git a/elm/AdvSearch/Set.elm b/elm/AdvSearch/Set.elm
index 6e22f382..e08c8c94 100644
--- a/elm/AdvSearch/Set.elm
+++ b/elm/AdvSearch/Set.elm
@@ -25,8 +25,8 @@ type Msg a
| Mode -- Toggle between single / multi (or) / multi (and)
-init : Model a
-init = { sel = Set.empty, single = True, and = False, neg = False, last = Set.empty }
+init : Data -> (Data, Model a)
+init dat = (dat, { sel = Set.empty, single = True, and = False, neg = False, last = Set.empty })
update : Msg comparable -> Model comparable -> Model comparable
@@ -61,20 +61,20 @@ toQuery f m =
-- setFromQuery (\q -> case q of
-- QStr "lang" op v -> Just (op, v)
-- _ -> Nothing) model
-fromQuery : (Query -> Maybe (Op,comparable)) -> Query -> Maybe (Model comparable)
-fromQuery f q =
+fromQuery : (Query -> Maybe (Op,comparable)) -> Data -> Query -> Maybe (Data, Model comparable)
+fromQuery f dat q =
let single and qs = f qs |> Maybe.andThen (\(op,v) ->
if op /= Ne && op /= Eq
then Nothing
- else Just { sel = Set.fromList [v], and = xor and (op == Ne), neg = (op == Ne), single = True, last = Set.empty })
+ else Just (dat, { sel = Set.fromList [v], and = xor and (op == Ne), neg = (op == Ne), single = True, last = Set.empty }))
lst mm xqs =
case (mm, xqs) of
(Nothing, _) -> Nothing
(_, []) -> mm
- (Just m, x :: xs) -> f x |> Maybe.andThen (\(op,v) ->
+ (Just (_,m), x :: xs) -> f x |> Maybe.andThen (\(op,v) ->
if (op /= Ne && op /= Eq) || (op == Ne) /= m.neg
then Nothing
- else lst (Just {m | single = False, sel = Set.insert v m.sel}) xs)
+ else lst (Just (dat, {m | single = False, sel = Set.insert v m.sel})) xs)
in case q of
QAnd (x::xs) -> lst (single True x) xs
QOr (x::xs) -> lst (single False x) xs
@@ -100,8 +100,8 @@ langView orig model =
in
( case Set.toList model.sel of
[] -> b [ class "grayedout" ] [ text <| if orig then "Orig language" else "Language" ]
- [v] -> span [ class "nowrap" ] [ lblPrefix model, langIcon v, text <| Maybe.withDefault "" (lookup v GT.languages) ]
- l -> span [ class "nowrap" ] <| lblPrefix model :: List.intersperse (text "") (List.map langIcon l)
+ [v] -> span [ class "nowrap" ] [ text tprefix, lblPrefix model, langIcon v, text <| Maybe.withDefault "" (lookup v GT.languages) ]
+ l -> span [ class "nowrap" ] <| text tprefix :: lblPrefix model :: List.intersperse (text "") (List.map langIcon l)
, \() ->
[ div [ class "advheader" ]
[ h3 [] [ text <| if orig then "Language the visual novel has been originally written in." else "Language(s) in which the visual novel is available." ]