Compare commits
1 commit
e3a004df20
...
b4a788826b
Author | SHA1 | Date | |
---|---|---|---|
b4a788826b |
7 changed files with 257 additions and 43 deletions
|
@ -19,7 +19,7 @@ black = "^21.6b0"
|
||||||
mypy = "^0.910"
|
mypy = "^0.910"
|
||||||
|
|
||||||
[tool.poetry.scripts]
|
[tool.poetry.scripts]
|
||||||
tutor = 'tutor.__main__:cli'
|
tutor = 'tutor.cli:main'
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["poetry>=0.12"]
|
requires = ["poetry>=0.12"]
|
||||||
|
|
|
@ -30,7 +30,7 @@ import tutor.server
|
||||||
default="warn",
|
default="warn",
|
||||||
)
|
)
|
||||||
@click.pass_context
|
@click.pass_context
|
||||||
def cli(ctx, database, log_level):
|
def main(ctx, database, log_level):
|
||||||
logging.basicConfig(
|
logging.basicConfig(
|
||||||
level={
|
level={
|
||||||
"debug": logging.DEBUG,
|
"debug": logging.DEBUG,
|
||||||
|
@ -43,7 +43,7 @@ def cli(ctx, database, log_level):
|
||||||
ctx.obj["database"] = database
|
ctx.obj["database"] = database
|
||||||
|
|
||||||
|
|
||||||
@cli.command()
|
@main.command()
|
||||||
@click.option("--port", type=int, envvar="TUTOR_PORT", default=8888)
|
@click.option("--port", type=int, envvar="TUTOR_PORT", default=8888)
|
||||||
@click.option("--static", envvar="TUTOR_STATIC", type=click.Path(file_okay=False))
|
@click.option("--static", envvar="TUTOR_STATIC", type=click.Path(file_okay=False))
|
||||||
@click.option("--debug", is_flag=True)
|
@click.option("--debug", is_flag=True)
|
||||||
|
@ -60,7 +60,7 @@ def server(ctx, port, static, debug):
|
||||||
tornado.ioloop.IOLoop.current().start()
|
tornado.ioloop.IOLoop.current().start()
|
||||||
|
|
||||||
|
|
||||||
@cli.command("import")
|
@main.command("import")
|
||||||
@click.argument("filename", type=click.Path(dir_okay=False))
|
@click.argument("filename", type=click.Path(dir_okay=False))
|
||||||
@click.pass_context
|
@click.pass_context
|
||||||
def import_cards(ctx, filename):
|
def import_cards(ctx, filename):
|
||||||
|
@ -69,7 +69,7 @@ def import_cards(ctx, filename):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@cli.command("update_scryfall")
|
@main.command("update_scryfall")
|
||||||
@click.option("--filename", type=click.Path(dir_okay=False))
|
@click.option("--filename", type=click.Path(dir_okay=False))
|
||||||
@click.pass_context
|
@click.pass_context
|
||||||
def update_scryfall(ctx, filename):
|
def update_scryfall(ctx, filename):
|
||||||
|
@ -114,4 +114,4 @@ def update_scryfall(ctx, filename):
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
cli()
|
main()
|
|
@ -93,6 +93,7 @@ async def advanced_search(
|
||||||
db: aiosqlite.Connection,
|
db: aiosqlite.Connection,
|
||||||
search: tutor.search.Search,
|
search: tutor.search.Search,
|
||||||
limit: int = 10,
|
limit: int = 10,
|
||||||
|
offset: int = 0,
|
||||||
in_collection: typing.Optional[bool] = None,
|
in_collection: typing.Optional[bool] = None,
|
||||||
) -> typing.List[tutor.models.Card]:
|
) -> typing.List[tutor.models.Card]:
|
||||||
db.row_factory = aiosqlite.Row
|
db.row_factory = aiosqlite.Row
|
||||||
|
@ -146,7 +147,7 @@ async def advanced_search(
|
||||||
" ".join(joins),
|
" ".join(joins),
|
||||||
"WHERE" if constraints else "",
|
"WHERE" if constraints else "",
|
||||||
" AND ".join(constraints),
|
" AND ".join(constraints),
|
||||||
f"LIMIT {limit}",
|
f"LIMIT {offset},{limit}",
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
cursor = await db.execute(query, params)
|
cursor = await db.execute(query, params)
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import json
|
import json
|
||||||
|
import urllib.parse
|
||||||
|
|
||||||
import aiosqlite
|
import aiosqlite
|
||||||
import tornado.web
|
import tornado.web
|
||||||
|
@ -7,21 +8,57 @@ import tutor.database
|
||||||
import tutor.models
|
import tutor.models
|
||||||
import tutor.search
|
import tutor.search
|
||||||
|
|
||||||
|
|
||||||
|
def update_args(url: str, **qargs) -> str:
|
||||||
|
parts = urllib.parse.urlsplit(url)
|
||||||
|
return urllib.parse.urlunsplit(
|
||||||
|
(
|
||||||
|
parts.scheme,
|
||||||
|
parts.netloc,
|
||||||
|
parts.path,
|
||||||
|
urllib.parse.urlencode(
|
||||||
|
[
|
||||||
|
(k, v)
|
||||||
|
for k, v in urllib.parse.parse_qsl(parts.query)
|
||||||
|
if k not in qargs.keys()
|
||||||
|
]
|
||||||
|
+ list(qargs.items())
|
||||||
|
),
|
||||||
|
parts.fragment,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class SearchHandler(tornado.web.RequestHandler):
|
class SearchHandler(tornado.web.RequestHandler):
|
||||||
|
def set_links(self, **links) -> None:
|
||||||
|
self.set_header(
|
||||||
|
"Link",
|
||||||
|
", ".join([f'<{url}>; rel="{rel}"' for rel, url in links.items()]),
|
||||||
|
)
|
||||||
|
|
||||||
async def get(self) -> None:
|
async def get(self) -> None:
|
||||||
async with aiosqlite.connect(self.application.settings["database"]) as db:
|
async with aiosqlite.connect(self.application.settings["database"]) as db:
|
||||||
name = self.get_argument("name", None)
|
query = self.get_argument("q", "")
|
||||||
in_collection = self.get_argument("in_collection", None)
|
in_collection = self.get_argument("in_collection", None)
|
||||||
|
page = max(1, int(self.get_argument("page", 1)))
|
||||||
limit = int(self.get_argument("limit", 10))
|
limit = int(self.get_argument("limit", 10))
|
||||||
search = tutor.search.search.parse(name)
|
search = tutor.search.search.parse(query)
|
||||||
cards = await tutor.database.advanced_search(
|
cards = await tutor.database.advanced_search(
|
||||||
db,
|
db,
|
||||||
search,
|
search,
|
||||||
limit=limit,
|
limit=limit + 1,
|
||||||
|
offset=limit * (page - 1),
|
||||||
in_collection=in_collection,
|
in_collection=in_collection,
|
||||||
)
|
)
|
||||||
|
has_more = cards and len(cards) > limit
|
||||||
self.set_header("Content-Type", "application/json")
|
self.set_header("Content-Type", "application/json")
|
||||||
self.set_header("Access-Control-Allow-Origin", "*")
|
self.set_header("Access-Control-Allow-Origin", "*")
|
||||||
|
links = {}
|
||||||
|
if page > 1:
|
||||||
|
links["prev"] = update_args(self.request.full_url(), page=page - 1)
|
||||||
|
if has_more:
|
||||||
|
links["next"] = update_args(self.request.full_url(), page=page + 1)
|
||||||
|
self.set_links(**links)
|
||||||
self.write(
|
self.write(
|
||||||
json.dumps(
|
json.dumps(
|
||||||
[
|
[
|
||||||
|
@ -35,7 +72,7 @@ class SearchHandler(tornado.web.RequestHandler):
|
||||||
card.color_identity
|
card.color_identity
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
for card in cards
|
for card in cards[:limit]
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
|
@ -12,7 +12,9 @@
|
||||||
"elm/html": "1.0.0",
|
"elm/html": "1.0.0",
|
||||||
"elm/http": "2.0.0",
|
"elm/http": "2.0.0",
|
||||||
"elm/json": "1.1.3",
|
"elm/json": "1.1.3",
|
||||||
|
"elm/regex": "1.0.0",
|
||||||
"elm/url": "1.0.0",
|
"elm/url": "1.0.0",
|
||||||
|
"elm-community/maybe-extra": "5.2.0",
|
||||||
"mdgriffith/elm-ui": "1.1.8"
|
"mdgriffith/elm-ui": "1.1.8"
|
||||||
},
|
},
|
||||||
"indirect": {
|
"indirect": {
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
<title>Bulk Tagging Dashboard</title>
|
<title>Bulk Tagging Dashboard</title>
|
||||||
<script type="text/javascript" src="elm.js"></script>
|
<script type="text/javascript" src="elm.js"></script>
|
||||||
</head>
|
</head>
|
||||||
|
<meta charset="utf-8"/>
|
||||||
<body>
|
<body>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
var app = Elm.App.init({flags: {}});
|
var app = Elm.App.init({flags: {}});
|
||||||
|
|
237
www/src/App.elm
237
www/src/App.elm
|
@ -1,22 +1,33 @@
|
||||||
module App exposing (main)
|
module App exposing (main)
|
||||||
|
|
||||||
import Browser
|
import Browser
|
||||||
|
import Browser.Dom
|
||||||
|
import Browser.Events
|
||||||
|
import Dict
|
||||||
import Element as E
|
import Element as E
|
||||||
import Element.Background as Background
|
import Element.Background as Background
|
||||||
import Element.Border as Border
|
import Element.Border as Border
|
||||||
import Element.Font as Font
|
import Element.Font as Font
|
||||||
import Element.Input as Input
|
import Element.Input as Input
|
||||||
import Html
|
|
||||||
import Html.Events
|
import Html.Events
|
||||||
import Http
|
import Http
|
||||||
import Json.Decode
|
import Json.Decode
|
||||||
import Json.Decode.Pipeline as JDP
|
import Json.Decode.Pipeline as JDP
|
||||||
|
import Maybe.Extra
|
||||||
|
import Regex
|
||||||
|
import Task
|
||||||
import Url
|
import Url
|
||||||
import Url.Builder
|
import Url.Builder
|
||||||
|
|
||||||
|
|
||||||
|
type alias Window =
|
||||||
|
{ width : Int
|
||||||
|
, height : Int
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
type alias Criteria =
|
type alias Criteria =
|
||||||
{ name : String
|
{ query : String
|
||||||
, ownedOnly : Bool
|
, ownedOnly : Bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,17 +41,20 @@ type alias Card =
|
||||||
|
|
||||||
|
|
||||||
type alias Model =
|
type alias Model =
|
||||||
{ criteria : Criteria
|
{ viewport : Window
|
||||||
, cards : List Card
|
, criteria : Criteria
|
||||||
|
, cardPage : Maybe (ResultPage Card)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
type Msg
|
type Msg
|
||||||
= UrlChanged Url.Url
|
= UrlChanged Url.Url
|
||||||
|
| ViewportChanged Window
|
||||||
| LinkClicked Browser.UrlRequest
|
| LinkClicked Browser.UrlRequest
|
||||||
| UpdateCriteria CriteriaMsg
|
| UpdateCriteria CriteriaMsg
|
||||||
| Search
|
| Search
|
||||||
| FoundCards (Result Http.Error (List Card))
|
| GetPage Url.Url
|
||||||
|
| FoundCards (Result Http.Error (ResultPage Card))
|
||||||
|
|
||||||
|
|
||||||
type CriteriaMsg
|
type CriteriaMsg
|
||||||
|
@ -48,13 +62,81 @@ type CriteriaMsg
|
||||||
| UpdateOwnedOnly Bool
|
| UpdateOwnedOnly Bool
|
||||||
|
|
||||||
|
|
||||||
|
type alias ResultPage a =
|
||||||
|
{ prev : Maybe Url.Url
|
||||||
|
, next : Maybe Url.Url
|
||||||
|
, values : List a
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
expectPaginatedJson : (Result Http.Error (ResultPage value) -> msg) -> Json.Decode.Decoder value -> Http.Expect msg
|
||||||
|
expectPaginatedJson toMsg decoder =
|
||||||
|
let
|
||||||
|
linkPattern : Regex.Regex
|
||||||
|
linkPattern =
|
||||||
|
Regex.fromString "<(.*?)>; rel=\"(.*?)\""
|
||||||
|
|> Maybe.withDefault Regex.never
|
||||||
|
|
||||||
|
links : String -> Dict.Dict String String
|
||||||
|
links s =
|
||||||
|
let
|
||||||
|
toTuples xs =
|
||||||
|
case xs of
|
||||||
|
[ Just a, Just b ] ->
|
||||||
|
Just ( b, a )
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
Nothing
|
||||||
|
in
|
||||||
|
Regex.find linkPattern s
|
||||||
|
|> List.map .submatches
|
||||||
|
|> List.map toTuples
|
||||||
|
|> Maybe.Extra.values
|
||||||
|
|> Dict.fromList
|
||||||
|
in
|
||||||
|
Http.expectStringResponse toMsg <|
|
||||||
|
\response ->
|
||||||
|
case response of
|
||||||
|
Http.BadUrl_ url ->
|
||||||
|
Err (Http.BadUrl url)
|
||||||
|
|
||||||
|
Http.Timeout_ ->
|
||||||
|
Err Http.Timeout
|
||||||
|
|
||||||
|
Http.NetworkError_ ->
|
||||||
|
Err Http.NetworkError
|
||||||
|
|
||||||
|
Http.BadStatus_ metadata _ ->
|
||||||
|
Err (Http.BadStatus metadata.statusCode)
|
||||||
|
|
||||||
|
Http.GoodStatus_ metadata body ->
|
||||||
|
case Json.Decode.decodeString (Json.Decode.list decoder) body of
|
||||||
|
Ok values ->
|
||||||
|
Ok
|
||||||
|
{ prev =
|
||||||
|
Dict.get "link" (Debug.log "headers" metadata.headers)
|
||||||
|
|> Maybe.map links
|
||||||
|
|> Maybe.andThen (Dict.get "prev")
|
||||||
|
|> Maybe.andThen Url.fromString
|
||||||
|
, next =
|
||||||
|
Dict.get "link" metadata.headers
|
||||||
|
|> Maybe.map links
|
||||||
|
|> Maybe.andThen (Dict.get "next")
|
||||||
|
|> Maybe.andThen Url.fromString
|
||||||
|
, values = values
|
||||||
|
}
|
||||||
|
|
||||||
|
Err err ->
|
||||||
|
Err (Http.BadBody (Json.Decode.errorToString err))
|
||||||
|
|
||||||
|
|
||||||
search : Criteria -> Cmd Msg
|
search : Criteria -> Cmd Msg
|
||||||
search criteria =
|
search criteria =
|
||||||
Http.get
|
Http.get
|
||||||
{ url =
|
{ url =
|
||||||
Url.Builder.absolute
|
Url.Builder.absolute
|
||||||
[ "search" ]
|
[ "search" ]
|
||||||
[ Url.Builder.string "name" criteria.name
|
[ Url.Builder.string "q" criteria.query
|
||||||
, Url.Builder.string "in_collection"
|
, Url.Builder.string "in_collection"
|
||||||
(if criteria.ownedOnly then
|
(if criteria.ownedOnly then
|
||||||
"yes"
|
"yes"
|
||||||
|
@ -62,9 +144,17 @@ search criteria =
|
||||||
else
|
else
|
||||||
""
|
""
|
||||||
)
|
)
|
||||||
, Url.Builder.int "limit" 20
|
, Url.Builder.int "limit" 18
|
||||||
]
|
]
|
||||||
, expect = Http.expectJson FoundCards (Json.Decode.list decodeCard)
|
, expect = expectPaginatedJson FoundCards decodeCard
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
loadPage : Url.Url -> Cmd Msg
|
||||||
|
loadPage url =
|
||||||
|
Http.get
|
||||||
|
{ url = Url.toString url
|
||||||
|
, expect = expectPaginatedJson FoundCards decodeCard
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -78,29 +168,55 @@ decodeCard =
|
||||||
|
|
||||||
|
|
||||||
init : Json.Decode.Value -> url -> key -> ( Model, Cmd Msg )
|
init : Json.Decode.Value -> url -> key -> ( Model, Cmd Msg )
|
||||||
init flag url key =
|
init _ _ _ =
|
||||||
let
|
let
|
||||||
criteria =
|
criteria =
|
||||||
{ name = "", ownedOnly = True }
|
{ query = "", ownedOnly = True }
|
||||||
in
|
in
|
||||||
( { criteria = criteria
|
( { viewport = { width = 1280, height = 720 }
|
||||||
, cards = []
|
, criteria = criteria
|
||||||
|
, cardPage = Nothing
|
||||||
}
|
}
|
||||||
, search criteria
|
, Cmd.batch
|
||||||
|
[ search criteria
|
||||||
|
, Task.perform
|
||||||
|
(\x ->
|
||||||
|
ViewportChanged
|
||||||
|
{ width = floor x.viewport.width
|
||||||
|
, height = floor x.viewport.height
|
||||||
|
}
|
||||||
|
)
|
||||||
|
Browser.Dom.getViewport
|
||||||
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
updateCriteria : CriteriaMsg -> Criteria -> Criteria
|
||||||
updateCriteria msg model =
|
updateCriteria msg model =
|
||||||
case msg of
|
case msg of
|
||||||
UpdateName text ->
|
UpdateName text ->
|
||||||
{ model | name = text }
|
{ model | query = text }
|
||||||
|
|
||||||
UpdateOwnedOnly value ->
|
UpdateOwnedOnly value ->
|
||||||
{ model | ownedOnly = value }
|
{ model | ownedOnly = value }
|
||||||
|
|
||||||
|
|
||||||
|
updateCardPage cardpage newCards =
|
||||||
|
{ cardpage | cards = newCards }
|
||||||
|
|
||||||
|
|
||||||
|
update : Msg -> Model -> ( Model, Cmd Msg )
|
||||||
update msg model =
|
update msg model =
|
||||||
case msg of
|
case msg of
|
||||||
|
UrlChanged _ ->
|
||||||
|
( model, Cmd.none )
|
||||||
|
|
||||||
|
LinkClicked _ ->
|
||||||
|
( model, Cmd.none )
|
||||||
|
|
||||||
|
ViewportChanged viewport ->
|
||||||
|
( { model | viewport = viewport }, Cmd.none )
|
||||||
|
|
||||||
UpdateCriteria criteriaMsg ->
|
UpdateCriteria criteriaMsg ->
|
||||||
( { model | criteria = updateCriteria criteriaMsg model.criteria }
|
( { model | criteria = updateCriteria criteriaMsg model.criteria }
|
||||||
, Cmd.none
|
, Cmd.none
|
||||||
|
@ -109,10 +225,13 @@ update msg model =
|
||||||
Search ->
|
Search ->
|
||||||
( model, search model.criteria )
|
( model, search model.criteria )
|
||||||
|
|
||||||
FoundCards (Ok cards) ->
|
GetPage url ->
|
||||||
( { model | cards = cards }, Cmd.none )
|
( model, loadPage url )
|
||||||
|
|
||||||
_ ->
|
FoundCards (Ok cardPage) ->
|
||||||
|
( { model | cardPage = Just cardPage }, Cmd.none )
|
||||||
|
|
||||||
|
FoundCards (Err _) ->
|
||||||
( model, Cmd.none )
|
( model, Cmd.none )
|
||||||
|
|
||||||
|
|
||||||
|
@ -133,18 +252,70 @@ colors =
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
viewCard model =
|
viewCardBrowser model =
|
||||||
E.column []
|
let
|
||||||
[ E.image [ E.height <| E.px 300 ]
|
cardWidth =
|
||||||
{ src =
|
-- 50% of the Scryfall border_crop image width
|
||||||
Url.Builder.crossOrigin "https://api.scryfall.com"
|
240
|
||||||
[ "cards", model.scryfallId ]
|
|
||||||
[ Url.Builder.string "format" "image"
|
cardSpacing =
|
||||||
, Url.Builder.string "version" "border_crop"
|
10
|
||||||
|
|
||||||
|
navigationButtonWidth =
|
||||||
|
50
|
||||||
|
|
||||||
|
cardColumns =
|
||||||
|
-- Either 3, 6, or 9, based on viewport width
|
||||||
|
let
|
||||||
|
availableColumns =
|
||||||
|
(model.viewport.width - (2 * navigationButtonWidth))
|
||||||
|
// (cardWidth + cardSpacing)
|
||||||
|
in
|
||||||
|
(max 3 >> min 9) (availableColumns // 3 * 3)
|
||||||
|
|
||||||
|
viewCard cardModel =
|
||||||
|
E.column []
|
||||||
|
[ E.image [ E.width (E.px cardWidth) ]
|
||||||
|
{ src =
|
||||||
|
Url.Builder.crossOrigin "https://api.scryfall.com"
|
||||||
|
[ "cards", cardModel.scryfallId ]
|
||||||
|
[ Url.Builder.string "format" "image"
|
||||||
|
, Url.Builder.string "version" "border_crop"
|
||||||
|
]
|
||||||
|
, description = cardModel.name
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
navButton text maybeUrl =
|
||||||
|
case maybeUrl of
|
||||||
|
Just url ->
|
||||||
|
Input.button
|
||||||
|
[ E.height E.fill
|
||||||
|
, E.width (E.px navigationButtonWidth)
|
||||||
|
, Background.color colors.primary
|
||||||
|
, Font.color colors.text
|
||||||
|
, Font.center
|
||||||
|
]
|
||||||
|
{ label = E.text text, onPress = Just (GetPage url) }
|
||||||
|
|
||||||
|
Nothing ->
|
||||||
|
E.el [ E.width (E.px navigationButtonWidth) ] E.none
|
||||||
|
in
|
||||||
|
case model.cardPage of
|
||||||
|
Nothing ->
|
||||||
|
E.none
|
||||||
|
|
||||||
|
Just cardPage ->
|
||||||
|
E.row [ E.width E.fill, E.centerX ] <|
|
||||||
|
[ navButton "←" cardPage.prev
|
||||||
|
, E.wrappedRow
|
||||||
|
[ E.width (E.px (cardColumns * (cardWidth + cardSpacing)))
|
||||||
|
, E.centerX
|
||||||
|
, E.spacing cardSpacing
|
||||||
]
|
]
|
||||||
, description = model.name
|
(List.map viewCard cardPage.values)
|
||||||
}
|
, navButton "→" cardPage.next
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
onEnter : msg -> E.Attribute msg
|
onEnter : msg -> E.Attribute msg
|
||||||
|
@ -168,7 +339,7 @@ view model =
|
||||||
{ title = "Tutor"
|
{ title = "Tutor"
|
||||||
, body =
|
, body =
|
||||||
[ E.layout [ Background.color colors.background ] <|
|
[ E.layout [ Background.color colors.background ] <|
|
||||||
E.column []
|
E.column [ E.width E.fill ]
|
||||||
[ E.row [ E.spacing 10 ]
|
[ E.row [ E.spacing 10 ]
|
||||||
[ Input.text
|
[ Input.text
|
||||||
[ onEnter Search
|
[ onEnter Search
|
||||||
|
@ -176,7 +347,7 @@ view model =
|
||||||
, Font.color colors.text
|
, Font.color colors.text
|
||||||
]
|
]
|
||||||
{ onChange = UpdateCriteria << UpdateName
|
{ onChange = UpdateCriteria << UpdateName
|
||||||
, text = model.criteria.name
|
, text = model.criteria.query
|
||||||
, placeholder = Nothing
|
, placeholder = Nothing
|
||||||
, label = Input.labelHidden "Search Input"
|
, label = Input.labelHidden "Search Input"
|
||||||
}
|
}
|
||||||
|
@ -196,7 +367,7 @@ view model =
|
||||||
, label = Input.labelRight [ Font.color colors.text ] (E.text "Owned only?")
|
, label = Input.labelRight [ Font.color colors.text ] (E.text "Owned only?")
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
, E.wrappedRow [ E.spacing 5, E.padding 30 ] <| List.map viewCard model.cards
|
, viewCardBrowser model
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -212,5 +383,7 @@ main =
|
||||||
, subscriptions =
|
, subscriptions =
|
||||||
\_ ->
|
\_ ->
|
||||||
Sub.batch
|
Sub.batch
|
||||||
[]
|
[ Browser.Events.onResize
|
||||||
|
(\w h -> ViewportChanged { width = w, height = h })
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue