Преглед изворни кода

Load draft from 17lands public URL

Cadel Watson пре 4 месеци
родитељ
комит
9339ad2982
8 измењених фајлова са 203 додато и 112 уклоњено
  1. 0 13
      js/app.js
  2. 10 1
      src/API.elm
  3. 20 0
      src/Database.elm
  4. 1 1
      src/Draft.elm
  5. 34 8
      src/DraftMeta.elm
  6. 111 88
      src/Main.elm
  7. 12 1
      tests/TestDatabase.elm
  8. 15 0
      tests/TestDraftMeta.elm

+ 0 - 13
js/app.js

@@ -56,14 +56,6 @@ function getAllLocalSets(db, callback) {
 
 }
 
-function getSavedEventHistoryUrl() {
-    return localStorage.getItem("eventHistoryURL");
-}
-
-function putSavedEventHistoryUrl(url) {
-    return localStorage.getItem("eventHistoryURL", url);
-}
-
 const openDBRequest = indexedDB.open("set_database", 2);
 
 openDBRequest.onerror = (event) => {
@@ -86,7 +78,6 @@ openDBRequest.onsuccess = (event) => {
             flags: {
                 sets: sets,
                 draftData: JSON.stringify(draftData),
-                eventHistoryUrl: getSavedEventHistoryUrl()
             }
         });
 
@@ -103,10 +94,6 @@ openDBRequest.onsuccess = (event) => {
         app.ports.sendDeleteLocalData.subscribe((setCode) => {
             deleteSetData(database, setCode, (setCode) => app.ports.receiveDidDeleteLocalData.send(setCode))
         });
-
-        app.ports.sendSaveEventHistoryUrl.subscribe((url) => {
-            putSavedEventHistoryUrl(url);
-        });
     })
 };
 

+ 10 - 1
src/API.elm

@@ -1,4 +1,4 @@
-module API exposing (getDrafts, getSetData, getSets)
+module API exposing (getDraft, getDrafts, getSetData, getSets)
 
 import Database exposing (Database)
 import DraftMeta exposing (DraftMeta)
@@ -54,6 +54,15 @@ getDrafts historyUrl onSuccess =
         }
 
 
+getDraft : String -> (Result String DraftMeta -> msg) -> Cmd msg
+getDraft draftID onSuccess =
+    Http.get
+        { url = "https://www.17lands.com" ++ UrlB.absolute [ "data", "draft" ] [ UrlB.string "draft_id" draftID ]
+        , expect =
+            Http.expectJson (Result.mapError httpErrorToString >> onSuccess) DraftMeta.decoder
+        }
+
+
 httpErrorToString : Http.Error -> String
 httpErrorToString error =
     case error of

+ 20 - 0
src/Database.elm

@@ -248,9 +248,29 @@ encodeCardDetails card =
         , ( "colors", Encode.list encodeManaColor card.details.colors )
         , ( "mana_cost", Encode.string card.details.rawManaCost )
         , ( "image_uris", Encode.object [ ( "large", Encode.string card.details.imageUrl ) ] )
+        , ( "rarity", encodeRarity card.details.rarity )
         ]
 
 
+encodeRarity : Rarity -> Encode.Value
+encodeRarity r =
+    case r of
+        Common ->
+            Encode.string "common"
+
+        Uncommon ->
+            Encode.string "uncommon"
+
+        Rare ->
+            Encode.string "rare"
+
+        Mythic ->
+            Encode.string "mythic"
+
+        NoRarity ->
+            Encode.string ""
+
+
 decodeCardDetails : Decoder ( String, CardDetails )
 decodeCardDetails =
     Decode.map2 pair

+ 1 - 1
src/Draft.elm

@@ -29,7 +29,7 @@ decode x =
 
 decodeDraft : Decoder Draft
 decodeDraft =
