~sjm/builds-character

6f77451484d22a0a29893769c76fe9cc4a00a077 — Sam Marshall 2 years ago a5d6926
chore: add beginnings of foci

Going to introduce the ability to import/export work before I finish
this off. Import/Export will allow me to make changes to the JSON of the
character sheet easily, and to ensure that I can try out some test data
easily too.
M readme.org => readme.org +14 -7
@@ 6,12 6,6 @@ Control your data, avoid hosting character sheets online, still share them with 

** todo list
*** Backlog
**** TODO import/export json
**** TODO Improve use of profunctor-lenses

This one might require me to understand profunctor lenses a lot better...

***** TODO Read up on profunctor-lenses
**** Model hitpoints

these are derived from your class and your level and your constitution


@@ 29,12 23,16 @@ for shooting, skills, etc
relies on attributes and level
**** tool-tips with rules
**** Oracles of some kind
**** Edit mode
rather than have everything be editable all the time, allow the user to toggle between editability
**** real-time updates for viewers
**** 'fork' or 'copy' button, for people to start their own sheet

*** Ready
**** Model Skills

Only show psychic skills for psychics.

**** Model Foci
**** Model Attack bonus
**** Model Equipment



@@ 45,6 43,15 @@ Weapons have damage dice and stuff, armour has armour class.
**** Model Name
**** Model Goal
*** Doing
**** Model Foci

This is an interesting one, similar to equipment. There's a set of already-known possible foci, but GMs may make their own so we need to be able to add foci too.

I'm thinking that we can have a list of /possible foci/ in the json file, and a single set too.

The UI could be interesting. I think when selecting, we should see a list of foci in a panel on the right hand side, separate from the main body of the list.

**** TODO import/export json
*** Done
**** DONE save sheet on update
:LOGBOOK:

