This commit is contained in:
Caleb Fultz 2024-08-20 21:23:06 -04:00
commit ba4a7638c0
5 changed files with 355 additions and 0 deletions

149
app.js Normal file
View File

@ -0,0 +1,149 @@
let cardList = [];
document.addEventListener('DOMContentLoaded', () => {
const toggle = document.getElementById('dark-mode-toggle');
toggle.addEventListener('change', () => {
if (toggle.checked) {
document.documentElement.classList.add('dark-mode');
localStorage.setItem('theme', 'dark');
} else {
document.documentElement.classList.remove('dark-mode');
localStorage.setItem('theme', 'light');
}
});
// Load the saved theme preference
const savedTheme = localStorage.getItem('theme');
if (savedTheme === 'dark') {
document.documentElement.classList.add('dark-mode');
toggle.checked = true;
}
});
async function fetchCardSuggestions(query) {
const response = await fetch(`https://api.scryfall.com/cards/autocomplete?q=${encodeURIComponent(query)}`);
const data = await response.json();
return data.data;
}
document.getElementById('card-name').addEventListener('input', async function() {
const query = this.value;
const suggestions = await fetchCardSuggestions(query);
const suggestionsDiv = document.getElementById('suggestions');
suggestionsDiv.innerHTML = '';
suggestions.forEach(suggestion => {
const div = document.createElement('div');
div.textContent = suggestion;
div.onclick = () => {
document.getElementById('card-name').value = suggestion;
suggestionsDiv.innerHTML = '';
};
suggestionsDiv.appendChild(div);
});
});
async function fetchCardDetails(cardName, setCode) {
const response = await fetch(`https://api.scryfall.com/cards/named?exact=${encodeURIComponent(cardName)}&set=${encodeURIComponent(setCode)}`);
const data = await response.json();
return {
mana_cost: data.mana_cost || 'N/A',
power: data.power || 'N/A',
toughness: data.toughness || 'N/A',
type: data.type_line.split('—')[0].trim(), // Remove anything after em dash
rarity: data.rarity || 'N/A'
};
}
async function addCard() {
const cardName = document.getElementById('card-name').value;
const setCode = document.getElementById('set-code').value;
const cardId = document.getElementById('card-id').value;
const quantity = document.getElementById('quantity').value;
const foil = document.getElementById('foil').value;
const cardDetails = await fetchCardDetails(cardName, setCode);
const cardData = {
name: cardName,
set: setCode,
card_id: cardId,
quantity: quantity,
foil: foil,
mana_type: cardDetails.mana_cost,
power: cardDetails.power,
toughness: cardDetails.toughness,
type: cardDetails.type,
rarity: cardDetails.rarity
};
cardList.push(cardData);
// Update the textarea with the new card information
let csvContent = generateCSVContent();
document.getElementById('csv-output').value = csvContent;
// Show the card image in the dissolving popup
showCardPopup(cardName, setCode);
}
function generateCSVContent() {
let csvContent = "Card Name,Set,Card ID,Quantity,Foil,Mana Type,Power,Toughness,Type,Rarity\n";
cardList.forEach(card => {
const escapedValues = [
card.name,
card.set,
card.card_id,
card.quantity,
card.foil,
card.mana_type,
card.power,
card.toughness,
card.type,
card.rarity
].map(value => `"${value.replace(/"/g, '""')}"`); // Escape any existing quotes by doubling them
csvContent += escapedValues.join(",") + "\n";
});
return csvContent;
}
async function showCardPopup(cardName, setCode) {
const cardPopup = document.getElementById('card-popup');
const response = await fetch(`https://api.scryfall.com/cards/named?exact=${encodeURIComponent(cardName)}&set=${encodeURIComponent(setCode)}`);
const data = await response.json();
const imageUrl = data.image_uris ? data.image_uris.small : null;
if (imageUrl) {
cardPopup.innerHTML = `<img src="${imageUrl}" alt="${cardName}">`;
cardPopup.style.display = 'block';
cardPopup.style.opacity = 1;
// Dissolve the popup after a few seconds
setTimeout(() => {
cardPopup.style.opacity = 0;
setTimeout(() => {
cardPopup.style.display = 'none';
}, 500);
}, 3000);
}
}
function downloadCSV() {
const csvContent = generateCSVContent();
// Create a blob with the CSV content
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
const url = URL.createObjectURL(blob);
// Create a temporary link element
const link = document.createElement("a");
link.setAttribute("href", url);
link.setAttribute("download", "mtg_cards.csv");
document.body.appendChild(link);
// Trigger the download
link.click();
// Clean up and remove the link
document.body.removeChild(link);
}

