diff --git a/src/Core/Common.fs b/src/Core/Common.fs index d25aa26..65188ce 100644 --- a/src/Core/Common.fs +++ b/src/Core/Common.fs @@ -136,6 +136,7 @@ module String = | [] -> emptyString let join delimiter strings = System.String.Join((delimiter: string), (strings: string seq)) let equalsIgnoreCase lhs rhs = System.String.Equals(lhs, rhs, System.StringComparison.InvariantCultureIgnoreCase) + let containsIgnoreCase (lhs:string) (rhs:string) = lhs.ToLowerInvariant().Contains(rhs.ToLowerInvariant()) // note: lhs.Contains(rhs, System.StringComparison.InvariantCultureIgnoreCase) does not translate to JavaScript let firstWord input = match Option.ofObj input with | Some(v:string) -> v.Trim().Split(' ') |> Seq.head diff --git a/src/UI/Components/PriestSpells.fs b/src/UI/Components/PriestSpells.fs index d5257b7..2de0891 100644 --- a/src/UI/Components/PriestSpells.fs +++ b/src/UI/Components/PriestSpells.fs @@ -6,12 +6,26 @@ open Feliz module Data = type SphereName = string type SpellName = string + type DeityName = string type Spell = { name: SpellName; level: int; spheres: SphereName list } with override this.ToString() = let level = match this.level with 1 -> "1st" | 2 -> "2nd" | 3 -> "3rd" | n -> $"{n}th" $"""{this.name} ({level} level {this.spheres |> String.concat "/"})""" type Sphere = { name: SphereName; spells: Spell list } + type AccessLevel = Major | Minor + type SphereAccess = { sphere: SphereName; access: AccessLevel } + type Deity = { name: DeityName; spheres: SphereAccess list } + let consolidateSpells spheres = + // return a list of spells, not spheres, with no duplicates and with all spheres for a given spell linked to it + let spells = spheres |> List.collect (fun sphere -> sphere.spells) |> List.groupBy (fun spell -> spell.name) + [ for _, group in spells do + let spheres = group |> List.collect (fun spell -> spell.spheres) |> List.distinct + { group[0] with spheres = spheres } + ] + let consolidateSpheres (spells: Spell list) spheres = + let spells = spells |> List.map (fun spell -> spell.name, spell) |> Map.ofList + spheres |> List.map (fun sphere -> { sphere with spells = sphere.spells |> List.map (fun spell -> spells.[spell.name]) }) let spheres = """ All: Bless 1, Combine 1, Detect Evil 1, Purify Food & Drink 1, Atonement 5 Animal: Animal Friendship 1, Invisibility to Animals 1, Locate Animals or Plants 1, Charm Person or Mammal 2, Messenger 2, @@ -76,32 +90,86 @@ module Data = | Sphere(lhs, OWS (Spheres(rhs, rest))) -> Some(lhs::rhs, rest) | Sphere(v, rest) -> Some([v], rest) | _ -> None - let partial (|Recognizer|_|) txt = match ParseArgs.Init txt with | Recognizer(v, _) -> v - let partialR (|Recognizer|_|) txt = match ParseArgs.Init txt with | Recognizer(v, (input, pos)) -> v, input.input.Substring pos - let consolidateSpells spheres = - // return a list of spells, not spheres, with no duplicates and with all spheres for a given spell linked to it - let spells = spheres |> List.collect (fun sphere -> sphere.spells) |> List.groupBy (fun spell -> spell.name) - [ for _, group in spells do - let spheres = group |> List.collect (fun spell -> spell.spheres) |> List.distinct - { group[0] with spheres = spheres } - ] - let consolidateSpheres (spells: Spell list) spheres = - let spells = spells |> List.map (fun spell -> spell.name, spell) |> Map.ofList - spheres |> List.map (fun sphere -> { sphere with spells = sphere.spells |> List.map (fun spell -> spells.[spell.name]) }) - partial (|Spheres|_|) spheres |> List.collect _.spells |> List.filter (fun spell -> spell.name = "Chariot of Sustarre") - partial (|Spheres|_|) spheres |> fun spheres -> (consolidateSpells spheres) |> List.filter (fun spell -> spell.name = "Chariot of Sustarre") - partial (|Spheres|_|) spheres |> fun spheres -> spheres |> consolidateSpheres (consolidateSpells spheres) |> List.filter (fun sphere -> sphere.name = "Plant") |> List.collect _.spells |> List.map _.ToString() - |> String.join ", " + // let partial (|Recognizer|_|) txt = match ParseArgs.Init txt with | Recognizer(v, _) -> v + // let partialR (|Recognizer|_|) txt = match ParseArgs.Init txt with | Recognizer(v, (input, pos)) -> v, input.input.Substring pos + // partial (|Spheres|_|) spheres |> List.collect _.spells |> List.filter (fun spell -> spell.name = "Chariot of Sustarre") + // partial (|Spheres|_|) spheres |> fun spheres -> (consolidateSpells spheres) |> List.filter (fun spell -> spell.name = "Chariot of Sustarre") + // partial (|Spheres|_|) spheres |> fun spheres -> spheres |> consolidateSpheres (consolidateSpells spheres) |> List.filter (fun sphere -> sphere.name = "Plant") |> List.collect _.spells |> List.map _.ToString() + // |> String.join ", " + module Storage = + open LocalStorage + module Spheres = + let key = "Spheres" + let cacheRead, cacheInvalidate = Cache.create() + let read (): Sphere list = + cacheRead (thunk2 read key (fun () -> Packrat.parser Parser.(|Spheres|_|) (spheres.Trim()) |> fun spheres -> spheres |> consolidateSpheres (consolidateSpells spheres))) + let write (v: Sphere list) = + write key v + cacheInvalidate() + module Notes = + let key = "Notes" + let cacheRead, cacheInvalidate = Cache.create() + let read (): Map = + cacheRead (thunk2 read key (thunk Map.empty)) + let write (v: Map) = + write key v + cacheInvalidate() + module Deities = + let key = "Deities" + let cacheRead, cacheInvalidate = Cache.create() + let read (): Deity list = + cacheRead (thunk2 read key (thunk [])) + let write (v: Deity list) = + write key v + cacheInvalidate() + module SpellPicks = + let key = "Picks" + let cacheRead, cacheInvalidate = Cache.create() + let read (): Map = + cacheRead (thunk2 read key (thunk Map.empty)) + let write (v: Map) = + write key v + cacheInvalidate() + + module Impl = - type Model = { filter: string } + open Data + type Options = { spells: Spell list; notes: Map; spheres: Sphere list; deities: Deity list } + type Model = { options: Options; picks: Map } type Msg = NoOp - let init() = { filter = "" } + let init() = + let spheres = Storage.Spheres.read() + let options = { spells = consolidateSpells spheres; notes = Storage.Notes.read(); spheres = spheres; deities = Storage.Deities.read() } + { options = options; picks = Storage.SpellPicks.read() } let update msg model = model + let filteredSpells (filter: string) (model: Model) = + match filter.Trim() with + | "" -> model.options.spells + | filter -> + let fragments = filter.Split(' ') |> List.ofArray + model.options.spells |> List.filter (fun spell -> fragments |> List.every (fun fragment -> String.containsIgnoreCase (spell.ToString()) fragment)) open Impl [] let View() = let model, dispatch = React.useElmishSimple init update + let filter, setFilter = React.useState "" Html.div [ Html.h1 "Priest Spells" - ] \ No newline at end of file + Html.input [ + prop.value filter + prop.placeholder "Spell name, sphere or deity" + prop.onChange (fun txt -> setFilter txt) + ] + Html.ul [ + for spell in filteredSpells filter model do + Html.li [ + Html.span [ + prop.text (spell.ToString()) + ] + Html.span [ + prop.text (match model.picks.TryFind(spell.name) with Some(n) -> $" ({n})" | None -> "") + ] + ] + ] + ] diff --git a/src/UI/LocalStorage.fs b/src/UI/LocalStorage.fs index 3a95839..645ab40 100644 --- a/src/UI/LocalStorage.fs +++ b/src/UI/LocalStorage.fs @@ -29,11 +29,4 @@ module Cache = open Cache -module Spells = - let key = "Spells" - let cacheRead, cacheInvalidate = Cache.create() - let read (): obj list = - cacheRead (thunk2 read key (thunk [])) - let write (v: obj list) = - write key v - cacheInvalidate() +