Skip to content

Commit 945df5e

Browse files
committed
chore: Add maplit dependency, display type images and update window dimensions in tauri.conf.json
1 parent 3dbb0e3 commit 945df5e

File tree

7 files changed

+204
-60
lines changed

7 files changed

+204
-60
lines changed

LICENSE

Lines changed: 0 additions & 21 deletions
This file was deleted.

src-tauri/Cargo.lock

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src-tauri/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ serde = { version = "1", features = ["derive"] }
1616
serde_json = "1"
1717
reqwest = { version = "0.11", features = ["json", "blocking"] }
1818
tokio = { version = "1", features = ["full"] }
19+
maplit = "1.0"
1920

2021

2122
[features]

src-tauri/src/main.rs

Lines changed: 97 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,100 @@
11
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
22
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
33
use reqwest::get;
4-
use serde_json::Value;
4+
use serde_json::{Value, json};
5+
use std::collections::HashMap;
6+
7+
#[macro_use]
8+
extern crate maplit;
59

610
// Learn more about Tauri commands at https://tauri.app/v1/guides/features/command
711
#[tauri::command]
812
fn greet(name: &str) -> String {
913
format!("{} does not exist yet!", name)
1014
}
1115

12-
// fetch input from pokeapi
16+
// Function to fetch type effectiveness
17+
async fn fetch_type_data(url: &str) -> Result<Value, String> {
18+
match get(url).await {
19+
Ok(response) => {
20+
if response.status().is_success() {
21+
let json: Value = response.json().await.unwrap();
22+
Ok(json)
23+
} else {
24+
Err("Failed to fetch type data".to_string())
25+
}
26+
}
27+
Err(_) => Err("Failed to connect to PokéAPI".to_string()),
28+
}
29+
}
30+
31+
// Function to calculate weaknesses based on Pokémon types
32+
async fn calculate_weaknesses(types: &Vec<Value>) -> Result<HashMap<String, Vec<HashMap<String, String>>>, String> {
33+
let mut weaknesses: HashMap<String, Vec<HashMap<String, String>>> = HashMap::new();
34+
35+
for pokemon_type in types {
36+
let type_name = pokemon_type["type"]["name"].as_str().unwrap();
37+
let type_url = pokemon_type["type"]["url"].as_str().unwrap();
38+
let type_id = type_url.split('/').filter(|&s| !s.is_empty()).last().unwrap(); // Extract the type ID
39+
40+
let icon_url = format!("https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/types/generation-viii/sword-shield/{}.png", type_id);
41+
42+
if let Ok(type_data) = fetch_type_data(type_url).await {
43+
let damage_relations = &type_data["damage_relations"];
44+
45+
// Group by 2x effectiveness
46+
if let Some(double_damage_from) = damage_relations["double_damage_from"].as_array() {
47+
for damage_type in double_damage_from {
48+
let type_name = damage_type["name"].as_str().unwrap().to_string();
49+
let type_id = damage_type["url"].as_str().unwrap().split('/').filter(|&s| !s.is_empty()).last().unwrap();
50+
let icon_url = format!("https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/types/generation-viii/sword-shield/{}.png", type_id);
51+
weaknesses.entry("2x".to_string()).or_default().push(
52+
hashmap!{
53+
"type".to_string() => type_name,
54+
"icon".to_string() => icon_url
55+
}
56+
);
57+
}
58+
}
59+
60+
// Group by 0.5x effectiveness
61+
if let Some(half_damage_from) = damage_relations["half_damage_from"].as_array() {
62+
for damage_type in half_damage_from {
63+
let type_name = damage_type["name"].as_str().unwrap().to_string();
64+
let type_id = damage_type["url"].as_str().unwrap().split('/').filter(|&s| !s.is_empty()).last().unwrap();
65+
let icon_url = format!("https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/types/generation-viii/sword-shield/{}.png", type_id);
66+
weaknesses.entry("0.5x".to_string()).or_default().push(
67+
hashmap!{
68+
"type".to_string() => type_name,
69+
"icon".to_string() => icon_url
70+
}
71+
);
72+
}
73+
}
74+
75+
// Group by 0x effectiveness
76+
if let Some(no_damage_from) = damage_relations["no_damage_from"].as_array() {
77+
for damage_type in no_damage_from {
78+
let type_name = damage_type["name"].as_str().unwrap().to_string();
79+
let type_id = damage_type["url"].as_str().unwrap().split('/').filter(|&s| !s.is_empty()).last().unwrap();
80+
let icon_url = format!("https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/types/generation-viii/sword-shield/{}.png", type_id);
81+
weaknesses.entry("0x".to_string()).or_default().push(
82+
hashmap!{
83+
"type".to_string() => type_name,
84+
"icon".to_string() => icon_url
85+
}
86+
);
87+
}
88+
}
89+
} else {
90+
return Err("Failed to fetch type data".to_string());
91+
}
92+
}
93+
94+
Ok(weaknesses)
95+
}
96+
97+
// Fetch input from PokéAPI and include weaknesses
1398
#[tauri::command]
1499
async fn search_pokemon(name: &str) -> Result<String, String> {
15100
let url = format!("https://pokeapi.co/api/v2/pokemon/{}", name.to_lowercase());
@@ -18,7 +103,16 @@ async fn search_pokemon(name: &str) -> Result<String, String> {
18103
Ok(response) => {
19104
if response.status().is_success() {
20105
let json: Value = response.json().await.unwrap();
21-
Ok(json.to_string())
106+
let types = json["types"].as_array().unwrap();
107+
108+
// Calculate weaknesses
109+
let weaknesses = calculate_weaknesses(types).await?;
110+
111+
// Add weaknesses to the Pokémon data
112+
let mut result = json.clone();
113+
result["weaknesses"] = json!(weaknesses);
114+
115+
Ok(result.to_string())
22116
} else {
23117
Err(format!("Error: Pokémon {} not found!", name))
24118
}

src-tauri/tauri.conf.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@
1919
},
2020
"windows": [
2121
{
22-
"title": "pokedex",
23-
"width": 800,
24-
"height": 600
22+
"title": "PokeDex",
23+
"width": 550,
24+
"height": 950
2525
}
2626
],
2727
"security": {
@@ -40,4 +40,4 @@
4040
]
4141
}
4242
}
43-
}
43+
}

src/App.tsx

Lines changed: 71 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
import { useState } from "react";
22
import { invoke } from "@tauri-apps/api/tauri";
33
import "./App.css";
4+
import CollapsibleSection from "./components/CollapsibleSection";
45

56
function App() {
6-
const [pokemonData, setPokemonData] = useState(null);
7+
const [pokemonData, setPokemonData] = useState<any>(null);
78
const [error, setError] = useState("");
89
const [name, setName] = useState("");
910

1011
async function searchPokemon() {
1112
try {
12-
// Invoke the Tauri command to search for the Pokémon
1313
const result = await invoke("search_pokemon", { name });
14-
const data = JSON.parse(result);
14+
const data = JSON.parse(result as string) as any;
1515
setPokemonData(data);
1616
setError("");
1717
} catch (err) {
@@ -20,46 +20,85 @@ function App() {
2020
}
2121
}
2222

23-
return (
24-
<div className="m-0 pt-[10vh] flex flex-col justify-center text-center">
25-
<h1 className="text-3xl font-bold">
26-
Welcome to PokeDex!
27-
</h1>
23+
function capitalize(word: string) {
24+
return word.charAt(0).toUpperCase() + word.slice(1);
25+
}
2826

29-
<div className="flex justify-center">
30-
<img src="/image.png" className="h-64" alt="Pokedex logo" />
27+
return (
28+
<div className="m-0 pt-[5vh] flex flex-col justify-between h-screen text-center">
29+
<div>
30+
{pokemonData ? (
31+
<div>
32+
<h1 className="text-3xl font-bold capitalize">{capitalize(pokemonData?.name)}</h1>
33+
<img
34+
src={pokemonData?.sprites?.other?.['official-artwork']?.front_default}
35+
alt={pokemonData?.name}
36+
className="h-64 mx-auto"
37+
/>
38+
<p className="mt-2">
39+
<strong>Type:</strong> {pokemonData?.types?.map((type: any) => capitalize(type.type.name)).join(", ")}
40+
</p>
41+
<div className="mt-10">
42+
{pokemonData?.weaknesses ? (
43+
<>
44+
<CollapsibleSection title="2x Weaknesses">
45+
{pokemonData.weaknesses['2x']?.map((weakness: any) => (
46+
<div key={weakness.type} className="inline-block m-2">
47+
<img src={weakness.icon} alt={weakness.type} className="h-8 inline-block mr-2" />
48+
</div>
49+
)) || "None"}
50+
</CollapsibleSection>
51+
<CollapsibleSection title="0.5x Resistances">
52+
{pokemonData.weaknesses['0.5x']?.map((resistance: any) => (
53+
<div key={resistance.type} className="inline-block m-2">
54+
<img src={resistance.icon} alt={resistance.type} className="h-8 inline-block mr-2" />
55+
</div>
56+
)) || "None"}
57+
</CollapsibleSection>
58+
<CollapsibleSection title="0x Immunities">
59+
{pokemonData.weaknesses['0x']?.map((immunity: any) => (
60+
<div key={immunity.type} className="inline-block m-2">
61+
<img src={immunity.icon} alt={immunity.type} className="h-8 inline-block mr-2" />
62+
</div>
63+
)) || "None"}
64+
</CollapsibleSection>
65+
</>
66+
) : (
67+
"No weaknesses found"
68+
)}
69+
</div>
70+
</div>
71+
) : (
72+
<div>
73+
<h1 className="text-3xl font-bold capitalize">Welcome to PokeDex!</h1>
74+
<img
75+
src="/image.png"
76+
alt="Default Pokémon"
77+
className="h-64 mx-auto"
78+
/>
79+
</div>
80+
)}
3181
</div>
3282

83+
{error && <p className="text-red-500">{error}</p>}
84+
3385
<form
34-
className="flex justify-center gap-2"
86+
className="flex flex-col justify-center gap-2 mt-2"
3587
onSubmit={(e) => {
3688
e.preventDefault();
3789
searchPokemon();
3890
}}
3991
>
40-
<input
41-
id="pokemon-input"
42-
onChange={(e) => setName(e.currentTarget.value)}
43-
placeholder="Enter a Pokémon name..."
44-
/>
45-
<button type="submit" className="cursor-pointer">Find</button>
46-
</form>
47-
48-
{error && <p className="mt-2 text-red-500">{error}</p>}
49-
50-
{pokemonData && (
51-
<div className="mt-4">
52-
<h2 className="text-2xl font-bold capitalize">{pokemonData.name}</h2>
53-
<img
54-
src={pokemonData.sprites.front_default}
55-
alt={pokemonData.name}
56-
className="mx-auto"
92+
<div className="flex justify-center gap-2 mt-2">
93+
<input
94+
id="pokemon-input"
95+
onChange={(e) => setName(e.currentTarget.value.toLowerCase())}
96+
placeholder="Enter a Pokémon name..."
5797
/>
58-
<p className="mt-2">
59-
Type: {pokemonData.types.map((type) => type.type.name).join(", ")}
60-
</p>
98+
<button type="submit" className="cursor-pointer">Find</button>
6199
</div>
62-
)}
100+
<p className="text-xs italic">Note: Type effectiveness multipliers may vary in other games outside the core series.</p>
101+
</form>
63102
</div>
64103
);
65104
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import React, { useState } from "react";
2+
3+
interface CollapsibleSectionProps {
4+
title: string;
5+
children: React.ReactNode;
6+
}
7+
8+
const CollapsibleSection: React.FC<CollapsibleSectionProps> = ({ title, children }) => {
9+
const [isOpen, setIsOpen] = useState(false);
10+
11+
return (
12+
<div className="mb-2">
13+
<button
14+
className="text-lg font-bold w-64 mt-5"
15+
onClick={() => setIsOpen(!isOpen)}
16+
>
17+
{title} {isOpen ? "▲" : "▼"}
18+
</button>
19+
{isOpen && <div className="ml-4 mt-2">{children}</div>}
20+
</div>
21+
);
22+
};
23+
24+
export default CollapsibleSection;

0 commit comments

Comments
 (0)