M scripts.org => scripts.org +17 -42
@@ 2,6 2,9 @@
#+begin_src shell :results output verbatim
rm ./dist/*.js ./dist/*.js.map
spago build
#+end_src

#+begin_src shell :results output verbatim
yarn parcel build ./index.html
#+end_src



@@ 21,48 24,6 @@ ls -1 ./dist
#+end_src

#+RESULTS: build-project
#+begin_example
Warning found:
in module UI.Entry
at src/UI/Entry.purs:11:1 - 11:37 (line 11, column 1 - line 11, column 37)

  The qualified import of Halogen.HTML.Properties as HP is redundant


See https://github.com/purescript/documentation/blob/master/errors/UnusedImport.md for more information,
or to contribute content related to this warning.


yarn run v1.22.10
$ /home/sam/Documents/code/builds-character/node_modules/.bin/parcel build ./index.html
Building...
Bundling...
Packaging & Optimizing...
✨ Built in 6.91s

dist/index.0ebdc5d7.css                         ⚠️  3.07 MB    184ms
├── app.css                                         3.07 MB    6.00s
├── <no source>                                       128 B      0ms
├── Code from unknown sourcefiles                      48 B      0ms
└── %3Cinput%20css%20B9PXjF%3E                          6 B      0ms

dist/index.e475b57a.js                            652.43 KB    5.85s
├── output/Data.Map.Internal/index.js              35.27 KB    984ms
├── output/Data.List/index.js                       17.4 KB    993ms
├── output/Data.List.Lazy/index.js                 16.62 KB    337ms
├── output/Control.Monad.List.Trans/index.js       12.76 KB    785ms
├── output/Data.List.Lazy.Types/index.js           12.57 KB    369ms
├── output/Data.List.Types/index.js                11.81 KB    583ms
├── output/Data.Foldable/index.js                  10.81 KB    507ms
├── output/Data.Array/index.js                     10.15 KB    455ms
├── output/Data.FoldableWithIndex/index.js          9.86 KB    808ms
└── output/Halogen.Query.HalogenM/index.js           8.8 KB    463ms
└── + 382 more assets

dist/index.html                                       218 B    184ms
└── index.html                                        285 B     59ms
Done in 7.90s.
#+end_example

#+name: install
#+begin_src shell :results output verbose


@@ 70,3 31,17 @@ spago install effect
#+end_src

#+RESULTS: install

#+begin_src shell :results output verbose
df
#+end_src

#+RESULTS:
: Filesystem     1K-blocks      Used Available Use% Mounted on
: tmpfs            1611136      2560   1608576   1% /run
: /dev/nvme0n1p2 490691512 123015360 342680672  27% /
: tmpfs            8055680    389076   7666604   5% /dev/shm
: tmpfs               5120         4      5116   1% /run/lock
: tmpfs               4096         0      4096   0% /sys/fs/cgroup
: /dev/nvme0n1p1    523248      8004    515244   2% /boot/efi
: tmpfs            1611136       144   1610992   1% /run/user/1000

M src/CharSheet.purs => src/CharSheet.purs +35 -13
@@ 7,14 7,20 @@ module CharSheet
  , _background
  , _class
  , _xp
  , _possibleFoci
  ) where

import Prelude

import Beaker.HyperDrive as HD
import CharSheet.Attributes as CA
import CharSheet.Background (Background)
import CharSheet.Background as CB
import CharSheet.ChosenFocus (ChosenFocus)
import CharSheet.ChosenFocus as CCF
import CharSheet.Class as CC
import CharSheet.Focus (Focus)
import CharSheet.Focus as CF
import CharSheet.Focus.Default as FocusDefault
import CharSheet.XP as CX
import Data.Argonaut as A
import Data.Array as Array


@@ 22,7 28,8 @@ import Data.Either (Either(..))
import Data.Int as I
import Data.Lens as L
import Data.Lens.Index (ix)
import Data.Maybe (Maybe(..), maybe')
import Data.Maybe (Maybe(..), maybe, maybe')
import Data.Traversable (sequence)
import Data.Tuple (Tuple(..))
import Effect.Aff (Aff, Error, error)
import Effect.Class (liftEffect)


@@ 33,7 40,9 @@ type Character
  = { xp :: CX.XP
    , class :: CC.Class
    , attributes :: CA.Attributes
    , background :: Maybe CB.Background
    , background :: Maybe Background
    , chosenFoci :: Array ChosenFocus
    , possibleFoci :: Array Focus
    }

fresh :: Character


@@ 42,6 51,8 @@ fresh =
  , class: CC.Undecided
  , attributes: CA.empty
  , background: Nothing
  , chosenFoci: []
  , possibleFoci: FocusDefault.foci
  }

fromJson :: A.Json -> Maybe Character


@@ 54,26 65,34 @@ fromJson j = do
  int <- I.ceil <$> (L.preview (A._Object <<< ix "attributes" <<< A._Object <<< ix "int" <<< A._Number) j)
  wis <- I.ceil <$> (L.preview (A._Object <<< ix "attributes" <<< A._Object <<< ix "wis" <<< A._Number) j)
  cha <- I.ceil <$> (L.preview (A._Object <<< ix "attributes" <<< A._Object <<< ix "cha" <<< A._Number) j)
  bg <- (L.preview ( A._Object <<< ix "background" <<< A._String ) j)
  bg <- (L.preview (A._Object <<< ix "background" <<< A._String) j)
  let
    cfoci = (L.toArrayOf (A._Object <<< ix "chosenFoci" <<< A._JsonArray) j)

  let background = Array.find
                   (\b -> (L.view CB._background b) == bg)
                   CB.backgrounds
    pfoci = (L.toArrayOf (A._Object <<< ix "possibleFoci" <<< A._JsonArray) j)

  pure $ { xp: CX.xp x
         , class: cl
         , attributes: CA.manual str dex con int wis cha
         , background
         }
    background =
      Array.find
        (\b -> (L.view CB._background b) == bg)
        CB.backgrounds
  pure
    $ { xp: CX.xp x
      , class: cl
      , attributes: CA.manual str dex con int wis cha
      , background
      , chosenFoci: maybe [] identity (sequence $ CCF.fromJson <$> cfoci)
      , possibleFoci: maybe [] identity (sequence $ CF.fromJson <$> pfoci)
      }

toJson :: Character -> A.Json
toJson { xp: x, class: c, attributes: a, background: b } =
toJson { xp: x, class: c, attributes: a, background: b, possibleFoci: pf } =
  A.fromObject
    ( Object.fromFoldable
        [ Tuple "xp" (CX.toJson x)
        , Tuple "class" (CC.toJson c)
        , Tuple "attributes" (CA.toJson a)
        , Tuple "background" (maybe' (\_ -> A.fromString "undecided") CB.toJson b)
        , Tuple "possibleFoci" $ A.fromArray (CF.toJson <$> pf)
        ]
    )



@@ 99,3 118,6 @@ _xp = L.lens _.xp $ _ { xp = _ }

_background :: L.Lens' Character (Maybe CB.Background)
_background = L.lens _.background $ _ { background = _ }

_possibleFoci :: L.Lens' Character (Array Focus)
_possibleFoci = L.lens _.possibleFoci $ _ { possibleFoci = _ }

M src/CharSheet/Background.purs => src/CharSheet/Background.purs +0 -1
@@ 1,7 1,6 @@
module CharSheet.Background where

import Prelude

import Data.Argonaut as A
import Data.Lens as L
import Data.Tuple (Tuple(..))

A src/CharSheet/ChosenFocus.purs => src/CharSheet/ChosenFocus.purs +28 -0
@@ 0,0 1,28 @@
module CharSheet.ChosenFocus where

import Prelude
import Data.Argonaut as A
import Data.Int as I
import Data.Lens as L
import Data.Lens.Index (ix)
import Data.Maybe (Maybe)

type ChosenFocus
  = { name :: String
    , level :: Int
    }

chosenFocus :: String -> Int -> ChosenFocus
chosenFocus name level = { name, level }

fromJson :: A.Json -> Maybe ChosenFocus
fromJson json = do
  name <- L.preview (A._Object <<< ix "name" <<< A._String) json
  level <- I.ceil <$> L.preview (A._Object <<< ix "level" <<< A._Number) json
  pure { name, level }

_name :: L.Lens' ChosenFocus String
_name = L.lens _.name $ _ { name = _ }

_level :: L.Lens' ChosenFocus Int
_level = L.lens _.level $ _ { level = _ }

A src/CharSheet/Focus.purs => src/CharSheet/Focus.purs +64 -0
@@ 0,0 1,64 @@
module CharSheet.Focus where

import Prelude
import Data.Argonaut as A
import Data.Lens as L
import Data.Lens.Index (ix)
import Data.Maybe (Maybe)
import Data.Tuple (Tuple(..))
import Foreign.Object as Object

type Focus
  = { name :: String
    , description :: String
    , levelOne :: String
    , levelTwo :: String
    }

focus :: String -> String -> String -> String -> Focus
focus name description levelOne levelTwo =
  { name
  , description
  , levelOne
  , levelTwo
  }

-- _ObjectKey :: forall profunctor. Choice profunctor => Strong profunctor => String -> profunctor Json Json -> profunctor Json Json
_ObjectKey :: String -> L.Traversal' A.Json A.Json
_ObjectKey key = (A._Object <<< ix key)

fromJson :: A.Json -> Maybe Focus
fromJson json = do
  name <- L.preview (A._Object <<< ix "name" <<< A._String) json
  description <- L.preview (A._Object <<< ix "description" <<< A._String) json
  levelOne <- L.preview (_ObjectKey "levelOne" <<< A._String) json
  levelTwo <- L.preview (_ObjectKey "levelTwo" <<< A._String) json
  pure
    { name
    , description
    , levelOne
    , levelTwo
    }

toJson :: Focus -> A.Json
toJson f =
  A.fromObject
    ( Object.fromFoldable
        [ Tuple "name" $ A.fromString (L.view _name f)
        , Tuple "description" $ A.fromString (L.view _description f)
        , Tuple "levelOne" $ A.fromString (L.view _levelOne f)
        , Tuple "levelTwo" $ A.fromString (L.view _levelTwo f)
        ]
    )

_name :: L.Lens' Focus String
_name = L.lens _.name $ _ { name = _ }

_description :: L.Lens' Focus String
_description = L.lens _.description $ _ { description = _ }

_levelOne :: L.Lens' Focus String
_levelOne = L.lens _.levelOne $ _ { levelOne = _ }

_levelTwo :: L.Lens' Focus String
_levelTwo = L.lens _.levelTwo $ _ { levelTwo = _ }

A src/CharSheet/Focus/Default.purs => src/CharSheet/Focus/Default.purs +25 -0
@@ 0,0 1,25 @@
module CharSheet.Focus.Default where

import CharSheet.Focus

foci :: Array Focus
foci =
  [ focus
      "Alert"
      """
You are keenly aware of your surroundings and virtually
impossible to take you unaware. You have an instinctive
alacrity of response that helps you act before less wary
persons can think to move.
"""
      """
Gain notice as a bonus skill. You cannot be surprised,
nor can others use the Execution Attack option on you.
When you roll initiative, roll twice and take the
best result.
"""
      """
You always act first in a combat round unless someone
else involved is also Alert.
"""
  ]

M src/UI/Background.purs => src/UI/Background.purs +14 -14
@@ 1,7 1,6 @@
module UI.Background where

import Prelude

import CharSheet.Background as CB
import Data.Array as Array
import Data.Lens as L


@@ 35,22 34,23 @@ component :: forall q m. H.Component q Input Message m
component =
  Hooks.component \t i -> Hooks.do
    let
      handleValueChange value = let
        bg = Array.find (\b -> (L.view CB._background b) == value) CB.backgrounds
      handleValueChange value =
        let
          bg = Array.find (\b -> (L.view CB._background b) == value) CB.backgrounds
        in
         case bg of
           Just x -> Hooks.raise t.outputToken $ SetBackground x
           Nothing -> pure unit

          case bg of
            Just x -> Hooks.raise t.outputToken $ SetBackground x
            Nothing -> pure unit
    Hooks.pure do
      HH.div
       [ Util.classes [ "mb-2", "p-2", "border-2", "border-black", "w-96" ] ]
        [ Util.classes [ "mb-2", "p-2", "border-2", "border-black", "w-96" ] ]
        [ HH.label [ Util.classes [ "mr-2" ] ] [ HH.text "Background:" ]
        , HH.select
          [ HP.value (maybe' (\_ -> "undecided") (L.view CB._background) i)
          , HP.title (maybe' (\_ -> "choose a background") (L.view CB._description) i)
          , HE.onValueChange \v -> handleValueChange v
          ]
          (bkg <$>
           (CB.backgrounds <> [CB.background "undecided" "choose a background"]))
            [ HP.value (maybe' (\_ -> "undecided") (L.view CB._background) i)
            , HP.title (maybe' (\_ -> "choose a background") (L.view CB._description) i)
            , HE.onValueChange \v -> handleValueChange v
            ]
            ( bkg
                <$> (CB.backgrounds <> [ CB.background "undecided" "choose a background" ])
            )
        ]

M src/UI/Entry.purs => src/UI/Entry.purs +22 -14
@@ 1,7 1,6 @@
module UI.Entry where

import Prelude

import CharSheet as C
import CharSheet.XP as CXP
import Data.Lens as L


@@ 13,6 12,7 @@ import Type.Proxy (Proxy(..))
import UI.Attributes as UA
import UI.Background as UB
import UI.Class as UC
import UI.Foci.PossibleList as PossibleFoci
import UI.Level as UILevel
import UI.Util as Util
import UI.XP as UIXP


@@ 41,6 41,7 @@ type Slots
    , class :: (UC.Slot Unit)
    , attributes :: (UA.Slot Unit)
    , background :: (UB.Slot Unit)
    , possibleFoci :: (PossibleFoci.Slot Unit)
    )

_xp = Proxy :: Proxy "xp"


@@ 53,6 54,8 @@ _attributes = Proxy :: Proxy "attributes"

_background = Proxy :: Proxy "background"

_possibleFoci = Proxy :: Proxy "possibleFoci"

component :: forall m. MonadEffect m => H.Component Query Input Output m
component =
  H.mkComponent


@@ 79,21 82,26 @@ render state =
    attr = L.view C._attributes state

    background = L.view C._background state

    possibleFoci = L.view C._possibleFoci state
  in
    HH.div
      [ Util.classes
          [ "border-solid"
          , "border-2"
          , "border-black"
          , "m-2"
          , "p-2"
          , "w-4/6"
    HH.div []
      [ HH.div
          [ Util.classes
              [ "border-solid"
              , "border-2"
              , "border-black"
              , "m-2"
              , "p-2"
              , "w-4/6"
              ]
          ]
      ]
      [ HH.slot _class unit UC.component cl ChangeClass
      , HH.div [ Util.classes [ ]] [ HH.slot _background unit UB.component background ChangeBackground ]
      , HH.div [ Util.classes [ "grid", "grid-cols-2", "w-1/2" ] ] [ HH.slot _xp unit UIXP.component { xp } XPOutput, HH.slot_ _lvl unit UILevel.component xp ]
      , HH.slot _attributes unit UA.component attr ChangeAttributes
          [ HH.slot _class unit UC.component cl ChangeClass
          , HH.div [ Util.classes [] ] [ HH.slot _background unit UB.component background ChangeBackground ]
          , HH.div [ Util.classes [ "grid", "grid-cols-2", "w-1/2" ] ] [ HH.slot _xp unit UIXP.component { xp } XPOutput, HH.slot_ _lvl unit UILevel.component xp ]
          , HH.slot _attributes unit UA.component attr ChangeAttributes
          ]
      , HH.div [] [ HH.slot_ _possibleFoci unit PossibleFoci.component possibleFoci ]
      ]

handleAction :: forall m. MonadEffect m => Action -> H.HalogenM State Action Slots Output m Unit

A src/UI/Foci/PossibleList.purs => src/UI/Foci/PossibleList.purs +27 -0
@@ 0,0 1,27 @@
module UI.Foci.PossibleList where

import Prelude
import CharSheet.Focus as CF
import Data.Lens as L
import Halogen as H
import Halogen.HTML as HH
import Halogen.Hooks as Hooks

-- list the possible foci so the user can select one.
type Input
  = Array CF.Focus

data Message
  = ChooseFocus CF.Focus

type Slot s
  = forall q. H.Slot q Message s

focus :: forall w i. CF.Focus -> HH.HTML w i
focus f = HH.div [] [ HH.h1_ [ HH.text (L.view CF._name f) ] ]

component :: forall q m. H.Component q Input Message m
component =
  Hooks.component \_ possibleFoci -> Hooks.do
    Hooks.pure do
      HH.div [] (focus <$> possibleFoci)