-    Decode.field "picks" (Decode.list decodePick)
+    Decode.list decodePick
         |> Decode.map Zipper.fromList
         |> Decode.andThen
             (\z ->

+ 34 - 8
src/DraftMeta.elm

@@ -1,15 +1,14 @@
-module DraftMeta exposing (DraftMeta, decoder)
+module DraftMeta exposing (DraftMeta, decoder, parseDraftIDFromPublicURL)
 
 import Draft exposing (Draft)
-import Html as Decode
 import Json.Decode as Decode exposing (Decoder)
 import Json.Decode.Pipeline as Decode
+import Url
+import Url.Parser as UrlP exposing ((</>))
 
 
 type alias DraftMeta =
     { setCode : String
-    , format : String
-    , timestamp : String
     , draftData : Draft
     }
 
@@ -17,7 +16,34 @@ type alias DraftMeta =
 decoder : Decoder DraftMeta
 decoder =
     Decode.succeed DraftMeta
-        |> Decode.required "set_code" Decode.string
-        |> Decode.required "format" Decode.string
-        |> Decode.required "timestamp" Decode.string
-        |> Decode.required "draft_data" Draft.decodeDraft
+        |> Decode.required "expansion" (Decode.string |> Decode.map (\s -> String.toLower s))
+        |> Decode.required "picks" Draft.decodeDraft
+
+
+{-| Parse the draft ID from the public URL.
+
+e.g. the public URL for draft ID `490bda1edc574d71a2768a7d7a415a25` is
+<https://www.17lands.com/draft/490bda1edc574d71a2768a7d7a415a25>
+
+-}
+parseDraftIDFromPublicURL : String -> Result String String
+parseDraftIDFromPublicURL url =
+    let
+        draftURLP =
+            UrlP.s "draft" </> UrlP.string
+    in
+    String.trim url
+        |> Url.fromString
+        |> (\maybeUrl ->
+                case maybeUrl of
+                    Just u ->
+                        case UrlP.parse draftURLP u of
+                            Just draftID ->
+                                Result.Ok draftID
+
+                            Nothing ->
+                                Result.Err "parsing failed"
+
+                    Nothing ->
+                        Result.Err "not a valid URL"
+           )

+ 111 - 88
src/Main.elm

@@ -10,7 +10,7 @@ import Database
 import Deck
 import Dict exposing (Dict)
 import Draft exposing (Draft)
-import DraftMeta exposing (DraftMeta)
+import DraftMeta exposing (DraftMeta, parseDraftIDFromPublicURL)
 import Html exposing (Html, a, button, div, h1, img, input, label, li, p, span, text, ul)
 import Html.Attributes exposing (alt, class, classList, disabled, href, src, type_, value)
 import Html.Events as Events exposing (onClick, onInput, onMouseEnter)
@@ -73,18 +73,18 @@ type SetLoadStatus
     | DeletingLocalData
 
 
-type AvailableDrafts
-    = DraftsNotChecked
-    | DraftsLoading
-    | AvailableDrafts (List DraftMeta)
+type ImportDraft
+    = EditingImportUrl String
+    | LoadingDraft
+    | ImportDraftParseError String
+    | DraftImported DraftMeta
 
 
 type alias ChooseSetModel =
     { draftData : String
     , sets : Dict String SetLoadStatus
-    , eventHistoryUrl : Maybe String -- Resolved event history URL
-    , eventHistoryUrlField : String -- Working state of input
-    , drafts : AvailableDrafts
+    , importDraft : ImportDraft
+    , importDraftParseError : Maybe String
     }
 
 
@@ -115,7 +115,7 @@ type alias ErrorModel =
     }
 
 
-init : { sets : List String, draftData : String, eventHistoryUrl : Maybe String } -> ( Model, Cmd Msg )
+init : { sets : List String, draftData : String } -> ( Model, Cmd Msg )
 init flags =
     let
         setStatus : Dict String SetLoadStatus
@@ -129,24 +129,14 @@ init flags =
             List.map
                 (\s -> sendDoesSetHaveLocalData s)
                 flags.sets
-
-        draftCmds : List (Cmd Msg)
-        draftCmds =
-            case flags.eventHistoryUrl of
-                Nothing ->
-                    []
-
-                Just url ->
-                    [ API.getDrafts url IOGotDrafts ]
     in
     ( ChooseSet
         { draftData = flags.draftData
         , sets = setStatus
-        , eventHistoryUrl = flags.eventHistoryUrl
-        , eventHistoryUrlField = ""
-        , drafts = DraftsNotChecked
+        , importDraft = EditingImportUrl ""
+        , importDraftParseError = Nothing
         }
-    , Cmd.batch (API.getSets IOGotSets :: setCmds ++ draftCmds)
+    , Cmd.batch (API.getSets IOGotSets :: setCmds)
     )
 
 
@@ -154,8 +144,9 @@ type Msg
     = Increment
     | Decrement
     | Highlight String
-    | SetEventHistoryUrlField String
-    | SubmitEventHistoryUrlField
+    | ChangeDraftImportUrlField String
+    | AcknowledgeParseError
+    | SubmitImportDraft
     | FlipHighlightedCard
     | SetFocusStat FocusStat
     | SetSortOrder SortOrder
@@ -166,41 +157,35 @@ type Msg
     | ToggleDeckList
     | SetDeckSortMethod Deck.DeckSortMethod
     | IOGotSets (Result String (List String))
-    | IOGotDrafts (Result String (List DraftMeta))
+    | IOGotDraft (Result String DraftMeta)
     | IOFetchSetData String
     | IOGotSetData (Result String ( String, Maybe Database.Database ))
     | IODeleteSetData String -- Delete by set code
     | IOGotDeleteSetData String -- Successful deletion by set code
     | PortReceiveDoesSetHaveLocalData String
-    | StartDraft String Database.Database
     | OpenCardExplorer String Database.Database
 
 
-startDraft : ChooseSetModel -> Database.Database -> ( Model, Cmd Msg )
-startDraft initFlags database =
-    case Draft.decode initFlags.draftData of
-        Ok draftData ->
-            ( Ready
-                { draft = draftData
-                , database = database
-                , performanceDistributions = calculatePerformanceDistributions (Database.getAll database)
-                , highlighted = Nothing
-                , flipHighlighted = False
-                , focusStat = FocusPickRate
-                , deckProgress = DeckUpToPick
-                , toolboxAccordion =
-                    { cmc = True
-                    , signals = False
-                    , signalsDelta = False
-                    , deckList = False
-                    }
-                , deckSortOrder = Deck.SortByPickNumber
-                }
-            , Cmd.none
-            )
-
-        Err err ->
-            ( Error { error = "Error decoding draft data: " ++ err }, Cmd.none )
+startDraft : DraftMeta -> Database.Database -> ( Model, Cmd Msg )
+startDraft draft database =
+    ( Ready
+        { draft = draft.draftData
+        , database = database
+        , performanceDistributions = calculatePerformanceDistributions (Database.getAll database)
+        , highlighted = Nothing
+        , flipHighlighted = False
+        , focusStat = FocusPickRate
+        , deckProgress = DeckUpToPick
+        , toolboxAccordion =
+            { cmc = True
+            , signals = False
+            , signalsDelta = False
+            , deckList = False
+            }
+        , deckSortOrder = Deck.SortByPickNumber
+        }
+    , Cmd.none
+    )
 
 
 openCardExplorer : ChooseSetModel -> Database.Database -> ( Model, Cmd Msg )
@@ -221,9 +206,6 @@ update msg model =
     case model of
         ChooseSet mdl ->
             case msg of
-                StartDraft setCode database ->
-                    startDraft mdl database
-
                 OpenCardExplorer setCode database ->
                     openCardExplorer mdl database
 
@@ -306,23 +288,58 @@ update msg model =
                 IOGotSetData (Err e) ->
                     ( Error { error = "Error fetching remote set data (" ++ e ++ ")" }, Cmd.none )
 
-                SetEventHistoryUrlField v ->
-                    ( ChooseSet { mdl | eventHistoryUrlField = v }, Cmd.none )
+                ChangeDraftImportUrlField v ->
+                    let
+                        newImportDraft =
+                            case mdl.importDraft of
+                                EditingImportUrl _ ->
+                                    EditingImportUrl v
 
