Skip to content

Commit 5be48b8

Browse files
authored
Merge pull request #118 from RuneLabsxyz/Better-UI
Better UI
2 parents 32467a7 + dd05582 commit 5be48b8

File tree

6 files changed

+267
-19
lines changed

6 files changed

+267
-19
lines changed

client/src/lib/ui/TxToast.svelte

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
<script>
2+
import { fade } from 'svelte/transition';
3+
import { onMount } from 'svelte';
4+
5+
export let message = '';
6+
export let status = 'loading'; // 'loading', 'success', or 'error'
7+
export let duration = 3000; // Duration in milliseconds
8+
9+
let visible = true;
10+
11+
onMount(() => {
12+
if (status !== 'loading') {
13+
const timer = setTimeout(() => {
14+
visible = false;
15+
}, duration);
16+
return () => clearTimeout(timer);
17+
}
18+
});
19+
</script>
20+
21+
{#if visible}
22+
<div class="toast" transition:fade={{ duration: 300 }}>
23+
<div class="icon">
24+
{#if status === 'loading'}
25+
<div class="loading-circle"></div>
26+
{:else if status === 'success'}
27+
<svg class="checkmark" viewBox="0 0 24 24">
28+
<path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41L9 16.17z" />
29+
</svg>
30+
{:else if status === 'error'}
31+
<svg class="cross" viewBox="0 0 24 24">
32+
<path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z" />
33+
</svg>
34+
{/if}
35+
</div>
36+
<p>{message}</p>
37+
</div>
38+
{/if}
39+
40+
<style>
41+
.toast {
42+
position: fixed;
43+
top: 20px;
44+
right: 20px;
45+
background-color: #333;
46+
color: white;
47+
padding: 12px 20px;
48+
border-radius: 4px;
49+
display: flex;
50+
align-items: center;
51+
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
52+
z-index: 1000;
53+
}
54+
55+
.icon {
56+
margin-right: 12px;
57+
width: 24px;
58+
height: 24px;
59+
}
60+
61+
.loading-circle {
62+
border: 2px solid #f3f3f3;
63+
border-top: 2px solid #3498db;
64+
border-radius: 50%;
65+
width: 20px;
66+
height: 20px;
67+
animation: spin 1s linear infinite;
68+
}
69+
70+
@keyframes spin {
71+
0% { transform: rotate(0deg); }
72+
100% { transform: rotate(360deg); }
73+
}
74+
75+
.checkmark, .cross {
76+
fill: currentColor;
77+
}
78+
79+
.checkmark {
80+
color: #4CAF50;
81+
}
82+
83+
.cross {
84+
color: #F44336;
85+
}
86+
87+
p {
88+
margin: 0;
89+
}
90+
</style>
91+

client/src/routes/client/+layout.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
{/if}
3838
<Button href="/client/games/openGames">New Game</Button>
3939
<Button href="/client/games/yourGames">Your Games</Button>
40-
<Button href="/client/mapmaker">Maps</Button>
40+
<Button href="/client/maps">Maps</Button>
4141
<div class="flex-grow"></div>
4242
<Button href="/">Back to home screen</Button>
4343
</div>

client/src/routes/client/games/create/+page.svelte

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import { type Entity, getComponentValue } from '@dojoengine/recs'
77
import MiniMap from '$lib/MiniMap.svelte'
88
import Button from '$lib/ui/Button.svelte'
9+
import TxToast from '$lib/ui/TxToast.svelte'
910
import { cn } from '$lib/css/cn'
1011
import { SESSION_PRIMITIVES } from '$lib/consts'
1112
import { account, username, clearAccountStorage } from '$stores/account'
@@ -62,18 +63,37 @@
6263
console.log('selected map', map_id)
6364
}
6465
66+
let toastMessage = '';
67+
let toastStatus = 'loading';
68+
let showToast = false;
69+
6570
async function createGame() {
6671
if ($account) {
6772
console.log('SESSION_PRIMITIVES', SESSION_PRIMITIVES)
6873
console.log('selectedMap', $selectedMap)
69-
await client.start.create({
70-
account: $account,
71-
map_id: $selectedMap,
72-
session_primitives: SESSION_PRIMITIVES,
73-
})
74-
loadingToGame = true
74+
showToast = true;
75+
toastMessage = 'Creating game...';
76+
toastStatus = 'loading';
77+
try {
78+
await client.start.create({
79+
account: $account,
80+
map_id: $selectedMap,
81+
session_primitives: SESSION_PRIMITIVES,
82+
})
83+
toastMessage = 'Game created successfully!';
84+
toastStatus = 'success';
85+
loadingToGame = true;
86+
} catch (error) {
87+
console.error('Error creating game:', error);
88+
toastMessage = 'Failed to create game.';
89+
toastStatus = 'error';
90+
loadingToGame = false;
91+
}
7592
} else {
7693
console.error('No active account found')
94+
toastMessage = 'No active account found.';
95+
toastStatus = 'error';
96+
showToast = true;
7797
}
7898
}
7999
@@ -110,6 +130,10 @@
110130
</div>
111131
</div>
112132

133+
{#if showToast}
134+
<TxToast message={toastMessage} status={toastStatus} />
135+
{/if}
136+
113137
<style>
114138
.grid-fill {
115139
grid-template-columns: repeat(auto-fill, 330px);

client/src/routes/client/games/yourGames/+page.svelte

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,17 @@
3838
if (data) {
3939
const newSession: Session = {
4040
value: session.value,
41-
isYourTurn: areAddressesEqual(data.player1, $account.address),
41+
isYourTurn: false,
4242
isStarted: false,
4343
isFinished: data.state === 3
4444
}
4545
sessionMetaDataStore.subscribe((metaData) => {
46-
if (metaData && metaData.p1_character !== 0) {
47-
newSession.isStarted = true
46+
if (metaData) {
47+
newSession.isStarted = metaData.p1_character !== 0
48+
const currentPlayerId = areAddressesEqual(data.player1, $account.address) ? 1 : 2
49+
newSession.isYourTurn =
50+
(currentPlayerId === 1 && metaData.turn_count % 2 === 0) ||
51+
(currentPlayerId === 2 && metaData.turn_count % 2 === 1)
4852
}
4953
})
5054
sessions = [...sessions, newSession]

client/src/routes/client/mapmaker/+page.svelte

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,20 @@
33
import { writable } from 'svelte/store';
44
import { dojoStore } from '$stores/dojoStore';
55
import { account } from '$stores/account'
6-
import type { Account } from 'starknet';
7-
6+
import TxToast from '$lib/ui/TxToast.svelte';
7+
import { goto } from '$app/navigation';
88
99
const gridSize = 25;
1010
const totalCells = gridSize * gridSize;
1111
const grid = writable<number[]>([]);
1212
1313
let { client } = $dojoStore;
1414
15+
let toastMessage = '';
16+
let toastStatus = 'loading';
17+
let showToast = false;
1518
1619
onMount(() => {
17-
// Initialize the grid with no active cells
1820
grid.set([]);
1921
});
2022
@@ -35,8 +37,22 @@
3537
grid.set([]);
3638
}
3739
38-
function submit() {
39-
client.mapmaker.create({account: $account, objects: {objects: $grid}});
40+
async function submit() {
41+
showToast = true;
42+
toastMessage = 'Creating map...';
43+
toastStatus = 'loading';
44+
try {
45+
await client.mapmaker.create({account: $account, objects: {objects: $grid}});
46+
toastMessage = 'Map created successfully!';
47+
toastStatus = 'success';
48+
setTimeout(() => {
49+
goto('/client/games/openGames');
50+
}, 2000);
51+
} catch (error) {
52+
console.error('Error creating map:', error);
53+
toastMessage = 'Failed to create map.';
54+
toastStatus = 'error';
55+
}
4056
}
4157
4258
function isActive(index: number, activeIndices: number[]): boolean {
@@ -74,15 +90,21 @@
7490
</style>
7591

7692
<div class="controls">
77-
<button class = "ml-4 px-4 py-2 bg-gray-500 text-white rounded hover:bg-gray-700 transition" on:click={resetGrid}>Reset Grid</button>
78-
<button class = "mr-4 px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-700 transition" on:click={submit}>Submit</button>
93+
<button class="ml-4 px-4 py-2 bg-gray-500 text-white rounded hover:bg-gray-700 transition" on:click={resetGrid}>Reset Grid</button>
94+
<button class="mr-4 px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-700 transition" on:click={submit}>Submit</button>
7995
</div>
8096

8197
<div class="grid">
8298
{#each Array(totalCells) as _, index}
83-
<div
99+
<button
100+
type="button"
84101
class="cell {isActive(index, $grid) ? 'active' : ''}"
85102
on:click={() => toggleCell(Math.floor(index / gridSize), index % gridSize)}
86-
></div>
103+
on:keydown={(e) => e.key === 'Enter' && toggleCell(Math.floor(index / gridSize), index % gridSize)}
104+
/>
87105
{/each}
88106
</div>
107+
108+
{#if showToast}
109+
<TxToast message={toastMessage} status={toastStatus} />
110+
{/if}
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
<script lang="ts">
2+
import { dojoStore } from '$stores/dojoStore'
3+
import { componentValueStore } from '$dojo/componentValueStore'
4+
import { selectedMap } from '$stores/clientStores'
5+
import { goto } from '$app/navigation'
6+
import { type Entity, getComponentValue } from '@dojoengine/recs'
7+
import MiniMap from '$lib/MiniMap.svelte'
8+
import Button from '$lib/ui/Button.svelte'
9+
import TxToast from '$lib/ui/TxToast.svelte'
10+
import { cn } from '$lib/css/cn'
11+
import { account } from '$stores/account'
12+
13+
let playerEntity: Entity
14+
let mapCount: number = 0
15+
16+
$: ({ clientComponents, torii } = $dojoStore as any)
17+
18+
$: globalentity = torii.poseidonHash([BigInt(0).toString()])
19+
20+
$: if ($account) playerEntity = torii.poseidonHash([$account?.address])
21+
22+
$: global = componentValueStore(clientComponents.Global, globalentity)
23+
let maps: any[] = []
24+
25+
$: if ($global) {
26+
mapCount = $global.map_count
27+
maps = []
28+
for (let i = 0; i < mapCount; i++) {
29+
const map = getComponentValue(
30+
clientComponents.Map,
31+
torii.poseidonHash([BigInt(i).toString()])
32+
)
33+
maps.push(map)
34+
}
35+
}
36+
37+
38+
async function createMap() {
39+
goto('/client/mapmaker')
40+
}
41+
42+
</script>
43+
44+
<div class={cn('flex flex-col h-full')}>
45+
<div class="flex p-5 py-2 mb-4 items-center border-b-4 border-black">
46+
<h1 class="text-3xl font-bold">Available maps</h1>
47+
<span class="flex-grow"></span>
48+
<Button on:click={createMap}>Create new map</Button>
49+
</div>
50+
51+
<div
52+
class="grid grid-fill justify-around auto-cols-min px-3 overflow-auto overflow-x-hidden"
53+
>
54+
{#each maps as map}
55+
{#if map}
56+
<Button
57+
className="w-fit h-fit"
58+
>
59+
<MiniMap {map} />
60+
</Button>
61+
{/if}
62+
{/each}
63+
</div>
64+
</div>
65+
66+
67+
<style>
68+
.grid-fill {
69+
grid-template-columns: repeat(auto-fill, 330px);
70+
}
71+
72+
.map-grid {
73+
display: grid;
74+
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
75+
gap: 16px;
76+
padding: 16px;
77+
}
78+
79+
.flex {
80+
display: flex;
81+
}
82+
83+
.justify-between {
84+
justify-content: space-between;
85+
}
86+
87+
.p-4 {
88+
padding: 1rem;
89+
}
90+
91+
.fixed {
92+
position: fixed;
93+
}
94+
95+
.bottom-0 {
96+
bottom: 0;
97+
}
98+
99+
.left-0 {
100+
left: 0;
101+
}
102+
103+
.right-0 {
104+
right: 0;
105+
}
106+
</style>
107+

0 commit comments

Comments
 (0)