47
index.html Normal file
View File

@ -0,0 +1,47 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>MTG Card CSV Generator</title>
<link rel="stylesheet" href="styles.css">
<link rel="manifest" href="manifest.json">
<link href="https://fonts.googleapis.com/css2?family=Sorts+Mill+Goudy&display=swap" rel="stylesheet">
</head>
<body>
<div class="container">
<h1>MTG Card CSV Generator</h1>
<form id="card-form">
<div>
<label for="card-name">Card Name:</label>
<input type="text" id="card-name" autocomplete="off" required>
<div id="suggestions"></div>
</div>
<div>
<label for="set-code">Set Code:</label>
<input type="text" id="set-code" required>
</div>
<div>
<label for="card-id">Card ID:</label>
<input type="text" id="card-id" required>
</div>
<div>
<label for="quantity">Quantity:</label>
<input type="number" id="quantity" min="1" value="1" required>
</div>
<div>
<label for="foil">Is it Foil?</label>
<select id="foil">
<option value="No">No</option>
<option value="Yes">Yes</option>
</select>
</div>
<button type="button" onclick="addCard()">Add Card</button>
</form>
<button onclick="downloadCSV()">Download CSV</button>
<textarea id="csv-output" readonly></textarea>
<div id="card-popup" class="card-popup"></div>
</div>
<script src="app.js"></script>
</body>
</html>

22
manifest.json Normal file
View File

@ -0,0 +1,22 @@
{
"name": "MTG Card CSV Generator",
"short_name": "MTG CSV",
"start_url": ".",
"display": "standalone",
"background_color": "#cc5200",
"theme_color": "#007acc",
"description": "A PWA for generating CSV files with Magic: The Gathering card details",
"icons": [
{
"src": "icon.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "icon.png",
"sizes": "512x512",
"type": "image/png"
}
]
}

22
service-worker.js Normal file
View File

@ -0,0 +1,22 @@
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open('mtg-csv-generator-v1').then((cache) => {
return cache.addAll([
'/',
'/index.html',
'/styles.css',
'/app.js',
'/manifest.json',
'/icon.png'
]);
})
);
});
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then((response) => {
return response || fetch(event.request);
})
);
});

115
styles.css Normal file
View File

@ -0,0 +1,115 @@
:root {
--background-color: #f0f0f0;
--text-color: #333;
--card-background: #ffffff;
--button-background: #007acc;
--button-hover: #005fa3;
}
@media (prefers-color-scheme: dark) {
:root {
--background-color: #1e1e1e;
--text-color: #ffffff;
--card-background: #2e2e2e;
--button-background: #1f6fb2;
--button-hover: #145b8c;
}
}
body {
font-family: Arial, sans-serif;
background-color: var(--background-color);
color: var(--text-color);
margin: 0;
padding: 20px;
}
.container {
max-width: 600px;
margin: 0 auto;
background-color: var(--card-background);
padding: 20px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
border-radius: 8px;
}
h1 {
text-align: center;
margin-bottom: 20px;
font-family: 'Sorts Mill Goudy', serif;
}
form div {
margin-bottom: 15px;
}
label {
display: block;
margin-bottom: 5px;
}
input, select, textarea {
width: 100%;
padding: 8px;
box-sizing: border-box;
border: 1px solid #ccc;
border-radius: 4px;
background-color: var(--card-background);
color: var(--text-color);
}
button {
display: block;
width: 100%;
padding: 10px;
background-color: var(--button-background);
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
margin-top: 10px;
}
button:hover {
background-color: var(--button-hover);
}
#csv-output {
margin-top: 20px;
height: 200px;
background-color: var(--card-background);
color: var(--text-color);
}
.card-popup {
position: fixed;
bottom: 20px;
right: 20px;
max-width: 20%;
z-index: 1000;
background-color: rgba(0, 0, 0, 0.7);
padding: 10px;
border-radius: 8px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
opacity: 0;
transition: opacity 0.5s ease-in-out;
display: none;
}
.card-popup img {
width: 100%;
height: auto;
border-radius: 4px;
}
@media (max-width: 600px) {
.card-popup {
max-width: 40%;
}
}
@media (max-width: 400px) {
.card-popup {
max-width: 60%;
}
}