-                SubmitEventHistoryUrlField ->
-                    ( ChooseSet
-                        { mdl
-                            | eventHistoryUrl = Just mdl.eventHistoryUrlField
-                            , drafts = DraftsLoading
-                        }
-                    , API.getDrafts mdl.eventHistoryUrlField IOGotDrafts
-                    )
+                                _ ->
+                                    mdl.importDraft
+                    in
+                    ( ChooseSet { mdl | importDraft = newImportDraft }, Cmd.none )
+
+                AcknowledgeParseError ->
+                    ( ChooseSet { mdl | importDraft = EditingImportUrl "" }, Cmd.none )
+
+                SubmitImportDraft ->
+                    case mdl.importDraft of
+                        EditingImportUrl url ->
+                            case parseDraftIDFromPublicURL url of
+                                Ok draftID ->
+                                    ( ChooseSet
+                                        { mdl
+                                            | importDraft = LoadingDraft
+                                        }
+                                    , API.getDraft draftID IOGotDraft
+                                    )
+
+                                Err e ->
+                                    ( ChooseSet
+                                        { mdl
+                                            | importDraft = ImportDraftParseError e
+                                        }
+                                    , Cmd.none
+                                    )
 
-                IOGotDrafts (Ok drafts) ->
-                    ( ChooseSet { mdl | drafts = AvailableDrafts drafts }, Cmd.none )
+                        _ ->
+                            ( ChooseSet mdl, Cmd.none )
 
-                IOGotDrafts (Err e) ->
-                    ( Error { error = "Error fetching drafts (" ++ e ++ ")" }, Cmd.none )
+                IOGotDraft (Ok draft) ->
+                    let
+                        setData =
+                            Dict.get draft.setCode mdl.sets
+                    in
+                    case setData of
+                        Just (HasLocalData db) ->
+                            startDraft draft db
+
+                        _ ->
+                            ( Error { error = "You must download the set data for the draft before you can open it. (" ++ draft.setCode ++ ")" }, Cmd.none )
+
+                IOGotDraft (Err e) ->
+                    ( Error { error = "Error fetching draft (" ++ e ++ ")" }, Cmd.none )
 
                 _ ->
                     ( ChooseSet mdl, Cmd.none )
@@ -402,22 +419,22 @@ update msg model =
                 IOGotDeleteSetData setCode ->
                     ( Ready mdl, Cmd.none )
 
-                IOGotDrafts _ ->
+                IOGotDraft _ ->
                     ( Ready mdl, Cmd.none )
 
                 PortReceiveDoesSetHaveLocalData _ ->
                     ( Ready mdl, Cmd.none )
 
-                StartDraft _ _ ->
+                OpenCardExplorer _ _ ->
                     ( Ready mdl, Cmd.none )
 
-                OpenCardExplorer _ _ ->
+                ChangeDraftImportUrlField _ ->
                     ( Ready mdl, Cmd.none )
 
-                SetEventHistoryUrlField _ ->
+                SubmitImportDraft ->
                     ( Ready mdl, Cmd.none )
 
-                SubmitEventHistoryUrlField ->
+                AcknowledgeParseError ->
                     ( Ready mdl, Cmd.none )
 
         CardExplorer mdl ->
@@ -482,7 +499,6 @@ viewChooseSet model =
                 HasLocalData database ->
                     div [ class "flex gap-2" ]
                         [ Button.make "Explore" (OpenCardExplorer setCode database) |> Button.view
-                        , Button.make "View draft" (StartDraft setCode database) |> Button.view
                         , Button.make "Delete" (IODeleteSetData setCode) |> Button.view
                         ]
     in
