floriferous: add basic score calculator component
This commit is contained in:
parent
70e7e982e9
commit
579da441d1
3 changed files with 279 additions and 34 deletions
|
|
@ -3,6 +3,7 @@
|
||||||
import { slide } from 'svelte/transition';
|
import { slide } from 'svelte/transition';
|
||||||
import type { PageData } from './$types.js';
|
import type { PageData } from './$types.js';
|
||||||
import Navbar from '$lib/components/Navbar.svelte';
|
import Navbar from '$lib/components/Navbar.svelte';
|
||||||
|
import FloriferousScoreCalculator from './FloriferousScoreCalculator.svelte';
|
||||||
|
|
||||||
import { FloriferousGame } from '../../../lib/floriferous/floriferous-game.js';
|
import { FloriferousGame } from '../../../lib/floriferous/floriferous-game.js';
|
||||||
import type { FloriferousPlayer } from '../../../lib/floriferous';
|
import type { FloriferousPlayer } from '../../../lib/floriferous';
|
||||||
|
|
@ -19,6 +20,8 @@
|
||||||
let previousGames: FloriferousGame[] = data.previousGames;
|
let previousGames: FloriferousGame[] = data.previousGames;
|
||||||
let apiPassword = '';
|
let apiPassword = '';
|
||||||
let players: FloriferousPlayer[] = [];
|
let players: FloriferousPlayer[] = [];
|
||||||
|
let isScoreCalculatorVisible = true;
|
||||||
|
let isPlayersVisible = false;
|
||||||
let isPreviousScoresVisible = false;
|
let isPreviousScoresVisible = false;
|
||||||
let isWinnerVisible = false;
|
let isWinnerVisible = false;
|
||||||
let isSaveSubmitting = false;
|
let isSaveSubmitting = false;
|
||||||
|
|
@ -92,42 +95,64 @@
|
||||||
abundance of nature.
|
abundance of nature.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<section class="players">
|
<section class="score-calculator">
|
||||||
<h2>Players</h2>
|
<div class="score-calculator__header">
|
||||||
|
<h2>Score Calculator</h2>
|
||||||
{#if players.length > 0}
|
{#if isScoreCalculatorVisible}
|
||||||
<ul>
|
<button on:click={() => (isScoreCalculatorVisible = false)}>Hide</button>
|
||||||
{#each players as player}
|
{:else}
|
||||||
<li>
|
<button on:click={() => (isScoreCalculatorVisible = true)}>Show</button>
|
||||||
{player.name} ({player.score} points, finished on row {player.rowAtEndOfGame}) (<button
|
|
||||||
on:click={() => onRemovePlayer(player)}>Remove</button
|
|
||||||
>)
|
|
||||||
</li>
|
|
||||||
{/each}
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
{#if players.length > 1}
|
|
||||||
{#if isWinnerVisible}
|
|
||||||
<p transition:slide>And the winner is:<strong>{game.winner}</strong></p>
|
|
||||||
<h3>Add to Ledger</h3>
|
|
||||||
|
|
||||||
<ApiPasswordFrom on:change={(event) => (apiPassword = event.detail)} />
|
|
||||||
|
|
||||||
{#if apiPassword.length > 0}
|
|
||||||
<p>You can save this game in the Ledger</p>
|
|
||||||
<button on:click={onSaveGame}>Save Game</button>
|
|
||||||
{/if}
|
|
||||||
{:else}
|
|
||||||
<button on:click={handleShowWinner}>Show me the winner</button>
|
|
||||||
{/if}
|
|
||||||
{/if}
|
{/if}
|
||||||
{:else}
|
</div>
|
||||||
<p>Add at least one player to get started</p>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
{#if !isWinnerVisible}
|
<FloriferousScoreCalculator isVisible={isScoreCalculatorVisible} />
|
||||||
<h3>Add a New Player</h3>
|
</section>
|
||||||
<FloriferousPlayerForm on:submit={onAddPlayer} />
|
|
||||||
|
<section class="players">
|
||||||
|
<div class="players__header">
|
||||||
|
<h2>Players</h2>
|
||||||
|
{#if isPlayersVisible}
|
||||||
|
<button on:click={() => (isPlayersVisible = false)}>Hide</button>
|
||||||
|
{:else}
|
||||||
|
<button on:click={() => (isPlayersVisible = true)}>Show</button>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if isPlayersVisible}
|
||||||
|
{#if players.length > 0}
|
||||||
|
<ul>
|
||||||
|
{#each players as player}
|
||||||
|
<li>
|
||||||
|
{player.name} ({player.score} points, finished on row {player.rowAtEndOfGame}) (<button
|
||||||
|
on:click={() => onRemovePlayer(player)}>Remove</button
|
||||||
|
>)
|
||||||
|
</li>
|
||||||
|
{/each}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
{#if players.length > 1}
|
||||||
|
{#if isWinnerVisible}
|
||||||
|
<p transition:slide>And the winner is:<strong>{game.winner}</strong></p>
|
||||||
|
<h3>Add to Ledger</h3>
|
||||||
|
|
||||||
|
<ApiPasswordFrom on:change={(event) => (apiPassword = event.detail)} />
|
||||||
|
|
||||||
|
{#if apiPassword.length > 0}
|
||||||
|
<p>You can save this game in the Ledger</p>
|
||||||
|
<button on:click={onSaveGame}>Save Game</button>
|
||||||
|
{/if}
|
||||||
|
{:else}
|
||||||
|
<button on:click={handleShowWinner}>Show me the winner</button>
|
||||||
|
{/if}
|
||||||
|
{/if}
|
||||||
|
{:else}
|
||||||
|
<p>Add at least one player to get started</p>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if !isWinnerVisible}
|
||||||
|
<h3>Add a New Player</h3>
|
||||||
|
<FloriferousPlayerForm on:submit={onAddPlayer} />
|
||||||
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|
@ -147,6 +172,26 @@
|
||||||
max-width: 600px;
|
max-width: 600px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.players {
|
||||||
|
padding: var(--spacing-md) 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.players__header {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto min-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-calculator {
|
||||||
|
padding: var(--spacing-md) 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-calculator__header {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto min-content;
|
||||||
|
}
|
||||||
|
|
||||||
.previous-games {
|
.previous-games {
|
||||||
padding: var(--spacing-md) 0;
|
padding: var(--spacing-md) 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
|
||||||
188
src/routes/games/floriferous/FloriferousScoreCalculator.svelte
Normal file
188
src/routes/games/floriferous/FloriferousScoreCalculator.svelte
Normal file
|
|
@ -0,0 +1,188 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { slide } from 'svelte/transition';
|
||||||
|
export let isVisible: boolean;
|
||||||
|
|
||||||
|
let currentScore = 0;
|
||||||
|
let actions = [];
|
||||||
|
|
||||||
|
let newActionScore = 0;
|
||||||
|
let newActionName = '';
|
||||||
|
let newActionNameInput;
|
||||||
|
let isNewActionNameVisible = false;
|
||||||
|
|
||||||
|
function incrementNewActionScore() {
|
||||||
|
newActionScore += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
function decrementNewActionScore() {
|
||||||
|
newActionScore -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
function onNewActionSubmit(score: number, name = '') {
|
||||||
|
actions = [...actions, { score, name }];
|
||||||
|
currentScore += score;
|
||||||
|
newActionScore = 0;
|
||||||
|
newActionName = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleIsNewActionNameVisible() {
|
||||||
|
isNewActionNameVisible = true;
|
||||||
|
newActionNameInput.focus();
|
||||||
|
newActionName = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const suggestedDescriptions = [
|
||||||
|
'Arrangement Card',
|
||||||
|
'Desire Card',
|
||||||
|
'Bounty',
|
||||||
|
'Stones',
|
||||||
|
'Cup of Tea'
|
||||||
|
];
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if isVisible}
|
||||||
|
<div transition:slide>
|
||||||
|
<div>
|
||||||
|
<p>Your score is {currentScore}</p>
|
||||||
|
|
||||||
|
<ul class="actions-list">
|
||||||
|
{#each actions as action}
|
||||||
|
<li class="actions-list__item">
|
||||||
|
+{action.score}
|
||||||
|
{#if action.name.length > 0} ({action.name}) {/if}
|
||||||
|
</li>
|
||||||
|
{/each}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form
|
||||||
|
class="form"
|
||||||
|
on:submit|preventDefault={() => onNewActionSubmit(newActionScore, newActionName)}
|
||||||
|
>
|
||||||
|
<div class="form-field">
|
||||||
|
<label class="form__label" for="points">Points*</label>
|
||||||
|
<input required name="points" type="number" bind:value={newActionScore} step="1" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="increment-decrement">
|
||||||
|
<button type="button" on:click={decrementNewActionScore}>-</button>
|
||||||
|
<button type="button" on:click={incrementNewActionScore}>+</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-field">
|
||||||
|
<fieldset class="suggested-descriptions">
|
||||||
|
<legend>Reason for Points</legend>
|
||||||
|
{#each suggestedDescriptions as suggestion}
|
||||||
|
<label
|
||||||
|
class="suggested-descriptions__label"
|
||||||
|
class:selected={newActionName === suggestion}
|
||||||
|
for={`suggestion-${suggestion}`}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
name="suggestion"
|
||||||
|
id={`suggestion-${suggestion}`}
|
||||||
|
value={suggestion}
|
||||||
|
class="suggested-descriptions__item"
|
||||||
|
on:click={() => (newActionName = suggestion)}
|
||||||
|
checked={newActionName === suggestion}
|
||||||
|
/>
|
||||||
|
{suggestion}</label
|
||||||
|
>
|
||||||
|
{/each}
|
||||||
|
<label class="suggested-descriptions__item">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="suggested-descriptions__button"
|
||||||
|
on:click={toggleIsNewActionNameVisible}
|
||||||
|
>
|
||||||
|
Other
|
||||||
|
</button>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
transition:slide
|
||||||
|
name="action-name"
|
||||||
|
type="text"
|
||||||
|
step="1"
|
||||||
|
class:sr-only={!isNewActionNameVisible}
|
||||||
|
bind:value={newActionName}
|
||||||
|
bind:this={newActionNameInput}
|
||||||
|
/>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="submit">
|
||||||
|
<input type="submit" value="Add Points" class="thomaswilson-button form__submit" />
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.actions-list {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions-list__item {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 100%;
|
||||||
|
gap: var(--spacing-md);
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form__label {
|
||||||
|
font-size: var(--font-size-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-field {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.increment-decrement {
|
||||||
|
padding: var(--spacing-md) 0;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 50% 50%;
|
||||||
|
gap: var(--spacing-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.suggested-descriptions {
|
||||||
|
padding: var(--spacing-sm);
|
||||||
|
margin: 0;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.suggested-descriptions__item {
|
||||||
|
padding: var(--spacing-sm) 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.suggested-descriptions__label {
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
text-decoration: underline;
|
||||||
|
transition: all 0.15s;
|
||||||
|
padding: var(--spacing-sm) 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.suggested-descriptions__label.selected {
|
||||||
|
color: var(--brand-blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit {
|
||||||
|
padding-top: var(--spacing-lg);
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form__submit {
|
||||||
|
background: var(--brand-blue);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -131,3 +131,15 @@ ol {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sr-only {
|
||||||
|
position: absolute;
|
||||||
|
width: 1px;
|
||||||
|
height: 1px;
|
||||||
|
padding: 0;
|
||||||
|
margin: -1px;
|
||||||
|
overflow: hidden;
|
||||||
|
clip: rect(0, 0, 0, 0);
|
||||||
|
white-space: nowrap;
|
||||||
|
border-width: 0;
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue