できたもの
以下のページの下部「変異器官」をクリック。スプレッドシートから取得した値を表示する
ルールブック
グーグルドキュメントは以下。
https://docs.google.com/spreadsheets/d/1cyGpEw4GPI2k5snngBPKz7rfETklKdSaIBqQKnTta1w/view#gid=0
やったことのメモ
apiキーの環境変数化
jsに含めるのでどうせ見えることにはなるが、ソース管理から外したかった。
docker-composeの設定
env_fileで.envファイルを読み込むように修正
garden:
build: ./garden-webpack
+ env_file: .env
environment:
- NODE_ENV=develop
- SHELL=/bin/bash
command: [yarn, webpack-dev-server, --hot, --colors, --port, '3000', --host, '0.0.0.0', ]
.envファイルにgoogleapiキーを保存
webpackの設定
webpack.DefinePluginを使用して環境変数を読み込めるようにする。
if (MODE === 'development') {
console.log('Building for dev...');
module.exports = merge(common, {
plugins: [
// Suggested for hot-loading
new webpack.NamedModulesPlugin()
// Prevents compilation errors causing the hot loader to lose state
, new webpack.NoEmitOnErrorsPlugin()
+ , new webpack.DefinePlugin({
+ GOOGLE_SHEET_API_KEY: JSON.stringify(process.env.GOOGLE_SHEET_API_KEY)
+ })
],
module: {
typescriptの設定
typescritptでグローバル変数を読み込むために以下のファイルを作成する。*
// グローバル変数。webpack.DefinePluginで定義。環境変数から読み出し
declare var GOOGLE_SHEET_API_KEY: string;
const _GOOGLE_SHEET_API_KEY = GOOGLE_SHEET_API_KEY;
export { _GOOGLE_SHEET_API_KEY as GOOGLE_SHEET_API_KEY };
flagsにJSONを詰めてELMに渡す
import { Elm } from './Main'; // eslint-disable-line import/no-unresolved
import { GOOGLE_SHEET_API_KEY } from "./constants";
const flags = JSON.stringify({
googleSheetApiKey: GOOGLE_SHEET_API_KEY
});
const mountNode: HTMLElement = document.getElementById('main')!;
const app = Elm.Main.init({ node: mountNode, flags });
elmの設定
flagsに詰められて渡された値を取り出す
type alias Model =
{ key : Nav.Key
, page : Page
, googleSheetApiKey : String
}
init : String -> Url.Url -> Nav.Key -> ( Model, Cmd Msg )
init flags url key =
let
apiKey =
case D.decodeString (D.field "googleSheetApiKey" D.string) flags of
Ok decodedKey ->
decodedKey
Err _ ->
""
in
Model key (TopPage Page.Top.initModel) apiKey
|> goTo (Route.parse url)
|> initialized
initialized : ( Model, Cmd Msg ) -> ( Model, Cmd Msg )
initialized ( model, msg ) =
( model, Cmd.batch [ msg, initializedToJs () ] )
goTo : Maybe Route -> Model -> ( Model, Cmd Msg )
goTo maybeRoute model =
case maybeRoute of
Just (Route.RuleBook id) ->
let
( m, cmd ) =
RuleBook.init model.googleSheetApiKey id
in
( { model | page = RuleBookPage m }
, Cmd.map RuleBookMsg cmd
)
GSAPI.getOrgansで呼び出している。
port module Page.RuleBook exposing (Model, Msg(..), init, update, view)
import Browser.Dom as Dom
import Browser.Navigation as Navigation
import GoogleSpreadSheetApi as GSAPI exposing (Organ)
import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (onClick)
import Http
import Page.Rules.Base exposing (..)
import Skeleton exposing (viewLink, viewMain)
import Task exposing (..)
import Url
import Url.Builder
port openModal : () -> Cmd msg
type alias Model =
{ naviState : NaviState
, googleSheetApiKey : String
, id : String
, modalTitle : String
, modalContents : Html Msg
}
init : String -> Maybe String -> ( Model, Cmd Msg )
init apiKey s =
case s of
Just id ->
( Model Close apiKey id "" (text ""), jumpToBottom id )
Nothing ->
( initModel apiKey
, Cmd.none
)
initModel : String -> Model
initModel apiKey =
Model Close apiKey "" "異形器官一覧" (text "")
type Msg
= ModalOrgan String
| GotOrgans (Result Http.Error (List Organ))
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
ModalOrgan title ->
( { model | modalTitle = title }, GSAPI.getOrgans GotOrgans model.googleSheetApiKey "1cyGpEw4GPI2k5snngBPKz7rfETklKdSaIBqQKnTta1w" "organList!A1:B11" )
GotOrgans (Ok organs) ->
let
organTable =
organList organs
in
( { model | modalContents = organTable }, openModal () )
GotOrgans (Err _) ->
( model, Cmd.none )
view : Model -> Skeleton.Details Msg
view model =
let
naviClass =
getNavigationPageClass
model.naviState
in
{ title = "基本ルール"
, attrs = [ class naviClass ]
, kids =
[ viewMain viewRulebook
, viewNavi (List.map (\( value, text ) -> NavigationMenu value text) tableOfContents)
, openNavigationButton ToggleNavigation
, closeNavigationButton ToggleNavigation
, modalWindow model.modalTitle model.modalContents
]
}
modalWindow : String -> Html msg -> Html msg
modalWindow title content =
div [ id "mainModal", class "modal" ]
[ div [ class "modal-content" ]
[ h4 [] [ text title ]
, p [] [ content ]
]
, div [ class "modal-footer" ]
[ a [ href "#!", class "modal-close waves-effect waves-green btn-flat" ] [ text "閉じる" ]
]
]
viewRulebook : Html Msg
viewRulebook =
div []
[ div [ class "rulebook-title" ] [ div [] [ text Terms.trpgGenre ], h1 [] [ text "Garden 基本ルールブック" ] ]
, div [ class "content" ]
[ first
, world
, character
, a [ onClick (ModalOrgan "変異器官一覧"), class "waves-effect waves-light btn", href "#" ] [ text "変異器官一覧" ]
]
]
organList : List Organ -> Html Msg
organList organs =
table []
[ thead []
[ tr []
[ th [] [ text "器官" ]
, th [] [ text "説明" ]
]
]
, tbody []
(List.map (\organ -> tr [] [ td [] [ text organ.name ], td [] [ text organ.description ] ]) organs)
]
module GoogleSpreadSheetApi exposing (Organ, getOrgans, organDecodeFromString, organDecoder, organsDecodeFromString, organsDecoder, organsInObjectDecodeFromString, organsInObjectDecoder, sheetUrl)
import Http
import Json.Decode as D exposing (..)
import Url
import Url.Builder
type alias Organ =
{ name : String
, description : String
}
getOrgans : (Result Http.Error (List Organ) -> msg) -> String -> String -> String -> Cmd msg
getOrgans toMsg apiKey documentId range =
Http.get
{ url = sheetUrl apiKey documentId range
, expect = Http.expectJson toMsg organsInObjectDecoder
}
sheetUrl : String -> String -> String -> String
sheetUrl apiKey documentId range =
Url.Builder.crossOrigin "https://sheets.googleapis.com" [ "v4", "spreadsheets", documentId, "values", range ] [ Url.Builder.string "key" apiKey ]
organsInObjectDecodeFromString : String -> Result Error (List Organ)
organsInObjectDecodeFromString s =
decodeString organsInObjectDecoder s
organsInObjectDecoder : Decoder (List Organ)
organsInObjectDecoder =
field "values" organsDecoder
organsDecodeFromString : String -> Result Error (List Organ)
organsDecodeFromString s =
decodeString organsDecoder s
organsDecoder : Decoder (List Organ)
organsDecoder =
D.list organDecoder
organDecodeFromString : String -> Result Error Organ
organDecodeFromString s =
decodeString organDecoder s
organDecoder : Decoder Organ
organDecoder =
D.map2 Organ
(index 0 string)
(index 1 string)
module Tests exposing (sheet, suite, unitTest)
で、exposingしないとテストしてもらえないのにしばらく気づかずにハマった。
module Tests exposing (sheet, suite, unitTest)
import Expect
import GoogleSpreadSheetApi as GSApi
import Route exposing (..)
import Test exposing (..)
import Url
----
unitTest : Test
unitTest =
describe "simple unit test"
[ test "Inc adds one" <|
\() ->
1
|> Expect.equal 1
]
{-| テストケースを簡単に書くためのヘルパー関数
-}
testParse : String -> String -> Maybe Route -> Test
testParse name path expectedRoute =
test name <|
\_ ->
Url.fromString ("http://example.com" ++ path)
|> Maybe.andThen Route.parse
|> Expect.equal expectedRoute
suite : Test
suite =
describe "Route"
[ testParse "shold parse Top" "/" (Just Route.Top)
, testParse "shold parse Top with quesies" "/?dummy=value" (Just Route.Top)
, testParse "shold parse Top with hash" "/#dumy" (Just Route.Top)
, testParse "shold parse RuleBook" "/rulebook" (Just (Route.RuleBook Nothing))
, testParse "shold parse RuleBook with hash" "/rulebook#first" (Just (Route.RuleBook (Just "first")))
, testParse "shold parse PrivacyPolicy" "/privacy-policy" (Just Route.PrivacyPolicy)
, testParse "shold parse Agreement" "/agreement" (Just Route.Agreement)
, testParse "shold parse LoginUser" "/mypage" (Just Route.LoginUser)
, testParse "shold parse CreateCharacter" "/mypage/character/create/aaa" (Just (Route.CharacterCreate "aaa"))
, testParse "shold parse UpdateCharacter" "/mypage/character/edit/aaa/bbb" (Just (Route.CharacterUpdate "aaa" "bbb"))
, testParse "shold parse Inavalid path" "/foo/bar/baz" Nothing
]
sheet : Test
sheet =
describe "GoogleSpreadSheet"
[ test "配列をレコードに入れるテスト" <|
\_ ->
let
actual =
case GSApi.organDecodeFromString "[\"a\", \"b\"]" of
Ok a ->
a
Err _ ->
GSApi.Organ "" ""
expect =
{ name = "a", description = "b" }
in
Expect.equal actual expect
, test "配列の配列を処理するテスト" <|
\_ ->
let
actual =
case GSApi.organsDecodeFromString "[[\"a\", \"b\"],[\"c\", \"d\"]]" of
Ok a ->
a
Err _ ->
[ GSApi.Organ "a" "b" ]
expect =
[ GSApi.Organ "a" "b", GSApi.Organ "c" "d" ]
in
Expect.equal actual expect
, test "オブジェクトの中の配列を処理するテスト" <|
\_ ->
let
actual =
case GSApi.organsInObjectDecodeFromString "{\"values\":[[\"a\", \"b\"],[\"c\", \"d\"]]}" of
Ok a ->
a
Err _ ->
[]
expect =
[ GSApi.Organ "a" "b", GSApi.Organ "c" "d" ]
in
Expect.equal actual expect
]
参考
expect
json-decode
url builder
google spread
Webpackを使ってJSでも.envしたい
Web Pack define plugin variables are recoginized by ts-loader