@@ -490,7 +506,7 @@ viewChooseSet model =
         [ div [ class "max-w-4xl max-h-2xl bg-slate-500 rounded-lg p-6 shadow-xl" ]
             [ h1 [ class "text-3xl text-white font-medium" ] [ text "Drafter" ]
             , p [ class "text-white font-medium" ] [ text "Explore sets and analyse your draft events." ]
-            , viewEventHistoryURL model
+            , viewDraftURL model
             ]
         , div [ class "max-w-4xl max-h-2xl bg-slate-500 rounded-lg p-6 shadow-xl" ]
             [ ul [ class "divide-y divide-slate-600" ]
@@ -512,27 +528,34 @@ viewChooseSet model =
         ]
 
 
-viewEventHistoryURL : ChooseSetModel -> Html Msg
-viewEventHistoryURL model =
-    case model.eventHistoryUrl of
-        Just url ->
-            div []
-                [ p [] [ text "17Lands public event history:" ]
-                , a [ href url ] [ text url ]
-                ]
-
-        Nothing ->
+viewDraftURL : ChooseSetModel -> Html Msg
+viewDraftURL model =
+    case model.importDraft of
+        EditingImportUrl url ->
             div [ class "mt-4" ]
                 [ label [ class "block text-sm font-medium text-white" ] [ text "Enter your 17Lands public event history URL:" ]
                 , input
                     [ type_ "text"
-                    , onInput SetEventHistoryUrlField
-                    , value model.eventHistoryUrlField
+                    , onInput ChangeDraftImportUrlField
+                    , value url
                     ]
                     []
-                , Button.make "Save" SubmitEventHistoryUrlField |> Button.view
+                , Button.make "Save" SubmitImportDraft |> Button.view
+                ]
+
+        ImportDraftParseError e ->
+            div [ class "mt-4 text-white" ]
+                [ p [] [ text ("Could not extract draft ID from URL: " ++ e) ]
+                , p [] [ text "Please make sure your URL looks like: https://www.17lands.com/draft/490bda1edc574d71a2768a7d7a415a25" ]
                 ]
 
+        LoadingDraft ->
+            div [ class "mt-4" ]
+                [ text "Loading..." ]
+
+        _ ->
+            div [] []
+
 
 viewReady : ReadyModel -> Html Msg
 viewReady model =

+ 12 - 1
tests/TestDatabase.elm

@@ -1,6 +1,6 @@
 module TestDatabase exposing (..)
 
-import Card exposing (CardData, CardDetails, CardPerformanceData, CardType(..), ManaColor(..), ManaCost(..), Power(..))
+import Card exposing (CardData, CardDetails, CardPerformanceData, CardType(..), ManaColor(..), ManaCost(..), Power(..), Rarity(..))
 import Database exposing (Database)
 import Expect
 import Fuzz exposing (Fuzzer)
@@ -55,6 +55,7 @@ fuzzCardDetails name =
         |> Fuzz.andMap (Fuzz.constant "{X}{R}")
         |> Fuzz.andMap (Fuzz.constant (Just [ X, Color Red ]))
         |> Fuzz.andMap Fuzz.string
+        |> Fuzz.andMap fuzzRarity
 
 
 fuzzCardType : Fuzzer CardType
@@ -80,6 +81,16 @@ fuzzPower =
         ]
 
 
+fuzzRarity : Fuzzer Rarity
+fuzzRarity =
+    Fuzz.oneOfValues
+        [ Common
+        , Uncommon
+        , Rare
+        , Mythic
+        ]
+
+
 fuzzManaColor : Fuzzer ManaColor
 fuzzManaColor =
     Fuzz.oneOfValues

+ 15 - 0
tests/TestDraftMeta.elm

@@ -0,0 +1,15 @@
+module TestDraftMeta exposing (..)
+
+import DraftMeta
+import Expect exposing (Expectation)
+import Fuzz exposing (Fuzzer, int, list, string)
+import Signals
+import Test exposing (..)
+
+
+suite : Test
+suite =
+    describe "DraftMeta"
+        [ test "parseDraftIDFromPublicURL" <|
+            \_ -> Expect.equal (DraftMeta.parseDraftIDFromPublicURL "https://www.17lands.com/draft/490bda1edc574d71a2768a7d7a415a25") (Ok "490bda1edc574d71a2768a7d7a415a25")
+        ]