Skip to content

Commit

Permalink
feat: Add suspend with loading indicator
Browse files Browse the repository at this point in the history
Fixes an issue where bad internet connection could
introduce faulty gamestates due to race-condition
  • Loading branch information
ankarhem committed Mar 26, 2022
1 parent 76f75de commit d5c84e9
Show file tree
Hide file tree
Showing 5 changed files with 66 additions and 38 deletions.
13 changes: 12 additions & 1 deletion src/components/Board.svelte
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<script lang="ts">
import { GameState } from '$lib/types';
import { fade } from 'svelte/transition';
import LoadingSpinner from './LoadingSpinner.svelte';
import Modal from './Modal/Modal.svelte';
import ShareModalContent from './Modal/ShareModalContent.svelte';
import { gameState } from './store';
Expand All @@ -23,8 +25,17 @@
<ShareModalContent {wordId} />
</Modal>
<div
class="text-[11vw] sm:text-5xl flex flex-1 items-center py-2 standalone:text-5xl lg:flex-grow-0 lg:py-6"
class="text-[11vw] sm:text-5xl flex flex-1 items-center my-2 standalone:text-5xl lg:flex-grow-0 lg:my-6 relative"
>
{#if $gameState.loading}
<div
class="absolute w-full h-full bg-black/10 rounded dark:text-primary-400"
in:fade={{ delay: 300, duration: 150 }}
out:fade={{ duration: 150 }}
>
<LoadingSpinner />
</div>
{/if}
<div class="grid gap-[0.08em] grid-cols-1">
{#each $gameState.grid as row, i (`row-${i}`)}
{#if $gameState.invalidWord && i === currentRow}
Expand Down
4 changes: 3 additions & 1 deletion src/components/Keyboard/Key.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
export let key: string;
export let specialKey: boolean = false;
export let disabled: boolean = false;
const dispatch = createEventDispatcher();
Expand Down Expand Up @@ -37,11 +38,12 @@

<div class={`grid ${specialKey === false ? '' : 'col-span-2'}`}>
<button
{disabled}
style="transition-delay: {animationDelay}ms"
class={`${stateStyle} ${
// to override the lesser border in stateStyles used on the tiles
state === TileState.Unknown ? 'border-primary-500' : ''
} col-start-1 row-start-1 border text-lg flex transition-colors items-center justify-center h-14 sm:h-16 rounded uppercase font-bold select-none`}
} col-start-1 row-start-1 border text-lg flex transition-colors items-center justify-center h-14 sm:h-16 rounded uppercase font-bold select-none disabled:cursor-not-allowed`}
on:click={handleClick}
>
<slot>
Expand Down
9 changes: 6 additions & 3 deletions src/components/Keyboard/Keyboard.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
const handleKey = (key: string) => {
if ($gameState.state !== GameState.Playing) return;
if ($gameState.loading) return;
switch (key) {
case 'Backspace':
Expand Down Expand Up @@ -54,12 +55,14 @@
const handleSubmit = async () => {
if (currentGuess.length !== 5) return;
$gameState.loading = true;
const response = await fetch(`/api/words/validate.json`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ word: currentGuess, rowIndex: $gameState.currentRow })
body: JSON.stringify({ word: currentGuess, rowIndex: currentRow })
});
const data = await response.json();
$gameState.loading = false;
if (!response.ok) {
if (response.status === 404) {
addNotification({
Expand Down Expand Up @@ -109,11 +112,11 @@
{#each middleRow as char}
<Key key={char} on:click={handleClick} />
{/each}
<Key specialKey key="Enter" on:click={handleClick}>⏎</Key>
<Key specialKey key="Enter" on:click={handleClick} disabled={$gameState.loading}>⏎</Key>
{#each bottomRow as char}
<Key key={char} on:click={handleClick} />
{/each}
<Key specialKey key="Backspace" on:click={handleClick}>
<Key specialKey key="Backspace" on:click={handleClick} disabled={$gameState.loading}>
<Icon src={Backspace} size="24px" />
</Key>
</div>
Expand Down
76 changes: 43 additions & 33 deletions src/components/LoadingSpinner.svelte
Original file line number Diff line number Diff line change
@@ -1,48 +1,58 @@
<script lang="ts">
import { fade } from 'svelte/transition';
export let radius: string | number = 30;
</script>

<div class="lds-ring" in:fade={{ delay: 200 }}>
<div />
<div />
<div />
<div />
<div
class="absolute top-0 left-0 right-0 bottom-0 flex min-h-[50px] animate-fade-in items-center justify-center"
>
<div class="h-5 w-5">
<svg
class="spinner"
viewBox="0 0 66 66"
xmlns="http://www.w3.org/2000/svg"
aria-live="polite"
aria-label={'Var god vänta...'}
>
<circle class="path" fill="none" cx="33" cy="33" r={radius} />
</svg>
</div>
</div>

<style>
.lds-ring {
display: inline-block;
position: relative;
width: 80px;
height: 80px;
.spinner {
animation: rotator 1.4s linear infinite;
}
.lds-ring div {
box-sizing: border-box;
display: block;
position: absolute;
width: 64px;
height: 64px;
margin: 8px;
border: 8px solid #fff;
border-radius: 50%;
animation: lds-ring 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;
border-color: #fff transparent transparent transparent;
}
.lds-ring div:nth-child(1) {
animation-delay: -0.45s;
}
.lds-ring div:nth-child(2) {
animation-delay: -0.3s;
}
.lds-ring div:nth-child(3) {
animation-delay: -0.15s;
.path {
stroke-dasharray: 187;
stroke-dashoffset: 0;
transform-origin: center;
stroke: currentColor;
stroke-width: 6;
stroke-linecap: round;
animation: dash 1.4s ease-in-out infinite;
}
@keyframes lds-ring {
@keyframes rotator {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
transform: rotate(270deg);
}
}
@keyframes dash {
0% {
stroke-dashoffset: 187;
}
50% {
stroke-dashoffset: 46.75;
transform: rotate(135deg);
}
100% {
stroke-dashoffset: 187;
transform: rotate(450deg);
}
}
</style>
2 changes: 2 additions & 0 deletions src/components/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const createEmptyGrid = (): TileType[][] => {

export const createInitialGameState = (wordId?: number): GameData => {
const gameState: GameData = {
loading: false,
grid: createEmptyGrid(),
currentRow: 0,
state: GameState.Playing,
Expand All @@ -24,6 +25,7 @@ export const createInitialGameState = (wordId?: number): GameData => {
};

interface GameData {
loading: boolean;
grid: TileType[][];
currentRow: number;
state: GameState;
Expand Down

0 comments on commit d5c84e9

Please sign in to comment.